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 #include <stdio.h>
27 #include <string.h>
28 #include <time.h>
29 #include <errno.h>
30 
31 #ifdef HAVE_UNISTD_H
32 # include <unistd.h>
33 #endif
34 
35 #ifdef HAVE_SYS_TIME_H
36 # include <sys/time.h>
37 #endif
38 
39 #ifdef HAVE_STDLIB_H
40 # include <stdlib.h>
41 #endif
42 
43 #include <slang.h>
44 #include "jdmacros.h"
45 
46 /*}}}*/
47 /*{{{ slrn include files */
48 #include "slrn.h"
49 #include "group.h"
50 #include "art.h"
51 #include "art_sort.h"
52 #include "misc.h"
53 #include "post.h"
54 /* #include "clientlib.h" */
55 #include "startup.h"
56 #include "hash.h"
57 #include "score.h"
58 #include "menu.h"
59 #include "util.h"
60 #include "server.h"
61 #include "xover.h"
62 #include "charset.h"
63 #include "print.h"
64 #include "snprintf.h"
65 #include "mime.h"
66 #include "hooks.h"
67 #include "help.h"
68 #include "strutil.h"
69 #include "common.h"
70 
71 #if SLRN_HAS_UUDEVIEW
72 # include <uudeview.h>
73 #endif
74 #include "decode.h"
75 
76 #if SLRN_HAS_CANLOCK
77 # include <canlock.h>
78 #endif
79 
80 #if SLRN_HAS_GROUPLENS
81 # include "grplens.h"
82 #endif
83 
84 /*}}}*/
85 
86 /*{{{ extern Global variables  */
87 
88 SLKeyMap_List_Type *Slrn_Article_Keymap;
89 char *Slrn_X_Browser;
90 char *Slrn_NonX_Browser;
91 char *Slrn_Quote_String;
92 char *Slrn_Save_Directory;
93 char *Slrn_Header_Help_Line;
94 char *Slrn_Header_Status_Line;
95 char *Slrn_Art_Help_Line;
96 char *Slrn_Art_Status_Line;
97 char *Slrn_Followup_Custom_Headers;
98 char *Slrn_Reply_Custom_Headers;
99 char *Slrn_Supersedes_Custom_Headers;
100 char *Slrn_Overview_Date_Format;
101 char *Slrn_Followup_Date_Format;
102 int Slrn_Use_Localtime = 1;
103 
104 int Slrn_Emphasized_Text_Mode = 3;
105 #define EMPHASIZE_ARTICLE       1
106 #define EMPHASIZE_QUOTES	2
107 #define EMPHASIZE_SIGNATURE     4
108 #define EMPHASIZE_HEADER	8
109 int Slrn_Emphasized_Text_Mask = EMPHASIZE_ARTICLE;
110 int Slrn_Highlight_Urls = 1;
111 
112 int Slrn_Process_Verbatim_Marks = 1;
113 
114 Slrn_Article_Type *Slrn_Current_Article;
115 
116 int Slrn_Use_Tildes = 1;
117 
118 #if defined(IBMPC_SYSTEM)
119 int Slrn_Generate_Email_From = 1;
120 #else
121 int Slrn_Generate_Email_From = 0;
122 #endif
123 
124 int Slrn_Startup_With_Article = 0;
125 int Slrn_Followup_Strip_Sig = 1;
126 int Slrn_Smart_Quote = 1;
127 #define PIPE_RAW		0
128 #define PIPE_CONVERTED		1
129 #define PIPE_DECODED		2
130 int Slrn_Pipe_Type = 0;
131 
132 int Slrn_Query_Next_Article = 1;
133 int Slrn_Query_Next_Group = 1;
134 int Slrn_Auto_CC_To_Poster = 1;
135 int Slrn_Use_Tmpdir = 0;
136 int Slrn_Use_Header_Numbers = 1;
137 int Slrn_Warn_Followup_To = 1;
138 int Slrn_Color_By_Score = 3;
139 int Slrn_Highlight_Unread = 1;
140 int Slrn_High_Score_Min = 1;
141 int Slrn_Low_Score_Max = 0;
142 int Slrn_Kill_Score_Max = -9999;
143 FILE *Slrn_Kill_Log_FP = NULL;
144 int Slrn_Article_Window_Border = 0;
145 int Slrn_Reads_Per_Update = 50;
146 int Slrn_Sig_Is_End_Of_Article = 0;
147 #if SLRN_HAS_SPOILERS
148 int Slrn_Spoiler_Char = 42;
149 int Slrn_Spoiler_Display_Mode = 1;
150 #endif
151 
152 #if SLRN_HAS_UUDEVIEW
153 int Slrn_Use_Uudeview = 1;
154 #endif
155 
156 int Slrn_Del_Article_Upon_Read = 1;
157 
158 char *Slrn_Current_Group_Name;
159 Slrn_Header_Type *Slrn_First_Header;
160 Slrn_Header_Type *Slrn_Current_Header;
161 
162 Slrn_Header_Type *_art_Headers;
163 
164 /* range of articles on server for current group */
165 NNTP_Artnum_Type Slrn_Server_Min, Slrn_Server_Max;
166 
167 /* If +1, threads are all collapsed.  If zero, none are.  If -1, some may
168  * be and some may not.  In other words, if -1, this variable should not
169  * be trusted.
170  */
171 int _art_Threads_Collapsed = 0;
172 
173 /*}}}*/
174 /*{{{ static global variables */
175 static SLscroll_Window_Type Slrn_Article_Window;
176 static SLscroll_Window_Type Slrn_Header_Window;
177 
178 static int Header_Window_Nrows;
179 static unsigned int Number_Killed;
180 static unsigned int Number_Score_Killed;
181 static unsigned int Number_High_Scored;
182 static unsigned int Number_Low_Scored;
183 static unsigned int Number_Read;
184 static unsigned int Number_Total;
185 static int User_Aborted_Group_Read;
186 #if SLRN_HAS_SPOILERS
187 static Slrn_Header_Type *Spoilers_Visible;
188 static char Num_Spoilers_Visible = 1; /* show text up to Nth spoiler char */
189 #endif
190 
191 #if SLRN_HAS_GROUPLENS
192 static int Num_GroupLens_Rated = -1;
193 #endif
194 static Slrn_Header_Type *Mark_Header;  /* header with mark set */
195 
196 static Slrn_Group_Type *Current_Group; /* group being processed */
197 
198 static int Total_Num_Headers;	       /* headers retrieved from server.  This
199 					* number is used only by update meters */
200 static int Last_Cursor_Row;	       /* row where --> cursor last was */
201 static Slrn_Header_Type *Header_Showing;    /* header whose article is selected */
202 static Slrn_Header_Type *Last_Read_Header;
203 static int Article_Visible;	       /* non-zero if article window is visible */
204 static char Output_Filename[SLRN_MAX_PATH_LEN];
205 
206 #define HEADER_TABLE_SIZE 1250
207 static Slrn_Header_Type *Header_Table[HEADER_TABLE_SIZE];
208 static int Do_Rot13;
209 static int Perform_Scoring;
210 static int Largest_Header_Number;
211 static int Article_Window_Nrows;
212 static int Article_Window_HScroll;
213 static int Header_Window_HScroll;
214 
215 static Slrn_Header_Type *At_End_Of_Article;
216 /* If this variable is NULL, then we are not at the end of an article.  If it
217  * points at the current article, the we are at the end of that article.
218  * If it points anywhere else, ignore it.
219  */
220 
221 static int Headers_Hidden_Mode = 1;
222 int Slrn_Quotes_Hidden_Mode = 0;
223 int Slrn_Signature_Hidden = 0;
224 int Slrn_Pgp_Signature_Hidden = 0;
225 int Slrn_Verbatim_Marks_Hidden = 0;
226 int Slrn_Verbatim_Hidden = 0;
227 
228 /*}}}*/
229 /*{{{ static function declarations */
230 
231 static void toggle_header_formats (void);
232 static void slrn_art_hangup (int);
233 static void hide_or_unhide_quotes (void);
234 static void art_update_screen (void);
235 static void art_next_unread (void);
236 static void art_quit (void);
237 static int select_header (Slrn_Header_Type *, int);
238 static int select_article (int);
239 static void quick_help (void);
240 static void for_this_tree (Slrn_Header_Type *, void (*)(Slrn_Header_Type *));
241 static void find_non_hidden_header (void);
242 static void get_missing_headers (void);
243 static void decode_rot13 (unsigned char *);
244 
245 static void skip_to_next_group (void);
246 
247 #if SLRN_HAS_SPOILERS
248 static void show_spoilers (void);
249 #endif
250 /*}}}*/
251 
252 /*{{{ portability functions */
253 #ifndef HAVE_GETTIMEOFDAY
254 # ifdef VMS
255 #  define HAVE_GETTIMEOFDAY
256 #  include <starlet.h>
257 struct timeval { long tv_sec; long tv_usec;};
258 
gettimeofday(struct timeval * z,void * ignored)259 static int gettimeofday (struct timeval* z, void* ignored)
260 {
261    unsigned long tod[2];
262    SYS$GETTIM(tod);
263 
264    (void) ignored;
265    z->tv_sec=( (tod[0]/10000000) + ((tod[1]* 429 )&0x7fffffffl) );
266    z->tv_usec=((tod[0]/10)%1000000);
267    return 0;
268 }
269 # endif				       /* VMS */
270 # ifdef __WIN32__
271 #  define HAVE_GETTIMEOFDAY
272 #  ifndef WIN32_LEAN_AND_MEAN
273 #   define WIN32_LEAN_AND_MEAN
274 #  endif
275 #  include <windows.h>
276 struct timeval { long tick_count; };
277 
gettimeofday(struct timeval * timestruct,void * ignored)278 static int gettimeofday (struct timeval* timestruct , void* ignored)
279 {
280    (void) ignored;
281    timestruct->tick_count = GetTickCount();
282    return 0;
283 }
284 # endif				       /* __WIN32__ */
285 #endif
286 
287 #ifdef HAVE_GETTIMEOFDAY
288 # if defined (__WIN32__) && !defined(__MINGW32__)
289 /* Return time differences in microseconds */
time_diff(struct timeval t1,struct timeval t2)290 static unsigned long time_diff(struct timeval t1, struct timeval t2)
291 {
292    return (t1.tick_count - t2.tick_count)*1000;
293 }
294 # else /* Proper UNIX or VMS */
295 /* Return time differences in microseconds */
time_diff(struct timeval t1,struct timeval t2)296 static unsigned long time_diff(struct timeval t1, struct timeval t2)
297 {
298    return (t1.tv_sec - t2.tv_sec)*1000000 + (t1.tv_usec - t2.tv_usec);
299 }
300 # endif
301 #endif
302 /*}}}*/
303 
304 /*{{{ SIGWINCH and window resizing functions */
305 
306 /* This function syncs the current article */
art_winch(void)307 static void art_winch (void) /*{{{*/
308 {
309    static int rows = 0;
310 
311    /* Check to see if rows is still 0. If so, this is the first
312     * call here and we should run resize_screen_hook to allow it
313     * to change the initial Article_Window_Nrows.
314     */
315    if (rows == 0)
316      {
317 	rows = SLtt_Screen_Rows;
318 	slrn_run_hooks (HOOK_RESIZE_SCREEN, 0);
319      }
320 
321    if ((rows != SLtt_Screen_Rows)
322        || (Article_Window_Nrows <= 0)
323        || (Article_Window_Nrows >= SLtt_Screen_Rows - 3))
324      {
325 	rows = SLtt_Screen_Rows;
326 
327 	if (rows <= 28)
328 	  Article_Window_Nrows = rows - 8;
329 	else
330 	  Article_Window_Nrows = (3 * rows) / 4;
331      }
332 
333    Header_Window_Nrows = rows - 3;
334    if (Article_Visible)
335      {
336 	Header_Window_Nrows -= Article_Window_Nrows;
337 
338 	/* allow for art status line, unless we are zoomed. */
339 	if (Header_Window_Nrows != 0)
340 	  Header_Window_Nrows--;
341      }
342 
343    if (Header_Window_Nrows < 0) Header_Window_Nrows = 1;
344    if (Article_Window_Nrows < 0) Article_Window_Nrows = 1;
345 
346    Slrn_Article_Window.nrows = (unsigned int) Article_Window_Nrows;
347    Slrn_Header_Window.nrows = (unsigned int) Header_Window_Nrows;
348 
349    if (Slrn_Current_Article != NULL)
350      {
351 	if (Slrn_Current_Article->is_wrapped)
352 	  _slrn_art_wrap_article (Slrn_Current_Article);
353 
354 	if (Slrn_Current_Article->needs_sync)
355 	  slrn_art_sync_article (Slrn_Current_Article);
356      }
357 }
358 
359 /*}}}*/
360 
361 /* This function syncs the article */
set_article_visibility(int visible)362 static void set_article_visibility (int visible)
363 {
364    if (visible == Article_Visible)
365      return;
366    Article_Visible = visible;
367    art_winch ();
368 }
369 
art_winch_sig(int old_r,int old_c)370 static void art_winch_sig (int old_r, int old_c) /*{{{*/
371 {
372    (void) old_c;
373    if (old_r != SLtt_Screen_Rows)
374      Article_Window_Nrows = 0;
375 
376    art_winch ();
377 }
378 
379 /*}}}*/
380 
shrink_window(void)381 static void shrink_window (void) /*{{{*/
382 {
383    if (Article_Visible == 0) return;
384    Article_Window_Nrows++;
385    art_winch ();
386 }
387 
388 /*}}}*/
389 
enlarge_window(void)390 static void enlarge_window (void) /*{{{*/
391 {
392    if (Article_Visible == 0) return;
393    Article_Window_Nrows--;
394    art_winch ();
395 }
396 
397 /*}}}*/
398 
slrn_set_article_window_size(int nrows)399 void slrn_set_article_window_size (int nrows)
400 {
401    Article_Window_Nrows = nrows;
402    art_winch ();
403 }
404 
slrn_get_article_window_size(void)405 int slrn_get_article_window_size (void)
406 {
407    return Article_Window_Nrows;
408 }
409 
slrn_art_count_lines(void)410 unsigned int slrn_art_count_lines (void)
411 {
412    return Slrn_Article_Window.num_lines;
413 }
414 
slrn_art_cline_num(void)415 unsigned int slrn_art_cline_num (void)
416 {
417    return Slrn_Article_Window.line_num;
418 }
419 
420 /*}}}*/
421 /*{{{ header hash functions */
delete_hash_table(void)422 static void delete_hash_table (void) /*{{{*/
423 {
424    SLMEMSET ((char *) Header_Table, 0, sizeof (Header_Table));
425 }
426 
427 /*}}}*/
428 
make_hash_table(void)429 static void make_hash_table (void) /*{{{*/
430 {
431    Slrn_Header_Type *h;
432    delete_hash_table ();
433    h = Slrn_First_Header;
434    while (h != NULL)
435      {
436 	h->hash_next = Header_Table[h->hash % HEADER_TABLE_SIZE];
437 	Header_Table[h->hash % HEADER_TABLE_SIZE] = h;
438 	h = h->real_next;
439      }
440 }
441 
442 /*}}}*/
443 
remove_from_hash_table(Slrn_Header_Type * h)444 static void remove_from_hash_table (Slrn_Header_Type *h) /*{{{*/
445 {
446    Slrn_Header_Type *tmp;
447    if (h == (tmp = Header_Table[h->hash % HEADER_TABLE_SIZE]))
448      Header_Table[h->hash % HEADER_TABLE_SIZE] = h->hash_next;
449    else while (tmp != NULL)
450      {
451 	if (h == tmp->hash_next)
452 	  {
453 	     tmp->hash_next = h->hash_next;
454 	     break;
455 	  }
456 	tmp = tmp->hash_next;
457      }
458 }
459 /*}}}*/
460 
free_this_header(Slrn_Header_Type * h)461 static void free_this_header (Slrn_Header_Type *h)
462 {
463    slrn_free (h->tree_ptr);
464    slrn_free (h->subject);
465    slrn_free (h->from);
466    slrn_free (h->date);
467    slrn_free (h->msgid);
468    slrn_free (h->refs);
469    slrn_free (h->xref);
470    slrn_free (h->realname);
471    slrn_free_additional_headers (h->add_hdrs);
472    slrn_free ((char *) h);
473 }
474 
free_killed_header(Slrn_Header_Type * h)475 static void free_killed_header (Slrn_Header_Type *h)
476 {
477    Number_Total--;
478    if (h->flags & HEADER_READ)
479      Number_Read--;
480    if (h->flags & HEADER_HIGH_SCORE)
481      Number_High_Scored--;
482    if (h->flags & HEADER_LOW_SCORE)
483      Number_Low_Scored--;
484    remove_from_hash_table (h);
485    free_this_header (h);
486 }
487 
free_all_headers(void)488 static void free_all_headers (void)
489 {
490    Slrn_Header_Type *h = _art_Headers;
491 
492    while (h != NULL)
493      {
494 	Slrn_Header_Type *next = h->next;
495 	free_this_header (h);
496 	h = next;
497      }
498 
499    _art_Headers = NULL;
500 }
501 
502 /*}}}*/
503 
504 /*{{{ article line specific functions */
505 
slrn_art_sync_article(Slrn_Article_Type * a)506 void slrn_art_sync_article (Slrn_Article_Type *a)
507 {
508    Slrn_Article_Line_Type *l;
509 
510    if (a == NULL)
511      return;
512 
513    /* Make sure Article_Current_Line is not hidden */
514    l = a->cline;
515    while ((l != NULL) && (l->flags & HIDDEN_LINE))
516      l = l->prev;
517    if (l == NULL)
518      l = a->cline;
519    while ((l != NULL) && (l->flags & HIDDEN_LINE))
520      l = l->next;
521 
522    a->cline = l;
523 
524    Slrn_Article_Window.current_line = (SLscroll_Type *) l;
525 
526    /* Force current line to be at top of window */
527    Slrn_Article_Window.top_window_line = Slrn_Article_Window.current_line;
528 
529    SLscroll_find_line_num (&Slrn_Article_Window);
530    a->needs_sync = 0;
531 }
532 
find_article_line_num(void)533 static void find_article_line_num (void) /*{{{*/
534 {
535    if (Slrn_Current_Article == NULL)
536      return;
537 
538    slrn_art_sync_article (Slrn_Current_Article);
539 }
540 
541 /*}}}*/
542 
init_article_window_struct(void)543 static void init_article_window_struct (void) /*{{{*/
544 {
545    Slrn_Article_Type *a;
546 
547    if (Slrn_Current_Article == NULL)
548      return;
549 
550    a = Slrn_Current_Article;
551 
552    Slrn_Article_Window.hidden_mask = HIDDEN_LINE;
553    Slrn_Article_Window.current_line = (SLscroll_Type *) a->cline;
554    Slrn_Article_Window.cannot_scroll = SLtt_Term_Cannot_Scroll;
555    Slrn_Article_Window.lines = (SLscroll_Type *) a->lines;
556    Slrn_Article_Window.border = (unsigned int) Slrn_Article_Window_Border;
557    art_winch ();		       /* set nrows element */
558    find_article_line_num ();
559 }
560 
561 /*}}}*/
562 
slrn_art_free_line(Slrn_Article_Line_Type * l)563 void slrn_art_free_line (Slrn_Article_Line_Type *l)
564 {
565    slrn_free ((char *) l->buf);
566    slrn_free ((char *) l);
567 }
568 
slrn_art_free_article_line_list(Slrn_Article_Line_Type * lines)569 void slrn_art_free_article_line_list (Slrn_Article_Line_Type *lines)
570 {
571    while (lines != NULL)
572      {
573 	Slrn_Article_Line_Type *next = lines->next;
574 	slrn_art_free_line (lines);
575 	lines = next;
576      }
577 }
578 
copy_article_line(Slrn_Article_Line_Type * l)579 static Slrn_Article_Line_Type *copy_article_line (Slrn_Article_Line_Type *l)
580 {
581    Slrn_Article_Line_Type *retval = NULL, *r, *last = NULL;
582 
583    while (l != NULL)
584      {
585 	r = (Slrn_Article_Line_Type*) slrn_malloc (sizeof(Slrn_Article_Line_Type), 1, 1);
586 	if ((r == NULL) ||
587 	    (NULL == (r->buf = slrn_strmalloc (l->buf, 0))))
588 	  {
589 	     slrn_art_free_article_line_list (retval);
590 	     slrn_free ((char *)r);
591 	     return NULL;
592 	  }
593 	r->flags = l->flags;
594 	if (l->flags & QUOTE_LINE)
595 	  {
596 	     r->v.quote_level = l->v.quote_level;
597 	  }
598 
599 	r->next = NULL;
600 	if (retval == NULL)
601 	  {
602 	     r->prev = NULL;
603 	     retval = r;
604 	  }
605 	else
606 	  {
607 	     r->prev = last;
608 	     last->next = r;
609 	  }
610 	last = r;
611 	l = l->next;
612      }
613    return retval;
614 }
615 
free_article_lines(Slrn_Article_Type * a,int cooked)616 static void free_article_lines (Slrn_Article_Type *a, int cooked)
617 {
618    if (a == NULL)
619      return;
620 
621    if (cooked == 0)
622      {
623 	slrn_art_free_article_line_list (a->raw_lines);
624 	a->raw_lines = NULL;
625      }
626 
627    slrn_art_free_article_line_list (a->lines);
628    a->lines = NULL;
629    a->cline=NULL;
630 }
631 
slrn_art_free_article(Slrn_Article_Type * a)632 void slrn_art_free_article (Slrn_Article_Type *a)
633 {
634    if (a == NULL)
635      return;
636 
637    if (a == Slrn_Current_Article)
638      Slrn_Current_Article = NULL;
639 
640    slrn_mime_free(&a->mime);
641    free_article_lines (a, 0);
642    slrn_free ((char *) a);
643 }
644 
free_article(void)645 static void free_article (void) /*{{{*/
646 {
647    slrn_art_free_article (Slrn_Current_Article);
648    Slrn_Current_Article = NULL;
649 
650    memset ((char *) &Slrn_Article_Window, 0, sizeof(SLscroll_Window_Type));
651 
652    Header_Showing = NULL;
653    set_article_visibility (0);
654 }
655 
656 /*}}}*/
657 
skip_quoted_text(void)658 static void skip_quoted_text (void) /*{{{*/
659 {
660    _slrn_art_skip_quoted_text (Slrn_Current_Article);
661 }
662 
663 /*}}}*/
664 
skip_digest_forward(void)665 static void skip_digest_forward (void) /*{{{*/
666 {
667    if (-1 == _slrn_art_skip_digest_forward (Slrn_Current_Article))
668      slrn_error (_("No next digest."));
669 }
670 
671 /*}}}*/
672 
extract_header(Slrn_Header_Type * h,char * hdr,unsigned int len)673 static char *extract_header (Slrn_Header_Type *h, char *hdr, unsigned int len) /*{{{*/
674 {
675    char *retval;
676 
677    if (h == NULL)
678      return NULL;
679 
680    if ((len > 2) && (hdr[len-1] == ' ') && (hdr[len-2] == ':'))
681      len--;
682 
683    if (0 == slrn_case_strncmp ("From: ", hdr, len))
684      return slrn_skip_whitespace (h->from);
685    if (0 == slrn_case_strncmp ("Subject: ", hdr, len))
686      return h->subject;
687    if (0 == slrn_case_strncmp ("Message-Id: ", hdr, len))
688      return slrn_skip_whitespace (h->msgid);
689    if (0 == slrn_case_strncmp ("Date: ", hdr, len))
690      return h->date;
691    if (0 == slrn_case_strncmp ("References: ", hdr, len))
692      return slrn_skip_whitespace (h->refs);
693    if (0 == slrn_case_strncmp ("Xref: ", hdr, len))
694      return h->xref;
695    if (0 == slrn_case_strncmp ("Lines: ", hdr, len))
696      {
697 	static char lines_buf[32];
698 	sprintf (lines_buf, "%d", h->lines); /* safe */
699 	return lines_buf;
700      }
701 
702    retval = slrn_extract_add_header (h, hdr);
703    if ((retval == NULL) && (h == Header_Showing))
704      retval = slrn_art_extract_header (hdr, len);
705 
706    return retval;
707 }
708 /*}}}*/
709 
slrn_extract_header(char * hdr,unsigned int len)710 char *slrn_extract_header (char *hdr, unsigned int len) /*{{{*/
711 {
712    return extract_header (Header_Showing, hdr, len);
713 }
714 /*}}}*/
715 
slrn_cur_extract_header(char * hdr,unsigned int len)716 char *slrn_cur_extract_header (char *hdr, unsigned int len)
717 {
718    return extract_header (Slrn_Current_Header, hdr, len);
719 }
720 
721 /*{{{ wrap article functions  */
722 
unwrap_article(void)723 static void unwrap_article (void)
724 {
725    if (Header_Showing == NULL) return;
726    _slrn_art_unwrap_article (Slrn_Current_Article);
727    hide_or_unhide_quotes ();
728 }
729 
wrap_article(void)730 static void wrap_article (void) /*{{{*/
731 {
732    if (Header_Showing == NULL) return;
733    _slrn_art_wrap_article (Slrn_Current_Article);
734    hide_or_unhide_quotes ();
735 }
736 
737 /*}}}*/
738 
toggle_wrap_article(void)739 static void toggle_wrap_article (void)
740 {
741    Slrn_Article_Type *a = Slrn_Current_Article;
742 
743    if (a == NULL)
744      return;
745 
746    if (a->is_wrapped)
747      unwrap_article ();
748    else
749      {
750 	if (Slrn_Prefix_Arg_Ptr != NULL) Slrn_Wrap_Mode = 0x7F;
751 	Slrn_Prefix_Arg_Ptr = NULL;
752 	wrap_article ();
753      }
754 }
755 
756 /*}}}*/
757 
758 /* selects the article that should be affected by interactive commands */
select_affected_article(void)759 static int select_affected_article (void) /*{{{*/
760 {
761    if ((Slrn_Current_Article == NULL) || !Article_Visible)
762      {
763 	slrn_uncollapse_this_thread (Slrn_Current_Header, 1);
764 	if (select_header (Slrn_Current_Header, Slrn_Del_Article_Upon_Read) < 0)
765 	  return -1;
766 	else
767 	  return 0;
768      }
769    return 1;
770 }
771 /*}}}*/
772 
slrn_search_article(char * string,char ** ptrp,int is_regexp,int set_current_flag,int dir)773 Slrn_Article_Line_Type *slrn_search_article (char *string, /*{{{*/
774 					     char **ptrp,
775 					     int is_regexp,
776 					     int set_current_flag,
777 					     int dir)
778   /* dir: 0 is backward, 1 is forward, 2 is "find first" */
779 {
780 #if SLANG_VERSION < 20000
781    SLsearch_Type st;
782 #else
783    SLsearch_Type *st = NULL;
784 #endif
785    Slrn_Article_Line_Type *l;
786    Slrn_Article_Type *a;
787    char *ptr;
788    int ret;
789    SLRegexp_Type *re = NULL;
790 
791    if ((*string == 0) ||
792        (-1 == (ret = select_affected_article ())))
793      return NULL;
794 
795    if (is_regexp)
796      {
797 	re = slrn_compile_regexp_pattern (string);
798 	if (re == NULL)
799 	  return NULL;
800      }
801 #if SLANG_VERSION < 20000
802    else SLsearch_init (string, 1, 0, &st);
803 #else
804    else
805      {
806 	int flags = SLSEARCH_CASELESS;
807 	if (Slrn_UTF8_Mode)
808 	  flags |= SLSEARCH_UTF8;
809 
810 	st = SLsearch_new ((SLuchar_Type *) string, flags);
811 	if (st == NULL)
812 	  return NULL;
813      }
814 #endif
815 
816    a = Slrn_Current_Article;
817    if (dir == 2)
818      l = a->lines;
819    else
820      {
821 	l = a->cline;
822 	if (ret == 1)
823 	  l = (dir ? l->next : l->prev);
824      }
825 
826    while (l != NULL)
827      {
828 	if ((l->flags & HIDDEN_LINE) == 0)
829 	  {
830 	     if (is_regexp)
831 	       ptr = (char *) slrn_regexp_match (re, l->buf);
832 #if SLANG_VERSION < 20000
833 	     else ptr = (char *) SLsearch ((unsigned char *) l->buf,
834 					   (unsigned char *) l->buf + strlen (l->buf),
835 					   &st);
836 #else
837 	     else ptr = (char *)SLsearch_forward (st, (SLuchar_Type *) l->buf, (SLuchar_Type *) l->buf + strlen (l->buf));
838 #endif
839 
840 	     if (ptr != NULL)
841 	       {
842 		  if (ptrp != NULL) *ptrp = ptr;
843 		  if (set_current_flag)
844 		    {
845 		       set_article_visibility (1);
846 		       a->cline = l;
847 		       find_article_line_num ();
848 		    }
849 		  break;
850 	       }
851 	  }
852 	l = (dir ? l->next : l->prev);
853      }
854 
855 #if SLANG_VERSION >= 20000
856    if (re != NULL) SLregexp_free (re);
857    if (st != NULL) SLsearch_delete (st);
858 #endif
859    return l;
860 }
861 
862 /*}}}*/
863 
find_url(char * l_buf,unsigned int * p_len)864 static char *find_url (char *l_buf, unsigned int *p_len) /*{{{*/
865 {
866    char *ptr, *tmp, ch;
867 
868    while (NULL != (ptr = slrn_strbyte (l_buf, ':')))
869      {
870 	int is_news;
871 	tmp = ptr;
872 
873 	while ((ptr > l_buf)
874 	       && isalpha((unsigned char)(*(ptr - 1))))
875 	  ptr--;
876 
877 	/* all registered and reserved scheme names are >= 3 chars long */
878 	if ((ptr + 3 > tmp) ||
879 	    ((0 == (is_news = !strncmp (ptr, "news:",5))) &&
880 	     ((tmp[1] != '/') || (tmp[2] != '/'))))
881 	  {
882 	     l_buf = tmp + 1; /* skip : */
883 	     continue;
884 	  }
885 
886 	tmp = ptr;
887 
888 	if (is_news)
889 	  {
890 	     int saw_opening_bracket = 0;
891 
892 	     ptr+=5;
893 	     if (*ptr == '<')
894 	       {
895 		  ptr++;
896 		  saw_opening_bracket = 1;
897 	       }
898 	     while ((ch = *ptr) && (NULL == slrn_strbyte (" \t\n<>", ch)))
899 	       ptr++;
900 	     if ((*ptr == '>') && saw_opening_bracket)
901 	       ptr++;
902 	  }
903 	else
904 	  {
905 	     while ((ch = *ptr) && (NULL == slrn_strbyte (" \t\n\"{}<>", ch)))
906 	       ptr++;
907 	  }
908 
909 	/* at the end of the URL, these are probably punctuation */
910 	while ((tmp < ptr) && (NULL != slrn_strbyte (".,;:()", *(ptr - 1))))
911 	  ptr--;
912 
913 	l_buf = ptr;
914 
915 	if ((*p_len = (unsigned int) (ptr - tmp)) < 6)
916 	  continue;
917 
918 	ptr -= 3;
919 
920 	if ((ptr[0] == ':')
921 	    && (ptr[1] == '/')
922 	    && (ptr[2] == '/'))
923 	  continue;
924 
925 	return tmp;
926      }
927 
928    return NULL;
929 }
930 
931 /*}}}*/
932 
extract_urls(unsigned int * argc_ptr,char ** argv,unsigned int max_argc,unsigned int * start)933 static int extract_urls (unsigned int *argc_ptr, char **argv, unsigned int max_argc,
934 			 unsigned int *start) /*{{{*/
935 {
936    Slrn_Article_Line_Type *l;
937    Slrn_Article_Type *a;
938    unsigned int argc;
939    int was_wrapped;
940 
941    if (NULL == (a = Slrn_Current_Article))
942      return -1;
943 
944    if ((was_wrapped = (Slrn_Wrap_Method && a->is_wrapped)))
945      _slrn_art_unwrap_article (a);
946 
947    l = a->lines;
948    *start = 0;
949 
950    argc = 0;
951    while ((l != NULL) && (argc < max_argc))
952      {
953 	char *ptr;
954 	unsigned int len;
955 
956 	if (l == a->cline)
957 	  *start = argc;
958 
959 	ptr = l->buf;
960 	while (NULL != (ptr = find_url (ptr, &len)))
961 	  {
962 	     unsigned int iterator;
963 
964 	     if (argc == max_argc)
965 	       break;
966 
967 	     if (NULL == (argv[argc] = slrn_strnmalloc (ptr, len, 1)))
968 	       {
969 		  slrn_free_argc_argv_list (argc, argv);
970 		  return -1;
971 	       }
972 	     if (Do_Rot13)
973 	       decode_rot13 ((unsigned char *) argv[argc]);
974 	     ptr += len;
975 
976 	     /* remove duplicates */
977 	     for (iterator = 0; iterator < argc; ++iterator)
978 	       {
979 		  if (!strcmp (argv[iterator], argv[argc]))
980 		    {
981 		       slrn_free(argv[argc]);
982 		       --argc;
983 		       break;
984 		    }
985 	       }
986 	     argc++;
987 	  }
988 
989 	l = l->next;
990      }
991 
992    if (was_wrapped)
993      _slrn_art_wrap_article (a);
994 
995    *argc_ptr = argc;
996    return 0;
997 }
998 
999 /*}}}*/
1000 
quote_url(char * url)1001 static char *quote_url (char *url)
1002 {
1003    char *p;
1004    char *new_url;
1005    unsigned int len;
1006 
1007    p = url;
1008    len = 0;
1009    while (*p != 0)
1010      {
1011 	if (isalnum (*p))
1012 	  len++;
1013 	else
1014 	  len += 3;
1015 	p++;
1016      }
1017 
1018    new_url = SLmalloc (len + 1);
1019    if (new_url == NULL)
1020      return NULL;
1021    p = new_url;
1022 
1023    while (*url != 0)
1024      {
1025 	switch (*url)
1026 	  {
1027 	   default:
1028 	     *p++ = *url;
1029 	     break;
1030 
1031 	     /* RFC 2396 suggests that the following may be escaped without
1032 	      * changing the semantics of the URL.
1033 	      */
1034 	   /* case '-': */
1035 	   /* case '_': */
1036 	   /* case '.': */
1037 	   case '!':
1038 	   case '~':
1039 	   case '*':
1040 	   case '\'':
1041 	   case '(':
1042 	   case ')':
1043 	     sprintf (p, "%%%2X", (unsigned char) *url); /* safe */
1044 	     p += 3;
1045 	     break;
1046 	  }
1047 	url++;
1048      }
1049 
1050    *p = 0;
1051    return new_url;
1052 }
1053 
create_browser_command(char * cmd,char * url)1054 static char *create_browser_command (char *cmd, char *url) /*{{{*/
1055 {
1056    unsigned int len, urllen;
1057    char ch, *buf, *bp, *p = cmd;
1058 
1059    len = strlen (cmd) + 1;
1060    urllen = strlen (url);
1061    while (0 != (ch = *p++))
1062      {
1063 	if (ch == '%')
1064 	  {
1065 	     ch = *p++;
1066 	     if (ch == 's')
1067 	       len += urllen - 1;
1068 	     else if (ch != '%')
1069 	       {
1070 		  slrn_error (_("Invalid Browser definition."));
1071 		  return NULL;
1072 	       }
1073 	  }
1074      }
1075    buf = bp = slrn_safe_malloc (len);
1076    p = cmd;
1077    while (0 != (ch = *p++))
1078      {
1079 	if (ch == '%')
1080 	  {
1081 	     ch = *p++;
1082 	     if (ch == 's')
1083 	       {
1084 		  strcpy (bp, url); /* safe */
1085 		  bp += urllen;
1086 	       }
1087 	     else
1088 	       *bp++ = '%';
1089 	  }
1090 	else
1091 	  *bp++ = ch;
1092      }
1093    return buf;
1094 }
1095 /*}}}*/
1096 
1097 /* If want_edit is 1, url can be changed by user. */
launch_url(char * url,int want_edit)1098 static void launch_url (char *url, int want_edit) /*{{{*/
1099 {
1100    char *command, *browser, *has_percent;
1101    int reinit;
1102 
1103    if (want_edit
1104        && (slrn_read_input (_("Browse (^G aborts): "), NULL, url, 0, -1) <= 0))
1105      {
1106 	slrn_error (_("Aborted."));
1107 	return;
1108      }
1109 
1110    if (!strncmp (url, "news:", 5)) /* we can handle this ourself */
1111      {
1112 	char bracket = '>';
1113 	if (url[strlen(url)-1] == '>')
1114 	  bracket = 0;
1115 	url += 5;
1116 	if (!strncmp (url, "//", 2))
1117 	  url += 2; /* not RFC compliant, but accept it anyway */
1118 	if (*url == '<')
1119 	  url++; /* not RFC compliant either */
1120 	if (NULL != slrn_strbyte (url, '@'))
1121 	  {
1122 	     char *msgid = slrn_strdup_printf ("<%s%c", url, bracket);
1123 	     if (NULL == msgid)
1124 	       return;
1125 	     slrn_locate_header_by_msgid (msgid, 0, 1);
1126 	     slrn_free (msgid);
1127 	     return;
1128 	  }
1129 	if (!strcmp (url, "*")) /* special case */
1130 	  {
1131 	     if ((Slrn_User_Wants_Confirmation & SLRN_CONFIRM_URL) &&
1132 		 (slrn_get_yesno (1, _("Show all available groups")) == 0))
1133 	       return;
1134 
1135 	     art_quit ();
1136 	     slrn_hide_groups (0);
1137 	     slrn_list_all_groups (1);
1138 	     return;
1139 	  }
1140 	/* else: it's a group name */
1141 	if ((Slrn_User_Wants_Confirmation & SLRN_CONFIRM_URL) &&
1142 	    (slrn_get_yesno (1, _("Try switching to %s"), url) == 0))
1143 	  return;
1144 
1145 	art_quit ();
1146 	if (0 == slrn_add_group (url))
1147 	  if (-1 == slrn_group_select_group ())
1148 	    slrn_error (_("Group contains no articles."));
1149 	return;
1150      }
1151 
1152    reinit = 0;
1153    if ((NULL == getenv ("DISPLAY"))
1154        || (NULL == (browser = slrn_skip_whitespace (Slrn_X_Browser)))
1155        || (browser [0] == 0))
1156      {
1157 	reinit = 1;
1158 	browser = Slrn_NonX_Browser;
1159      }
1160 
1161    if (browser == NULL)
1162      {
1163 	slrn_error (_("No Web Browser has been defined."));
1164 	return;
1165      }
1166 
1167    if (reinit == 0)		       /* ==> X_Browser != NULL */
1168      {
1169 	/* Non_X and X browsers may be same. */
1170 	if ((Slrn_NonX_Browser != NULL)
1171 	    && (0 == strcmp (Slrn_NonX_Browser, Slrn_X_Browser)))
1172 	  reinit = 1;
1173      }
1174 
1175    /* Perform a simple-minded syntax check. */
1176    has_percent = slrn_strbyte (browser, '%');
1177    if (has_percent != NULL)
1178      {
1179 	if ((has_percent[1] != 's')
1180 	    || ((has_percent != browser) && (*(has_percent - 1) == '\\')))
1181 	  has_percent = NULL;
1182      }
1183 
1184    if (NULL == (url = quote_url (url)))
1185      return;
1186 
1187    if (has_percent != NULL)
1188      command = create_browser_command (browser, url);
1189    else
1190      /* Is this quoting ok on VMS and OS/2?? */
1191      command = slrn_strdup_printf ("%s '%s'", browser, url);
1192 
1193    if (command != NULL)
1194      (void) slrn_posix_system (command, reinit);
1195 
1196    SLfree (command);
1197    SLfree (url);
1198 }
1199 
browse_url(void)1200 static void browse_url (void) /*{{{*/
1201 {
1202    char url[SLRL_DISPLAY_BUFFER_SIZE];
1203    int selected;
1204    unsigned int argc, start_argc;
1205 #define MAX_URLS 1024
1206    char *argv[MAX_URLS];
1207    int want_edit;
1208 
1209    if (-1 == extract_urls (&argc, argv, MAX_URLS, &start_argc))
1210      return;
1211 
1212    if (0 == argc)
1213      {
1214 	slrn_error (_("No URLs found."));
1215 	return;
1216      }
1217 
1218    selected = 0;
1219    want_edit = 1;
1220 
1221    if (argc > 1)
1222      selected = slrn_select_list_mode ("URL", argc, argv, start_argc, 1, &want_edit);
1223 
1224    if (-1 == selected)
1225      {
1226 	slrn_free_argc_argv_list (argc, argv);
1227 	return;
1228      }
1229 
1230    strncpy (url, argv[selected], sizeof (url));
1231    url[sizeof(url) - 1] = 0;
1232 
1233    slrn_free_argc_argv_list (argc, argv);
1234 
1235    slrn_redraw ();
1236    launch_url (url, want_edit);
1237 }
1238 
1239 /*}}}*/
1240 
article_search(void)1241 static void article_search (void) /*{{{*/
1242 {
1243    static char search_str[SLRL_DISPLAY_BUFFER_SIZE];
1244    Slrn_Article_Line_Type *l;
1245 
1246    if (slrn_read_input (_("Search: "), search_str, NULL, 0, 0) <= 0) return;
1247 
1248    l = slrn_search_article (search_str, NULL, 0, 1, 1);
1249 
1250    if (l == NULL) slrn_error (_("Not found."));
1251 }
1252 
1253 /*}}}*/
1254 
1255 /*}}}*/
1256 /*{{{ current article movement functions */
1257 
slrn_art_lineup_n(unsigned int n)1258 unsigned int slrn_art_lineup_n (unsigned int n) /*{{{*/
1259 {
1260    if (select_article (0) <= 0) return 0;
1261 
1262    n = SLscroll_prev_n (&Slrn_Article_Window, n);
1263 
1264    Slrn_Current_Article->cline = (Slrn_Article_Line_Type *) Slrn_Article_Window.current_line;
1265 
1266    /* Force current line to be at top of window */
1267    Slrn_Article_Window.top_window_line = Slrn_Article_Window.current_line;
1268    Slrn_Full_Screen_Update = 1;
1269    return n;
1270 }
1271 
1272 /*}}}*/
1273 
art_pageup(void)1274 static void art_pageup (void) /*{{{*/
1275 {
1276    /* Since we always require the current line to be at the top of the
1277     * window, SLscroll_pageup cannot be used.  Instead, do it this way:
1278     */
1279    (void) slrn_art_lineup_n (Slrn_Article_Window.nrows - 1);
1280 }
1281 
1282 /*}}}*/
1283 
slrn_get_next_pagedn_action(void)1284 int slrn_get_next_pagedn_action (void)
1285 {
1286    Slrn_Header_Type *h;
1287 
1288    if (Slrn_Current_Header == NULL)
1289      return -1;
1290 
1291    if ((Article_Visible == 0)
1292        || (At_End_Of_Article != Slrn_Current_Header))
1293      return 0;
1294 
1295    h = Slrn_Current_Header->next;
1296    while (h != NULL)
1297      {
1298 	if (0 == (h->flags & HEADER_READ))
1299 	  return 1;
1300 	h = h->next;
1301      }
1302 
1303    return 2;
1304 }
1305 
art_pagedn(void)1306 static void art_pagedn (void) /*{{{*/
1307 {
1308    char *msg = NULL;
1309 
1310    if (Slrn_Current_Header == NULL) return;
1311 
1312 #if SLRN_HAS_SPOILERS
1313    if (Spoilers_Visible == Slrn_Current_Header)
1314      {
1315 	show_spoilers ();
1316 	return;
1317      }
1318 #endif
1319 
1320    if ((Article_Visible == 0) || (At_End_Of_Article != Slrn_Current_Header))
1321      {
1322 	int av = Article_Visible;
1323 
1324 	At_End_Of_Article = NULL;
1325 
1326 	if ((select_article (0) <= 0)
1327 	    || (av == 0))
1328 	  return;
1329 
1330 	SLscroll_pagedown (&Slrn_Article_Window);
1331 	Slrn_Current_Article->cline = (Slrn_Article_Line_Type *) Slrn_Article_Window.current_line;
1332 	/* Force current line to be at top of window */
1333 #if 1
1334 	Slrn_Article_Window.top_window_line = Slrn_Article_Window.current_line;
1335 #endif
1336 	return;
1337      }
1338 
1339    At_End_Of_Article = NULL;
1340 
1341    if (Slrn_Batch) return;
1342 
1343    if (Slrn_Current_Header->next == NULL)
1344      {
1345 	if (Slrn_Query_Next_Group)
1346 	  msg = _("At end of article, press %s for next group.");
1347      }
1348    else if (Slrn_Query_Next_Article)
1349      msg = _("At end of article, press %s for next unread article.");
1350 
1351    if (msg != NULL)
1352      {
1353 	char *keyseq = slrn_help_keyseq_from_function ("article_page_down", Slrn_Article_Keymap);
1354 	if (keyseq != NULL)
1355 	  {
1356 	     SLang_Key_Type *key;
1357 	     char *keystr = slrn_help_keyseq_to_string(keyseq+1,*keyseq-1);
1358 	     if (keystr == NULL)
1359 	       keystr = SLang_make_keystring((unsigned char*) keyseq);
1360 
1361 	     slrn_message_now (msg, keystr);
1362 	     key = SLang_do_key (Slrn_Article_Keymap, slrn_getkey);
1363 	     if (key == NULL) return;
1364 	     if ((key->type != SLKEY_F_INTRINSIC) ||
1365 		 (key->f.f != (FVOID_STAR) art_pagedn))
1366 	       {
1367 		  SLang_ungetkey_string (key->str+1, (unsigned int)*(key->str)-1);
1368 		  return;
1369 	       }
1370 	  }
1371 	else /* article_page_down is unbound */
1372 	  {
1373 	     unsigned int ch;
1374 
1375 	     slrn_message_now (msg, _("<Space>"));
1376 #if 0
1377 	     if (SLANG_GETKEY_ERROR == (ch = SLang_getkey ()))
1378 	       slrn_exit_error ("SLang_getkey failed");
1379 #else
1380 	     ch = slrn_getkey ();
1381 #endif
1382 	     if (ch != ' ')
1383 	       {
1384 		  SLang_ungetkey ((unsigned char) ch);
1385 		  return;
1386 	       }
1387 	  }
1388      }
1389 
1390    At_End_Of_Article = NULL;
1391    if (Slrn_Current_Header->next != NULL) art_next_unread ();
1392    else skip_to_next_group ();
1393 }
1394 
1395 /*}}}*/
1396 
art_lineup(void)1397 static void art_lineup (void) /*{{{*/
1398 {
1399    slrn_art_lineup_n (1);
1400 }
1401 
1402 /*}}}*/
1403 
art_bob(void)1404 static void art_bob (void) /*{{{*/
1405 {
1406    while (0xFFFF == slrn_art_lineup_n (0xFFFF));
1407 }
1408 
1409 /*}}}*/
1410 
slrn_art_linedn_n(unsigned int n)1411 unsigned int slrn_art_linedn_n (unsigned int n) /*{{{*/
1412 {
1413    unsigned int new_article = 0;
1414    switch (select_article (0))
1415      {
1416       case 0:
1417 	new_article = 1;
1418 	if (n-- > 1)
1419 	  break; /* else fall through */
1420       case -1:
1421 	return new_article;
1422      }
1423 
1424    n = SLscroll_next_n (&Slrn_Article_Window, n);
1425    Slrn_Current_Article->cline = (Slrn_Article_Line_Type *) Slrn_Article_Window.current_line;
1426 
1427    /* Force current line to be at top of window */
1428    Slrn_Article_Window.top_window_line = Slrn_Article_Window.current_line;
1429    Slrn_Full_Screen_Update = 1;
1430    return n + new_article;
1431 }
1432 
1433 /*}}}*/
1434 
art_linedn(void)1435 static void art_linedn (void) /*{{{*/
1436 {
1437    (void) slrn_art_linedn_n (1);
1438 }
1439 
1440 /*}}}*/
1441 
art_eob(void)1442 static void art_eob (void) /*{{{*/
1443 {
1444    while (slrn_art_linedn_n (0xFFFF) > 0)
1445      ;
1446    (void) slrn_art_lineup_n (Slrn_Article_Window.nrows - 1);
1447 }
1448 
1449 /*}}}*/
1450 
1451 /*}}}*/
1452 
1453 /*{{{ Tag functions */
1454 
1455 typedef struct /*{{{*/
1456 {
1457    Slrn_Header_Type **headers;
1458    unsigned int max_len;
1459    unsigned int len;
1460 }
1461 
1462 /*}}}*/
1463 Num_Tag_Type;
1464 
1465 static Num_Tag_Type Num_Tag_List;
1466 
free_tag_list(void)1467 static void free_tag_list (void) /*{{{*/
1468 {
1469    if (Num_Tag_List.headers != NULL)
1470      {
1471 	SLFREE (Num_Tag_List.headers);
1472 	Num_Tag_List.headers = NULL;
1473 	Num_Tag_List.len = Num_Tag_List.max_len = 0;
1474      }
1475 }
1476 
1477 /*}}}*/
1478 
slrn_goto_num_tagged_header(int * nump)1479 int slrn_goto_num_tagged_header (int *nump) /*{{{*/
1480 {
1481    unsigned int num;
1482 
1483    num = (unsigned int) *nump;
1484    num--;
1485 
1486    if (num >= Num_Tag_List.len)
1487      return 0;
1488 
1489    if (Num_Tag_List.headers == NULL)
1490      return 0;
1491 
1492    if (-1 == slrn_goto_header (Num_Tag_List.headers[num], 0))
1493      return 0;
1494 
1495    Slrn_Full_Screen_Update = 1;
1496    return 1;
1497 }
1498 
1499 /*}}}*/
1500 
num_tag_header(void)1501 static void num_tag_header (void) /*{{{*/
1502 {
1503    Slrn_Header_Type *h = Slrn_Current_Header;
1504    unsigned int len, tag_thread = 0;
1505 
1506    if ((h->child != NULL)
1507        && (h->child->flags & HEADER_HIDDEN))
1508      tag_thread = 1;
1509 
1510    if (Num_Tag_List.headers == NULL)
1511      {
1512 	Slrn_Header_Type **headers;
1513 	unsigned int max_len = 20;
1514 
1515 	headers = (Slrn_Header_Type **) slrn_malloc (max_len * sizeof (Slrn_Header_Type),
1516 						     0, 1);
1517 	if (headers == NULL)
1518 	  return;
1519 
1520 	Num_Tag_List.max_len = max_len;
1521 	Num_Tag_List.headers = headers;
1522 	Num_Tag_List.len = 0;
1523      }
1524 
1525    do
1526      {
1527 	if (Num_Tag_List.max_len == Num_Tag_List.len)
1528 	  {
1529 	     Slrn_Header_Type **headers = Num_Tag_List.headers;
1530 	     unsigned int max_len = Num_Tag_List.max_len + 20;
1531 
1532 	     headers = (Slrn_Header_Type **) slrn_realloc ((char *)headers,
1533 							   max_len * sizeof (Slrn_Header_Type),
1534 							   1);
1535 	     if (headers == NULL)
1536 	       return;
1537 
1538 	     Num_Tag_List.max_len = max_len;
1539 	     Num_Tag_List.headers = headers;
1540 	  }
1541 
1542 	Slrn_Full_Screen_Update = 1;
1543 	if ((h->flags & HEADER_NTAGGED) == 0)
1544 	  {
1545 	     Num_Tag_List.headers[Num_Tag_List.len] = h;
1546 	     Num_Tag_List.len += 1;
1547 	     h->tag_number = Num_Tag_List.len;
1548 	     h->flags |= HEADER_NTAGGED;
1549 	     continue;
1550 	  }
1551 
1552 	/* It is already tagged.  Giving this header the last number lead to
1553 	 * the slightly annoying fact that you had to hit "tag" twice to untag,
1554 	 * unless you were on the last tag.  We now simply remove it and
1555 	 * renumber the others that follow it.
1556 	 */
1557 	for (len = h->tag_number + 1; len <= Num_Tag_List.len; len++)
1558 	  {
1559 	     Slrn_Header_Type *tmp = Num_Tag_List.headers[len - 1];
1560 	     Num_Tag_List.headers[len - 2] = tmp;
1561 	     tmp->tag_number -= 1;
1562 	  }
1563 	Num_Tag_List.len--;
1564 	h->tag_number = 0;
1565 	h->flags &= ~HEADER_NTAGGED;
1566      }
1567    while ((tag_thread) && (NULL != (h = h->next)) && (h->parent != NULL));
1568    (void) slrn_header_down_n (1, 0);
1569 }
1570 
1571 /*}}}*/
1572 
num_untag_headers(void)1573 static void num_untag_headers (void) /*{{{*/
1574 {
1575    unsigned int len;
1576    for (len = 1; len <= Num_Tag_List.len; len++)
1577      {
1578 	Slrn_Header_Type *h = Num_Tag_List.headers[len - 1];
1579 	h->flags &= ~HEADER_NTAGGED;
1580 	h->tag_number = 0;
1581      }
1582    Num_Tag_List.len = 0;
1583    Slrn_Full_Screen_Update = 1;
1584 }
1585 
1586 /*}}}*/
1587 
toggle_one_header_tag(Slrn_Header_Type * h)1588 static void toggle_one_header_tag (Slrn_Header_Type *h) /*{{{*/
1589 {
1590    if (h == NULL) return;
1591    if (h->flags & HEADER_TAGGED)
1592      {
1593 	h->flags &= ~HEADER_TAGGED;
1594      }
1595    else
1596      {
1597 	h->flags |= HEADER_TAGGED;
1598 	if (h->flags & HEADER_READ)
1599 	  {
1600 	     h->flags &= ~HEADER_READ;
1601 	     Number_Read--;
1602 	  }
1603      }
1604 }
1605 
1606 /*}}}*/
1607 
toggle_header_tag(void)1608 static void toggle_header_tag (void) /*{{{*/
1609 {
1610    if (Slrn_Prefix_Arg_Ptr != NULL)
1611      {
1612 	Slrn_Header_Type *h;
1613 
1614 	Slrn_Prefix_Arg_Ptr = NULL;
1615 	h = _art_Headers;
1616 	while (h != NULL)
1617 	  {
1618 	     h->flags &= ~HEADER_TAGGED;
1619 	     h = h->next;
1620 	  }
1621 	Slrn_Full_Screen_Update = 1;
1622 	return;
1623      }
1624 
1625    if ((Slrn_Current_Header->parent != NULL)/* in middle of thread */
1626        || (Slrn_Current_Header->child == NULL)/* At top with no child */
1627        /* or at top with child showing */
1628        || (0 == (Slrn_Current_Header->child->flags & HEADER_HIDDEN)))
1629      {
1630 	toggle_one_header_tag (Slrn_Current_Header);
1631      }
1632    else
1633      {
1634 	for_this_tree (Slrn_Current_Header, toggle_one_header_tag);
1635      }
1636    (void) slrn_header_down_n (1, 0);
1637    Slrn_Full_Screen_Update = 1;
1638 }
1639 
1640 /*}}}*/
1641 
slrn_prev_tagged_header(void)1642 int slrn_prev_tagged_header (void) /*{{{*/
1643 {
1644    Slrn_Header_Type *h = Slrn_Current_Header;
1645 
1646    if (h == NULL) return 0;
1647 
1648    while (h->prev != NULL)
1649      {
1650 	h = h->prev;
1651 	if (h->flags & HEADER_TAGGED)
1652 	  {
1653 	     slrn_goto_header (h, 0);
1654 	     return 1;
1655 	  }
1656      }
1657    return 0;
1658 }
1659 
1660 /*}}}*/
1661 
slrn_next_tagged_header(void)1662 int slrn_next_tagged_header (void) /*{{{*/
1663 {
1664    Slrn_Header_Type *h = Slrn_Current_Header;
1665 
1666    if (h == NULL) return 0;
1667 
1668    while (h->next != NULL)
1669      {
1670 	h = h->next;
1671 	if (h->flags & HEADER_TAGGED)
1672 	  {
1673 	     slrn_goto_header (h, 0);
1674 	     return 1;
1675 	  }
1676      }
1677    return 0;
1678 }
1679 
1680 /*}}}*/
1681 
1682 /*}}}*/
1683 /*{{{ Header specific functions */
1684 
_art_find_header_line_num(void)1685 void _art_find_header_line_num (void) /*{{{*/
1686 {
1687    Slrn_Full_Screen_Update = 1;
1688    find_non_hidden_header ();
1689    Slrn_Header_Window.lines = (SLscroll_Type *) _art_Headers;
1690    Slrn_Header_Window.current_line = (SLscroll_Type *) Slrn_Current_Header;
1691    SLscroll_find_line_num (&Slrn_Header_Window);
1692 }
1693 
1694 /*}}}*/
1695 
init_header_window_struct(void)1696 static void init_header_window_struct (void) /*{{{*/
1697 {
1698    Slrn_Header_Window.nrows = 0;
1699    Slrn_Header_Window.hidden_mask = HEADER_HIDDEN;
1700    Slrn_Header_Window.current_line = (SLscroll_Type *) Slrn_Current_Header;
1701 
1702    Slrn_Header_Window.cannot_scroll = SLtt_Term_Cannot_Scroll;
1703    Slrn_Header_Window.border = 1;
1704 
1705    if (Slrn_Scroll_By_Page)
1706      {
1707 	/* Slrn_Header_Window.border = 0; */
1708 	Slrn_Header_Window.cannot_scroll = 2;
1709      }
1710 
1711    Slrn_Header_Window.lines = (SLscroll_Type *) _art_Headers;
1712    art_winch ();		       /* get row information correct */
1713 
1714    _art_find_header_line_num ();
1715 }
1716 
1717 /*}}}*/
1718 
find_header_from_serverid(NNTP_Artnum_Type id)1719 static Slrn_Header_Type *find_header_from_serverid (NNTP_Artnum_Type id) /*{{{*/
1720 {
1721    Slrn_Header_Type *h;
1722 
1723    h = Slrn_First_Header;
1724    while (h != NULL)
1725      {
1726 	if (h->number > id) return NULL;
1727 	if (h->number == id) break;
1728 	h = h->real_next;
1729      }
1730    return h;
1731 }
1732 
1733 /*}}}*/
1734 
kill_cross_references(Slrn_Header_Type * h)1735 static void kill_cross_references (Slrn_Header_Type *h) /*{{{*/
1736 {
1737    char *b;
1738    char *group, *g;
1739    NNTP_Artnum_Type num;
1740 
1741    if ((h->xref == NULL) || (*h->xref == 0))
1742      {
1743 	if ((Header_Showing != h)
1744 	    || (NULL == (b = slrn_art_extract_header ("Xref: ", 6))))
1745 	  {
1746 	     return;
1747 	  }
1748      }
1749    else b = h->xref;
1750 
1751    /* The format appears to be:
1752     * Xref: machine group:num group:num...
1753     */
1754 
1755    /* skip machine name */
1756    while (*b > ' ') b++;
1757 
1758    while (*b != 0)
1759      {
1760 	while (*b == ' ') b++;
1761 	if (*b == 0) break;
1762 
1763 	/* now we are looking at the groupname */
1764 	g = b;
1765 	while (*b && (*b != ':')) b++;
1766 	if ((g == b) || (b[0] == 0) || (b[1] == 0)
1767 	    || (NULL == (group = slrn_strnmalloc (g, (unsigned int)(b-g), 0))))
1768 	  break;
1769 	b++;			       /* skip ':' */
1770 	num = NNTP_STR_TO_ARTNUM (b);
1771 	while ((*b <= '9') && (*b >= '0')) b++;
1772 	if ((num != h->number)
1773 	    || strcmp (group, Slrn_Current_Group_Name))
1774 	  slrn_mark_articles_as_read (group, num, num);
1775 	SLfree (group);
1776      }
1777 }
1778 
1779 /*}}}*/
1780 
for_all_headers(void (* func)(Slrn_Header_Type *),int all)1781 static void for_all_headers (void (*func)(Slrn_Header_Type *), int all) /*{{{*/
1782 {
1783    Slrn_Header_Type *h, *end;
1784 
1785    Slrn_Full_Screen_Update = 1;
1786 
1787    if (func == NULL) return;
1788 
1789    if (all) end = NULL; else end = Slrn_Current_Header;
1790 
1791    h = _art_Headers;
1792 
1793    while (h != end)
1794      {
1795 	(*func)(h);
1796 	h = h->next;
1797      }
1798 }
1799 
1800 /*}}}*/
1801 
1802 /* When using this, make sure *header points to a valid header. */
slrn_goto_header(Slrn_Header_Type * header,int read_flag)1803 int slrn_goto_header (Slrn_Header_Type *header, int read_flag) /*{{{*/
1804 {
1805    Slrn_Current_Header = header;
1806    if (header->flags & HEADER_HIDDEN)
1807      slrn_uncollapse_this_thread (header, 0);
1808    _art_find_header_line_num ();
1809 
1810    if (read_flag) select_article (1);
1811    return 0;
1812 }
1813 
1814 /*}}}*/
1815 
1816 /*{{{ parse_from  */
read_comment(char * start,char * dest,size_t max)1817 static char *read_comment (char *start, char *dest, size_t max) /*{{{*/
1818 {
1819    int depth = 1; /* RFC 2822 allows nesting */
1820    unsigned int len = 1;
1821 
1822    if ((*start != '(') || (*(++start) == ')'))
1823      depth = 0;
1824    while (*start && depth)
1825      {
1826 	if (*start == '(') depth++;
1827 	else if (*start == ')') depth--;
1828 	else if ((*start == '\\') && (*(++start) == '\0'))
1829 	  break;
1830 	if (depth && (len < max))
1831 	  {
1832 	     *dest++ = *start; len++;
1833 	  }
1834 	start++;
1835      }
1836    if (len < max) *dest = '\0';
1837    if (*start == ')') start++;
1838    while ((*start == ' ') || (*start == '\t')) start++;
1839 
1840    return start;
1841 }
1842 /*}}}*/
1843 
1844 /* Copy a quoted string from start to dest.
1845  * Assumptions: start is the beginning of a quoted string
1846  *              dest can hold at least max characters
1847  * Returns: Length of the quoted string; if it is > max, it has not been
1848  *          fully copied to dest
1849  */
read_quotedstring(char * start,char * dest,size_t max)1850 static size_t read_quotedstring (char *start, char *dest, size_t max) /*{{{*/
1851 {
1852    size_t len = 0;
1853 
1854    if (len < max)
1855       *dest++ = '"';
1856    len++;
1857    start++;
1858 
1859    while (*start && (*start != '"'))
1860      {
1861 	unsigned int backslash = 0;
1862 	if (*start == '\\')
1863 	  {
1864 	     backslash = 1;
1865 	     if (*(++start) == '\0')
1866 		break;
1867 	  }
1868 	if (len + backslash < max)
1869 	  {
1870 	     if (backslash) *dest++ = '\\';
1871 	     *dest++ = *start;
1872 	  }
1873 	len += 1 + backslash;
1874 	start++;
1875      }
1876    if (*start == '"')
1877      {
1878 	if (len < max)
1879 	   *dest++ = '"';
1880 	len++;
1881 	start++;
1882      }
1883    return len;
1884 }
1885 /*}}}*/
1886 
1887 /* Copy a dot-atom from start to dest (which can hold at least max chars).
1888  * Returns: Length of the dot-atom; if it is > max, it has not been fully
1889  * copied to dest
1890  */
read_dotatom(char * start,char * dest,size_t max)1891 static size_t read_dotatom (char *start, char *dest, size_t max) /*{{{*/
1892 {
1893    size_t len = 0;
1894 
1895    if (len < max)
1896       *dest++ = *start;
1897    len++;
1898    start++;
1899 
1900    while (1)
1901      {
1902 	unsigned int dot = 0;
1903 	if (*start == '.')
1904 	  {
1905 	     dot = 1;
1906 	     start++;
1907 	  }
1908 	if ((*start == '\0') ||
1909 	    (NULL != slrn_strbyte ("\t \"(),.:;<>@[\\]", *start)))
1910 	   break;
1911 	if (len + dot < max)
1912 	  {
1913 	     if (dot) *dest++ = '.';
1914 	     *dest++ = *start;
1915 	  }
1916 	len += 1 + dot;
1917 	start++;
1918      }
1919    return len;
1920 }
1921 /*}}}*/
1922 
1923 /* Copy the localpart of an email address from start to dest.
1924  * The result will be NUL-terminated.
1925  * Returns a pointer to the char behind the localpart (or NULL, if nothing
1926  * is left behind the localpart).
1927  */
read_localpart(char * start,char * dest,size_t max)1928 static char *read_localpart (char *start, char *dest, size_t max) /*{{{*/
1929 {
1930    size_t len = 0; /* Can become larger than max! */
1931 
1932    if (max) *dest = '\0';
1933 
1934    if (*start == 0)
1935      return NULL;
1936 
1937    while ((*start == ' ') || (*start == '\t')) start++;
1938    while (*start == '(')
1939      {
1940 	if (NULL == (start = read_comment (start, NULL, 0)))
1941 	   return NULL;
1942      }
1943 
1944    if (*start == '"')
1945      {
1946 	len = read_quotedstring (start, dest, max ? max-1 : 0);
1947      }
1948    else if (*start && (NULL == slrn_strbyte ("\t \"(),.:;<>@[\\]", *start)))
1949      {
1950 	len = read_dotatom (start, dest, max ? max-1 : 0);
1951      }
1952    else
1953      {
1954 	if (max) *dest = '\0';
1955 	len = 1;
1956      }
1957    start += len;
1958    if (max)
1959      {
1960 	if (len <= max-1)
1961 	   dest[len] = '\0';
1962 	else
1963 	   dest[max-1] = '\0';
1964      }
1965 
1966    while ((*start == ' ') || (*start == '\t')) start++;
1967    while ((start != NULL) && (*start == '('))
1968      {
1969 	start = read_comment (start, NULL, 0);
1970      }
1971 
1972    if ((start != NULL) && (*start == '\0'))
1973       return NULL;
1974    return start;
1975 }
1976 /*}}}*/
1977 
1978 /* Copy the domain part of an email address from start to dest.  The result
1979  * will be NUL-terminated (which means that max is expected to be at least 1).
1980  * Returns a pointer to the char behind the domain (in start) or NULL if no
1981  * valid domain name is found or it did not fit.
1982  */
read_domain(char * start,char * dest,size_t max)1983 static char *read_domain (char *start, char *dest, size_t max) /*{{{*/
1984 {
1985    size_t len = 0; /* Can become larger than max! */
1986 
1987    *dest = '\0';
1988    while ((*start == ' ') || (*start == '\t')) start++;
1989    while (*start == '(')
1990      {
1991 	if (NULL == (start = read_comment (start, NULL, 0)))
1992 	   return NULL;
1993      }
1994 
1995    if (*start == '[') /* read domain literal */
1996      {
1997 	while ((*start == ' ') || (*start == '\t')) start++;
1998 	while (*start && (*start != ']'))
1999 	  {
2000 	     size_t dotatom_len = read_dotatom (start, dest, max-len-1);
2001 	     if (dotatom_len >= max-len)
2002 	       {
2003 		  dest[max-1] = '\0';
2004 		  if (start[max-2] == '.')
2005 		     dest[max-2] = '\0';
2006 		  return NULL;
2007 	       }
2008 	     start += dotatom_len;
2009 	     dest += dotatom_len;
2010 	     len += dotatom_len;
2011 
2012 	     while ((*start == ' ') || (*start == '\t'))
2013 	       {
2014 		  if (len >= max-1)
2015 		    {
2016 		       dest[max-1] = '\0';
2017 		       return NULL;
2018 		    }
2019 		  *dest++ = *start++;
2020 		  len++;
2021 	       }
2022 	  }
2023 	if ((*start != ']') || (len >= max-1))
2024 	  {
2025 	     dest[max-1] = '\0';
2026 	     return NULL;
2027 	  }
2028 	*dest++ = ']';
2029 	len++;
2030 	start++;
2031      }
2032    else if (*start && (NULL == slrn_strbyte ("\t \"(),.:;<>@\\]", *start)))
2033      {
2034 	len = read_dotatom (start, dest, max);
2035 	start += len;
2036 	dest += len;
2037      }
2038    else
2039       return NULL;
2040 
2041    if (len>=max)
2042       return NULL;
2043    *dest = '\0';
2044 
2045    return start;
2046 }
2047 /*}}}*/
2048 
2049 /* Parse an address line and try to copy the first email address to dest.
2050  * dest must be able to hold at least max characters
2051  * max must be at least 1, result is always NUL-terminated
2052  * Returns pointer to the first character behind the address in from or
2053  * NULL if no (valid) address could be found (or it did not fit). */
parse_from(char * from,char * dest,size_t max)2054 static char *parse_from (char *from, char *dest, size_t max) /*{{{*/
2055 {
2056    unsigned int len;
2057    char *p;
2058 
2059    /* First, try to find an address in <>;
2060     * else, assume simple form (read from beginning) */
2061 
2062    if (from == NULL) return NULL;
2063    *dest = '\0';
2064    from = slrn_skip_whitespace (from);
2065    p = from;
2066 
2067    /* The allowed syntax before the address in <> is similar to
2068     * the one of the local part. */
2069    while ((p != NULL) && (*p != '<'))
2070      {
2071 	p = read_localpart (p, NULL, 0);
2072      }
2073 
2074    if (p != NULL) from = p + 1;
2075 
2076    if (NULL == (p = read_localpart (from, dest, max)))
2077       return NULL;
2078    len = strlen (dest);
2079    if ((*p != '@') || (len >= max - 3) || (len == 0))
2080      return NULL;
2081 
2082    dest[len] = '@';
2083    return read_domain (p + 1, dest + len + 1, max - len - 1);
2084 }
2085 
2086 /*}}}*/
2087 
2088 /*}}}*/
2089 
2090 /*}}}*/
2091 
2092 /*{{{ get_header_real_name */
2093 
unquote_string(char * str)2094 static void unquote_string (char *str)
2095 {
2096    int offset = 1;
2097    if (*str == '"')
2098      {
2099 	if (str[1] == '\\')
2100 	  offset = 2;
2101 	while (str[offset] &&
2102 	       ((str[offset]!='"') || str[offset+1]))
2103 	  {
2104 	     str[0] = str[offset];
2105 	     str++;
2106 	     if (str[offset] == '\\')
2107 	       {
2108 		  offset++;
2109 	       }
2110 	  }
2111 	*str = '\0';
2112      }
2113 }
2114 
get_header_real_name(Slrn_Header_Type * h)2115 static void get_header_real_name (Slrn_Header_Type *h) /*{{{*/
2116 {
2117    char buf[128];
2118    char *from = h->from, *f, *p;
2119    /* First, try to find "display name <address>";
2120     * else, skip the address and look for a comment */
2121 
2122    f = from = slrn_skip_whitespace (from);
2123    p = buf;
2124    *buf = '\0';
2125 
2126    while ((f != NULL) && (*f != '<'))
2127      {
2128 	f = read_localpart (f, p, (unsigned int) (sizeof(buf) - (p-buf)));
2129 	unquote_string(p); /* the name may have been quoted */
2130 	p += strlen (p);
2131 	if ((f != NULL) && (p + 1 < buf + sizeof (buf)))
2132 	  {
2133 	     *p++ = ' '; *p = '\0';
2134 	  }
2135      }
2136 
2137    if (f == NULL)
2138      {
2139 	char dummy[256];
2140 
2141 	*buf = '\0';
2142         /* This will give us a pointer behind the address */
2143 	f = parse_from (from, dummy, sizeof(dummy));
2144 	if (f != NULL)
2145 	  {
2146 	     while (*f && ((*f == ' ') || (*f == '\t'))) f++;
2147 	     if (*f == '(')
2148 	       read_comment (f, buf, sizeof (buf));
2149 	  }
2150      }
2151 
2152    if (*buf == '\0')
2153      {
2154 	slrn_strncpy (buf, from, sizeof (buf));
2155      }
2156 
2157    if (*buf != '\0')
2158      {
2159 	f = buf + strlen (buf) - 1;
2160 	while ((f != buf) && ((*f == ' ') || (*f == '\t')))
2161 	  {
2162 	     *f-- = '\0';
2163 	  }
2164      }
2165    if (h->realname != NULL)
2166      slrn_free (h->realname);
2167 
2168    h->realname = slrn_strmalloc (buf, 0);
2169 }
2170 
2171 /*}}}*/
2172 
2173 /*}}}*/
2174 
_art_find_header_from_msgid(char * r0,char * r1)2175 Slrn_Header_Type *_art_find_header_from_msgid (char *r0, char *r1) /*{{{*/
2176 {
2177    unsigned long hash;
2178    Slrn_Header_Type *h;
2179    unsigned int len;
2180    len = (unsigned int) (r1 - r0);
2181    hash = slrn_compute_hash ((unsigned char *) r0, (unsigned char *) r1);
2182 
2183    h = Header_Table[hash % HEADER_TABLE_SIZE];
2184    while (h != NULL)
2185      {
2186 	if (!slrn_case_strncmp ( h->msgid,
2187 				 r0,
2188 				len))
2189 	  break;
2190 
2191 	h = h->hash_next;
2192      }
2193    return h;
2194 }
2195 
2196 /*}}}*/
2197 
slrn_find_header_with_msgid(char * msgid)2198 Slrn_Header_Type *slrn_find_header_with_msgid (char *msgid) /*{{{*/
2199 {
2200    return _art_find_header_from_msgid (msgid, msgid + strlen (msgid));
2201 }
2202 
2203 /*}}}*/
2204 
goto_article(void)2205 static void goto_article (void) /*{{{*/
2206 {
2207    Slrn_Header_Type *h;
2208    NNTP_Artnum_Type want_n;
2209 
2210    if (-1 == slrn_read_artnum_int (_("Goto article: "), NULL, &want_n))
2211      return;
2212 
2213    h = _art_Headers;
2214    while (h != NULL)
2215      {
2216 	if (h->number == want_n)
2217 	  {
2218 	     Slrn_Current_Header = h;
2219 	     if (h->flags & HEADER_HIDDEN) slrn_uncollapse_this_thread (h, 0);
2220 	     _art_find_header_line_num ();
2221 	     return;
2222 	  }
2223 
2224 	h = h->next;
2225      }
2226 
2227    slrn_error (_("Article not found."));
2228 }
2229 
2230 /*}}}*/
2231 
slrn_is_article_visible(void)2232 int slrn_is_article_visible (void)
2233 {
2234    int mask = 0;
2235 
2236    if (Slrn_Current_Header != NULL)
2237      {
2238 	if (Slrn_Current_Header == Header_Showing)
2239 	  mask |= 2;
2240 	if (Article_Visible)
2241 	  mask |= 1;
2242      }
2243    return mask;
2244 }
2245 
2246 /*}}}*/
2247 
prepare_article(Slrn_Article_Type * a)2248 static int prepare_article (Slrn_Article_Type *a)
2249 {
2250    if (a == NULL)
2251      return -1;
2252 
2253    slrn_art_mark_quotes (a);
2254 
2255 #if SLRN_HAS_SPOILERS
2256    slrn_art_mark_spoilers (a);
2257    Num_Spoilers_Visible = 1;
2258 #endif
2259 
2260    /* We should ignore signature delimiters in verbatim parts, so
2261     * order matters. */
2262    if (Slrn_Process_Verbatim_Marks)
2263      slrn_art_mark_verbatim (a);
2264 
2265    /* mark_signature unmarks lines in the signature which look like
2266     * quotes, so do it after mark_quotes, but before hide_or_unhide_quotes */
2267    slrn_art_mark_signature (a);
2268    slrn_art_mark_pgp_signature (a);
2269 
2270    /* The actual wrapping is done elsewhere. */
2271    if (Slrn_Wrap_Mode & 0x4)
2272      a->is_wrapped = 1;
2273    if (Headers_Hidden_Mode)
2274      _slrn_art_hide_headers (a);
2275    if (Slrn_Quotes_Hidden_Mode)
2276      _slrn_art_hide_quotes (a, 0);
2277    if (Slrn_Signature_Hidden)
2278      _slrn_art_hide_signature (a);
2279    if (Slrn_Pgp_Signature_Hidden)
2280      _slrn_art_hide_pgp_signature (a);
2281    if (Slrn_Verbatim_Hidden)
2282      _slrn_art_hide_verbatim (a);
2283    return 0;
2284 }
2285 
2286 /* Downloads an article and returns it; header lines are marked */
read_article(Slrn_Header_Type * h,int kill_refs)2287 static Slrn_Article_Type *read_article (Slrn_Header_Type *h, int kill_refs) /*{{{*/
2288 {
2289    Slrn_Article_Type *retval;
2290    Slrn_Article_Line_Type *cline, *l;
2291    int status, num_lines_update;
2292    unsigned int len, total_lines;
2293    char buf[NNTP_BUFFER_SIZE];
2294 #ifdef HAVE_GETTIMEOFDAY
2295    double current_bps;
2296    struct timeval start_time, new_time;
2297    gettimeofday(&start_time, NULL);
2298 #endif
2299 
2300    if (h->tag_number) slrn_message_now (_("#%2d/%-2d: Retrieving... %s"),
2301 					h->tag_number, Num_Tag_List.len,
2302 					h->subject);
2303    else slrn_message_now (_("[" NNTP_FMT_ARTNUM "] Reading..."), h->number);
2304 
2305    status = Slrn_Server_Obj->sv_select_article (h->number, h->msgid);
2306    if (status != OK_ARTICLE)
2307      {
2308 	if (status == -1)
2309 	  {
2310 	     slrn_error ("%s", _("Server failed to return the article."));
2311 	     return NULL;
2312 	  }
2313 
2314 	slrn_error (_("Article " NNTP_FMT_ARTNUM " is unavailable."), h->number);
2315 
2316 	if (kill_refs && ((h->flags & HEADER_READ) == 0) &&
2317 	    ((h->flags & HEADER_DONT_DELETE_MASK) == 0))
2318 	  {
2319 	     kill_cross_references (h);
2320 	     h->flags |= HEADER_READ;
2321 	     Number_Read++;
2322 	  }
2323 	return NULL;
2324      }
2325 
2326    if ((num_lines_update = Slrn_Reads_Per_Update) < 5)
2327      {
2328 	if (h->lines < 200)
2329 	  num_lines_update = 20;
2330 	else
2331 	  num_lines_update = 50;
2332      }
2333 
2334    retval = (Slrn_Article_Type*) slrn_malloc (sizeof(Slrn_Article_Type), 1, 1);
2335    if (retval == NULL) return NULL;
2336 
2337    cline = NULL;
2338    total_lines = 0;
2339 
2340    /* Reset byte counter */
2341    (void) Slrn_Server_Obj->sv_nntp_bytes(1);
2342 
2343    while (1)
2344      {
2345 	status = Slrn_Server_Obj->sv_read_line(buf, sizeof(buf));
2346 	if (status == 0)
2347 	  break;
2348 
2349 	if ((status == -1) || (SLang_get_error() == USER_BREAK))
2350 	  {
2351 	     if (Slrn_Server_Obj->sv_reset != NULL)
2352 	       Slrn_Server_Obj->sv_reset ();
2353 	     slrn_error ("%s", _("Article transfer aborted or connection lost."));
2354 	     break;
2355 	  }
2356 
2357 	total_lines++;
2358 	if ((1 == (total_lines % num_lines_update))
2359 	    /* Just so the ratio does not confuse the reader because of the
2360 	     * header lines... */
2361 	    && (total_lines < (unsigned int) h->lines))
2362 	  {
2363 #ifdef HAVE_GETTIMEOFDAY
2364 	     gettimeofday(&new_time, NULL);
2365 	     current_bps = time_diff(new_time, start_time)/1000000.0;
2366 	     if (current_bps > 0)
2367 	       current_bps = (Slrn_Server_Obj->sv_nntp_bytes(0) / 1024.0)/current_bps;
2368 #endif
2369 	     if (h->tag_number)
2370 #ifdef HAVE_GETTIMEOFDAY
2371 	       slrn_message_now (_("#%2d/%-2d: Read %4d/%-4d lines (%s) at %.2fkB/sec"),
2372 				 h->tag_number, Num_Tag_List.len, total_lines,
2373 				 h->lines, h->subject, current_bps);
2374 #else
2375 	       slrn_message_now (_("#%2d/%-2d: Read %4d/%-4d lines (%s)"),
2376 				 h->tag_number, Num_Tag_List.len,
2377 				 total_lines, h->lines, h->subject);
2378 #endif
2379 	     else
2380 #ifdef HAVE_GETTIMEOFDAY
2381 	       slrn_message_now (_("[" NNTP_FMT_ARTNUM "] Read %d/%d lines so far at %.2fkB/sec"),
2382 				 h->number, total_lines, h->lines, current_bps);
2383 #else
2384 	       slrn_message_now (_("[" NNTP_FMT_ARTNUM"] Read %d/%d lines so far"),
2385 				 h->number, total_lines, h->lines);
2386 #endif
2387 	  }
2388 
2389 	len = strlen (buf);
2390 
2391 	l = (Slrn_Article_Line_Type *) slrn_malloc (sizeof (Slrn_Article_Line_Type),
2392 						    1, 1);
2393 	if ((l == NULL)
2394 	    || (NULL == (l->buf = slrn_malloc (len + 1, 0, 1))))
2395 	  {
2396 	     slrn_free ((char *) l);
2397 	     slrn_art_free_article (retval);
2398 	     return NULL;
2399 	  }
2400 
2401 	/* Note: I no longer remove _^H combinations, as it corrupts yenc-
2402 	 * encoded data and seems unnecessary in today's usenet.
2403 	 *
2404 	 * The processsing of the leading doubled dot took place here.
2405 	 * However, that is now handled at the protocol layer.
2406 	 */
2407 #if 0
2408 	if ((*buf == '.') && (*(buf + 1) == '.'))
2409 	  strcpy (l->buf, buf + 1); /* safe */
2410 	else
2411 #endif
2412 	  strcpy (l->buf, buf); /* safe */
2413 
2414 	l->next = l->prev = NULL;
2415 	l->flags = 0;
2416 
2417 	if (retval->lines == NULL)
2418 	  retval->lines = l;
2419 	else
2420 	  {
2421 	     l->prev = cline;
2422 	     cline->next = l;
2423 	  }
2424 	cline = l;
2425      }
2426 
2427    if (retval->lines == NULL)
2428      {
2429 	slrn_error (_("Server sent empty article."));
2430 	slrn_art_free_article (retval);
2431 	return NULL;
2432      }
2433 
2434    retval->raw_lines = copy_article_line (retval->lines);
2435    if (retval->raw_lines == NULL)
2436      {
2437 	slrn_art_free_article (retval);
2438 	return NULL;
2439      }
2440    retval->cline = retval->lines;
2441    retval->needs_sync = 1;
2442 
2443    if (kill_refs && ((h->flags & HEADER_READ) == 0) &&
2444        ((h->flags & HEADER_DONT_DELETE_MASK) == 0))
2445      {
2446 	kill_cross_references (h);
2447 	h->flags |= HEADER_READ;
2448 	Number_Read++;
2449      }
2450 
2451    slrn_mark_header_lines (retval);
2452    slrn_mime_init (&retval->mime);
2453 
2454    return retval;
2455 }
2456 /*}}}*/
2457 
2458 /* On errors, free a and return -1 */
2459 #if 0
2460 static int art_undo_modifications (Slrn_Article_Type *a)
2461 {
2462    unsigned int linenum = Slrn_Article_Window.line_num;
2463    if (a == NULL) return 0;
2464 
2465    a->is_modified = 0;
2466    a->is_wrapped = 0;
2467    slrn_mime_free (&a->mime);
2468    slrn_mime_init (&a->mime);
2469 
2470    slrn_art_free_article_line (a->lines);
2471    a->lines = NULL;
2472    if (NULL == (a->lines = copy_article_line (a->raw_lines)))
2473      {
2474 	slrn_art_free_article (a);
2475 	return -1;
2476      }
2477 
2478    a->cline = a->lines;
2479    slrn_mark_header_lines (a);
2480    if (-1 == _slrn_art_unfold_header_lines (a))
2481      {
2482 	slrn_art_free_article (a);
2483 	return -1;
2484      }
2485    prepare_article (a);
2486    init_article_window_struct();
2487    slrn_art_linedn_n (linenum-1); /* find initial line */
2488 
2489    return 0;
2490 }
2491 #endif
2492 
select_header(Slrn_Header_Type * h,int kill_refs)2493 static int select_header (Slrn_Header_Type *h, int kill_refs) /*{{{*/
2494 {
2495    Slrn_Article_Type *a;
2496    Slrn_Header_Type *last_header_showing;
2497    char *subj, *from;
2498 
2499    last_header_showing = Header_Showing;
2500 
2501    if ((Header_Showing == h)
2502        && (Slrn_Current_Article != NULL))
2503      { /* We already have the article in memory. */
2504 	return 0;
2505      }
2506 
2507    free_article ();
2508    if (NULL == (a = read_article (h, kill_refs)))
2509      return -1;
2510 
2511    At_End_Of_Article = NULL;
2512    Do_Rot13 = 0;
2513    Article_Window_HScroll = 0;
2514    Slrn_Full_Screen_Update = 1;
2515 
2516    if (-1 == _slrn_art_unfold_header_lines (a))
2517      {
2518 	slrn_art_free_article (a);
2519 	return -1;
2520      }
2521 
2522    if ( -1 == slrn_mime_process_article (a))
2523      {
2524 	slrn_art_free_article (a);
2525 	slrn_error(_("MIME processing unsuccessful"));
2526 	return -1;
2527      }
2528 
2529    /* Only now may the article be said to be the current article */
2530    Slrn_Current_Article = a;
2531 
2532    /* RFC 2980 says not to unfold headers when writing them to the overview.
2533     * Work around this by taking Subject and From out of the article. */
2534    subj = slrn_art_extract_header ("Subject: ", 9);
2535    from = slrn_art_extract_header ("From: ", 6);
2536 
2537    if ((NULL != subj) && (NULL != from) &&
2538        (strcmp (subj, h->subject) || strcmp (from, h->from)))
2539      {
2540 	subj = slrn_strmalloc (subj, 1);
2541 	if (subj == NULL)
2542 	  return -1;
2543 	slrn_free (h->subject);
2544 	h->subject = subj;
2545 
2546 	from = slrn_strmalloc (from, 1);
2547 	if (from == NULL)
2548 	  return -1;
2549 	slrn_free (h->from);
2550 	h->from = from;
2551 	get_header_real_name (h);
2552      }
2553 
2554    if (last_header_showing != h)
2555      {
2556 	Last_Read_Header = last_header_showing;
2557      }
2558    Header_Showing = h;
2559 
2560    if (h == Slrn_Current_Header)
2561      {
2562 	(void) prepare_article (a);
2563 	if (Last_Read_Header == NULL)
2564 	  Last_Read_Header = h;
2565      }
2566 
2567    init_article_window_struct ();
2568 
2569    (void) slrn_run_hooks (HOOK_READ_ARTICLE, 0);
2570 
2571    /* slrn_set_suspension (0); */
2572    return 0;
2573 }
2574 
2575 /*}}}*/
2576 
slrn_string_to_article(char * str,int handle_mime,int cooked)2577 int slrn_string_to_article (char *str, int handle_mime, int cooked)
2578 {
2579    char *estr;
2580    Slrn_Article_Line_Type *l, *cline = NULL;
2581    Slrn_Article_Type *a;
2582 
2583    if ((str == NULL) || (*str == 0))
2584      return -1;
2585 
2586    if (NULL == (a = Slrn_Current_Article))
2587      return -1;
2588 
2589    free_article_lines (a, cooked);
2590 
2591    a->is_modified = 1;
2592    a->needs_sync = 1;
2593 
2594    while (1)
2595      {
2596 	unsigned int len;
2597 
2598 	estr = slrn_strbyte (str, '\n');
2599 	if (estr == NULL)
2600 	  estr = str + strlen (str);
2601 
2602 	len = (unsigned int) (estr - str);
2603 
2604 	if ((0 == len) && (0 == *estr))
2605 	  break;
2606 
2607 	l = (Slrn_Article_Line_Type *) slrn_malloc (sizeof(Slrn_Article_Line_Type),
2608 						    1, 1);
2609 	if ((l == NULL)
2610 	    || (NULL == (l->buf = slrn_malloc (len + 1, 0, 1))))
2611 	  {
2612 	     slrn_free ((char *) l);
2613 	     free_article ();
2614 	     return -1;
2615 	  }
2616 	strncpy (l->buf, str, len);
2617 	l->buf[len] = 0;
2618 
2619 	l->next = l->prev = NULL;
2620 	l->flags = 0;
2621 
2622 	if (a->lines == NULL)
2623 	  a->lines = l;
2624 	else
2625 	  {
2626 	     l->prev = cline;
2627 	     cline->next = l;
2628 	  }
2629 	cline = l;
2630 
2631 	str = estr;
2632 	if (*str == 0)
2633 	  break;
2634 	else str++;		       /* skip \n */
2635      }
2636 
2637 #if 0 /* does this make any sense? */
2638    Header_Showing = Slrn_Current_Header;
2639 #endif
2640    if ((cooked == 0)
2641        && (NULL == (a->raw_lines = copy_article_line (a->lines))))
2642      {
2643 	free_article ();
2644 	return -1;
2645      }
2646    a->cline = a->lines;
2647 
2648    slrn_mark_header_lines (a);
2649 
2650    if (-1 == _slrn_art_unfold_header_lines (a))
2651      {
2652 	free_article ();
2653 	return -1;
2654      }
2655 
2656    if (handle_mime)
2657      {
2658 	slrn_mime_free (&a->mime);
2659 	slrn_mime_init (&a->mime);
2660 
2661 	if (-1 == slrn_mime_process_article (a))
2662 	  {
2663 	     slrn_art_free_article (a);
2664 	     slrn_error(_("MIME processing unsuccessful"));
2665 	     return -1;
2666 	  }
2667      }
2668 
2669    prepare_article (a);
2670 
2671    /* This must be called before any of the other functions are called because
2672     * they may depend upon the line number and window information.
2673     */
2674    init_article_window_struct ();
2675    set_article_visibility (1);
2676    return 0;
2677 }
2678 
2679 /*{{{ reply, reply_cmd, forward, followup */
2680 
insert_followup_format(char * f,FILE * fp)2681 static int insert_followup_format (char *f, FILE *fp) /*{{{*/
2682 {
2683    char ch, *s, *m, *f_conv=NULL;
2684    unsigned int i;
2685    char buf[256];
2686 
2687    if ((f == NULL) || (*f == 0))
2688      return -1;
2689 
2690    if (Header_Showing == NULL)
2691      return -1;
2692 
2693    if (slrn_test_and_convert_string(f, &f_conv, Slrn_Editor_Charset, Slrn_Display_Charset) == -1)
2694 	return -1;
2695 
2696    if (f_conv != NULL)
2697 	f=f_conv;
2698 
2699    while ((ch = *f++) != 0)
2700      {
2701 	if (ch != '%')
2702 	  {
2703 	     putc (ch, fp); /* charset ok*/
2704 	     continue;
2705 	  }
2706 	s = m = NULL;
2707 	ch = *f++;
2708 	if (ch == 0) break;
2709 
2710 	switch (ch)
2711 	  {
2712 	   case 's':
2713 	     s = Header_Showing->subject;
2714 	     break;
2715 	   case 'm':
2716 	     s = Header_Showing->msgid;
2717 	     break;
2718 	   case 'r':
2719 	     s = Header_Showing->realname;
2720 	     break;
2721 	   case 'R':
2722 	     for (i=0; i <= strlen (Header_Showing->realname); i++)
2723 	       {
2724 		  if (Header_Showing->realname[i] == ' ')
2725 		       break;
2726 	       }
2727 	     m = slrn_strnmalloc (Header_Showing->realname,i,1);
2728 	     break;
2729 	   case 'f':
2730 	     (void) parse_from (Header_Showing->from, buf, sizeof(buf));
2731 	     s = buf;
2732 	     break;
2733 	   case 'n':
2734 	     s = Slrn_Current_Group_Name;
2735 	     break;
2736 	   case 'd':
2737 	     s = Header_Showing->date;
2738 	     break;
2739 	   case 'D':
2740 	       {
2741 		  char *fmtstr;
2742 
2743 		  fmtstr = Slrn_Followup_Date_Format;
2744 		  if (fmtstr == NULL)
2745 		    fmtstr = _("%Y-%m-%d");
2746 
2747 		  slrn_strftime(buf, sizeof(buf), fmtstr,
2748 				Header_Showing->date,
2749 				Slrn_Use_Localtime & 0x02);
2750 		  s = buf;
2751 	       }
2752 	     break;
2753 	   case '%':
2754 	   default:
2755 	     putc (ch, fp); /* charset ok*/
2756 	  }
2757 
2758 	if (m != NULL)
2759 	  {
2760 	     if (slrn_convert_fprintf(fp, Slrn_Editor_Charset, Slrn_Display_Charset, "%s", m) < 0)
2761 	       {
2762 		  slrn_free(m);
2763 		  slrn_free(f_conv);
2764 		  return -1;
2765 	       }
2766 	     slrn_free(m);
2767 	  }
2768 	if (s != NULL)
2769 	  {
2770 	     if (slrn_convert_fprintf(fp, Slrn_Editor_Charset, Slrn_Display_Charset, "%s", s) < 0)
2771 	       {
2772 		  slrn_free(f_conv);
2773 		  return -1;
2774 	       }
2775 	  }
2776      } /* while ((ch = *f++) != 0) */
2777 
2778    slrn_free(f_conv);
2779 
2780    return 0;
2781 }
2782 
2783 /*}}}*/
2784 
extract_reply_address(void)2785 static char *extract_reply_address (void)
2786 {
2787    char *from;
2788 
2789    if ((NULL != (from = slrn_extract_header ("Mail-Reply-To: ", 15)))
2790        && (*from != 0))
2791      return from;
2792 
2793    if ((NULL != (from = slrn_extract_header ("Reply-To: ", 10)))
2794        && (*from != 0))
2795      return from;
2796 
2797    return slrn_extract_header ("From: ", 6);
2798 }
2799 
2800 /* This function is called after an article hook has been called to make
2801  * sure the user did not delete the article.
2802  */
check_for_current_article(void)2803 static int check_for_current_article (void)
2804 {
2805    if (SLang_get_error ())
2806      return -1;
2807    if (Slrn_Current_Article == NULL)
2808      {
2809 	slrn_error (_("This operation requires an article"));
2810 	return -1;
2811      }
2812    return 0;
2813 }
2814 
run_article_hook(unsigned int hook)2815 static int run_article_hook (unsigned int hook)
2816 {
2817    slrn_run_hooks (hook, 0);
2818    return check_for_current_article ();
2819 }
2820 
2821 /* This function strips the old subject included with "(was: <old sub>)" */
slrn_subject_strip_was(char * subject)2822 void slrn_subject_strip_was (char *subject) /*{{{*/
2823 {
2824    SLRegexp_Type **r;
2825    unsigned char *was = NULL;
2826    unsigned int len = strlen (subject);
2827 
2828    r = Slrn_Strip_Was_Regexp;
2829 
2830    while (*r != NULL)
2831      {
2832 	SLRegexp_Type *re;
2833 	re = *r++;
2834 	was = (unsigned char *)SLregexp_match (re, subject, len);
2835 	if (NULL != was)
2836 	  {
2837 	     if (was == (unsigned char*) subject)
2838 	       was = NULL;
2839 	     else break;
2840 	  }
2841      }
2842 
2843    if (was != NULL)
2844      *was = '\0';
2845 }
2846 
2847 /*}}}*/
2848 
subject_skip_re(char * subject)2849 static char *subject_skip_re (char *subject) /*{{{*/
2850 {
2851    SLRegexp_Type **r;
2852    unsigned int len;
2853 
2854    while (1)
2855      {
2856 	subject = slrn_skip_whitespace (subject);
2857 
2858 	if (((*subject | 0x20) == 'r')
2859 	    && ((*(subject + 1) | 0x20) == 'e')
2860 	    && (*(subject + 2) == ':'))
2861 	  {
2862 	     subject = subject + 3;
2863 	     continue;
2864 	  }
2865 
2866 	r = Slrn_Strip_Re_Regexp;
2867 	len = strlen (subject);
2868 
2869 	while (*r != NULL)
2870 	  {
2871 	     SLRegexp_Type *re = *r;
2872 	     char *match_pos;
2873 
2874 #if SLANG_VERSION < 20000
2875 	     match_pos = SLang_regexp_match ((unsigned char*) subject, len, re);
2876 #else
2877 	     match_pos = SLregexp_match (re, subject, len);
2878 #endif
2879 	     if (subject == match_pos)
2880 	       {
2881 #if SLANG_VERSION < 20000
2882 		  subject = subject + re->end_matches[0];
2883 #else
2884 		  unsigned int ofs, mlen;
2885 		  (void) SLregexp_nth_match (re, 0, &ofs, &mlen);
2886 		  subject += mlen;
2887 #endif
2888 		  break;
2889 	       }
2890 	     r++;
2891 	  }
2892 	if (*r == NULL)
2893 	  break;
2894      }
2895 
2896    return subject;
2897 }
2898 
2899 /*}}}*/
2900 
2901 /* If from != NULL, it's taken as the address to send the reply to, otherwise
2902  * the reply address is taken from Reply-To: or From: */
reply(char * from,int use_cc)2903 static void reply (char *from, int use_cc) /*{{{*/
2904 {
2905    char *msgid, *subject, *from_t;
2906    Slrn_Article_Line_Type *l;
2907    FILE *fp;
2908    char file [SLRN_MAX_PATH_LEN];
2909    char from_buf[256];
2910    unsigned int n;
2911    int is_wrapped;
2912    char *quote_str;
2913 
2914    if ((-1 == slrn_check_batch ())
2915        || (-1 == select_affected_article ())
2916        || (-1 == run_article_hook (HOOK_REPLY))
2917       )
2918      return;
2919 
2920    /* Check for FQDN.  If it appear bogus, warn user */
2921    if (from == NULL) from = extract_reply_address ();
2922    from_t = parse_from (from, from_buf, sizeof(from_buf));
2923 
2924    if ((from_t == NULL)
2925        || ((strlen(from_buf) > 8) &&
2926 	   !(slrn_case_strcmp(".invalid",
2927 			      from_buf+strlen(from_buf)-8))))
2928      {
2929 	if (0 == slrn_get_yesno (1, _("%s appears invalid.  Continue anyway"),
2930 				 (*from_buf) ? from_buf : _("Email address")))
2931 	  return;
2932      }
2933 
2934    if (Slrn_Use_Tmpdir)
2935      fp = slrn_open_tmpfile (file, sizeof (file));
2936    else fp = slrn_open_home_file (SLRN_LETTER_FILENAME, "w", file,
2937 				  sizeof (file), 0);
2938 
2939    if (NULL == fp)
2940      {
2941 	slrn_error (_("Unable to open %s for writing."), file);
2942 	return;
2943      }
2944 
2945    /* parse header */
2946    msgid = slrn_extract_header ("Message-ID: ", 12);
2947    subject = slrn_extract_header ("Subject: ", 9);
2948 
2949    n = 0;
2950 
2951    if (from != NULL)
2952      {
2953 	if (slrn_convert_fprintf(fp, Slrn_Editor_Charset, Slrn_Display_Charset, "To: %s\n", from) < 0)
2954 	     return;
2955      }
2956    else
2957 	fputs ("To: \n", fp);
2958    n++;
2959 
2960    if (use_cc)
2961      {
2962 	char *cc, *f;
2963 
2964 	f = slrn_extract_header ("To: ", 4);
2965 	cc = slrn_extract_header ("Cc: ", 4);
2966 	if ((f != NULL) || (cc != NULL))
2967 	  {
2968 	     fputs ("Cc: ", fp);
2969 	     if (f != NULL)
2970 	       {
2971 		  fputs (f, fp);
2972 		  if (cc != NULL)
2973 		    fputs (", ", fp);
2974 	       }
2975 	     if (cc != NULL)
2976 	       fputs (cc, fp);
2977 	     fputs ("\n", fp);
2978 	     n++;
2979 	  }
2980      }
2981 
2982    if (Slrn_Generate_Email_From)
2983      {
2984 	char *fromstr = slrn_make_from_header ();
2985 	if (fromstr == NULL) return;
2986 	if (slrn_convert_fprintf(fp, Slrn_Editor_Charset, Slrn_Display_Charset, "%s\n", fromstr)< 0)
2987 	  {
2988 	     slrn_free(fromstr);
2989 	     return;
2990 	  }
2991 	slrn_free(fromstr);
2992 	n += 1;
2993      }
2994 
2995    if (subject == NULL) subject = "";
2996    else subject = subject_skip_re (subject);
2997 
2998    /* We need a copy of subject as slrn_subject_strip_was() might change it */
2999    if (NULL == (subject = slrn_safe_strmalloc (subject)))
3000      return;
3001 
3002    slrn_subject_strip_was (subject);
3003 
3004    if (slrn_convert_fprintf(fp, Slrn_Editor_Charset, Slrn_Display_Charset, "Subject: Re: %s\n", subject) < 0)
3005      {
3006 	slrn_free(subject);
3007 	return;
3008      }
3009    fprintf (fp, "In-Reply-To: %s\n", (msgid == NULL ? "" : msgid));
3010    n += 2;
3011 
3012    if ((msgid != NULL) && (*msgid != 0))
3013      {
3014 	char *refs = slrn_extract_header("References: ", 12);
3015 	if ((refs == NULL) || (*refs == 0))
3016 	  fprintf (fp, "References: %s\n", msgid);
3017 	else
3018 	  fprintf (fp, "References: %s %s\n", refs, msgid);
3019 	n++;
3020      }
3021 
3022    if (0 != *Slrn_User_Info.replyto)
3023      {
3024 	if (slrn_convert_fprintf(fp, Slrn_Editor_Charset, Slrn_Display_Charset, "Reply-To: %s\n", Slrn_User_Info.replyto) < 0)
3025 	  {
3026 	     slrn_free (subject);
3027 	     return;
3028 	  }
3029 	n += 1;
3030      }
3031 
3032    n += slrn_add_custom_headers (fp, Slrn_Reply_Custom_Headers, insert_followup_format);
3033    fputs ("\n", fp);
3034 
3035    insert_followup_format (Slrn_User_Info.reply_string, fp);
3036    fputs ("\n", fp);
3037 
3038    n += 2;
3039 
3040    is_wrapped = Slrn_Current_Article->is_wrapped;
3041    (void) _slrn_art_unwrap_article (Slrn_Current_Article);
3042 
3043    l = Slrn_Current_Article->lines;
3044    if ((Slrn_Prefix_Arg_Ptr == NULL) || ((*Slrn_Prefix_Arg_Ptr & 1) == 0))
3045      {
3046 	while ((l != NULL) && (*l->buf != 0)) l = l->next;
3047 	if (l != NULL) l = l->next;
3048      }
3049 
3050    if (NULL == (quote_str = Slrn_Quote_String))
3051      quote_str = ">";
3052 
3053    while (l != NULL)
3054      {
3055 	int smart_space = (Slrn_Smart_Quote & 0x01) && ! (l->flags & QUOTE_LINE)
3056 	  && (*l->buf != 0);
3057 	if ((*l->buf == 0) && (Slrn_Smart_Quote & 0x02))
3058 	  fputc ('\n', fp);
3059 	else
3060 	  {
3061 	     if (slrn_convert_fprintf(fp, Slrn_Editor_Charset, Slrn_Display_Charset, "%s%s%s\n", quote_str, (smart_space)? " " : "" , l->buf) < 0)
3062 	       {
3063 		  slrn_free (subject);
3064 		  return;
3065 	       }
3066 	  }
3067 	l = l->next;
3068      }
3069 
3070    if (is_wrapped)
3071      (void) _slrn_art_wrap_article (Slrn_Current_Article);
3072 
3073    slrn_add_signature (fp);
3074    slrn_fclose (fp);
3075 
3076    slrn_mail_file (file, 1, n, from_buf, subject);
3077    slrn_free (subject);
3078    if (Slrn_Use_Tmpdir) (void) slrn_delete_file (file);
3079 }
3080 
3081 /*}}}*/
3082 
reply_cmd(void)3083 static void reply_cmd (void) /*{{{*/
3084 {
3085    if (-1 == slrn_check_batch ())
3086      return;
3087 
3088    select_affected_article ();
3089    slrn_update_screen ();
3090 
3091    if ((Slrn_User_Wants_Confirmation & SLRN_CONFIRM_POST)
3092        && (0 == slrn_get_yesno (1, _("Are you sure you want to reply"))))
3093      return;
3094 
3095    reply (NULL, (Slrn_Prefix_Arg_Ptr != NULL) ? (*Slrn_Prefix_Arg_Ptr & 2) : 0);
3096 }
3097 
3098 /*}}}*/
3099 
forward_article(void)3100 static void forward_article (void) /*{{{*/
3101 {
3102    char *subject;
3103    Slrn_Article_Line_Type *l;
3104    FILE *fp;
3105    char file [SLRN_MAX_PATH_LEN];
3106    char to[SLRL_DISPLAY_BUFFER_SIZE];
3107    char *charset=NULL;
3108    int edit, is_wrapped, full = 0;
3109    unsigned int n;
3110 
3111    if (Slrn_Prefix_Arg_Ptr != NULL)
3112      {
3113 	full = *Slrn_Prefix_Arg_Ptr;
3114 	Slrn_Prefix_Arg_Ptr = NULL;
3115      }
3116 
3117    if ((-1 == slrn_check_batch ()) ||
3118        (-1 == select_affected_article ()))
3119      return;
3120 
3121    *to = 0;
3122    if (slrn_read_input (_("Forward to (^G aborts): "), NULL, to, 1, 0) <= 0)
3123      {
3124 	slrn_error (_("Aborted.  An email address is required."));
3125 	return;
3126      }
3127 
3128    if (-1 == (edit = slrn_get_yesno_cancel (1,"%s",_("Edit the message before sending"))))
3129      return;
3130 
3131    if (!edit)
3132      {
3133 	charset=Slrn_Editor_Charset;
3134 	Slrn_Editor_Charset=NULL;
3135      }
3136 
3137    if (-1 == run_article_hook (HOOK_FORWARD))
3138      return;
3139 
3140    if (Slrn_Use_Tmpdir)
3141      {
3142 	fp = slrn_open_tmpfile (file, sizeof (file));
3143      }
3144    else fp = slrn_open_home_file (SLRN_LETTER_FILENAME, "w", file,
3145 				  sizeof (file), 0);
3146 
3147    if (fp == NULL)
3148      {
3149 	slrn_error (_("Unable to open %s for writing."), file);
3150 	return;
3151      }
3152 
3153    subject = slrn_extract_header ("Subject: ", 9);
3154 
3155    if (slrn_convert_fprintf(fp, Slrn_Editor_Charset, Slrn_Display_Charset, "To: %s\n", to) <0)
3156 	return;
3157    n = 4;
3158 
3159    if (Slrn_Generate_Email_From)
3160      {
3161 	char *from = slrn_make_from_header ();
3162 	if (from == NULL) return;
3163 	if (slrn_convert_fprintf(fp, Slrn_Editor_Charset, Slrn_Display_Charset, "%s\n", from) < 0)
3164 	  {
3165 	     slrn_free(from);
3166 	     return;
3167 	  }
3168 	slrn_free(from);
3169 	n++;
3170      }
3171 
3172    if (slrn_convert_fprintf(fp, Slrn_Editor_Charset, Slrn_Display_Charset,"Subject: Fwd: %s\n", subject == NULL ? "" : subject) < 0)
3173 	return;
3174 
3175    if (0 != *Slrn_User_Info.replyto)
3176      {
3177 	if (slrn_convert_fprintf(fp, Slrn_Editor_Charset, Slrn_Display_Charset, "Reply-To: %s\n", Slrn_User_Info.replyto)  < 0)
3178 	     return;
3179 	n++;
3180      }
3181    putc ('\n', fp);
3182 
3183    is_wrapped = Slrn_Current_Article->is_wrapped;
3184    (void) _slrn_art_unwrap_article (Slrn_Current_Article);
3185 
3186    l = Slrn_Current_Article->lines;
3187    while (l != NULL)
3188      {
3189 	if (full || (0 == (l->flags & HEADER_LINE)) ||
3190 	    (0 == (l->flags & HIDDEN_LINE)))
3191 	  if (slrn_convert_fprintf(fp, Slrn_Editor_Charset, Slrn_Display_Charset, "%s\n", l->buf) < 0)
3192 	        return;
3193 	l = l->next;
3194      }
3195    slrn_fclose (fp);
3196 
3197    if (is_wrapped)
3198      (void) _slrn_art_wrap_article (Slrn_Current_Article);
3199 
3200    (void) slrn_mail_file (file, edit, n, to, subject);
3201 
3202    if (charset != NULL)
3203 	Slrn_Editor_Charset=charset;
3204 
3205    if (Slrn_Use_Tmpdir) slrn_delete_file (file);
3206 }
3207 
3208 /*}}}*/
3209 
reply_to_mailing_list(void)3210 static void reply_to_mailing_list (void)
3211 {
3212    /* Some mailing lists have a Mail-Followup-To header.  But do this if there
3213     * is no Newsgroups header.
3214     */
3215    char *mail_followupto;
3216 
3217    mail_followupto = slrn_extract_header ("Mail-Followup-To: ", 18);
3218    if ((mail_followupto == NULL) || (*mail_followupto == 0))
3219      reply (NULL, 1);
3220    else
3221      reply (mail_followupto, 0);
3222 }
3223 
3224 /* If prefix arg is 1, insert all headers.  If it is 2, insert all headers
3225  * but do not quote text nor attach signature.  2 is good for re-posting.
3226  */
followup(void)3227 static void followup (void) /*{{{*/
3228 {
3229    char *msgid, *newsgroups, *subject, *xref, *quote_str;
3230    char *cc_address, *cc_address_t;
3231    char cc_address_buf[256];
3232    char *followupto = NULL;
3233    Slrn_Article_Line_Type *l;
3234    FILE *fp;
3235    char file [SLRN_MAX_PATH_LEN];
3236    unsigned int n;
3237    int prefix_arg;
3238    int perform_cc;
3239    int free_cc_string = 0;
3240    int strip_sig, wrap;
3241    char *newsgroups_hdr;
3242 
3243    /* The perform_cc testing is ugly.  Is there an easier way?? */
3244 
3245    if ((Slrn_User_Wants_Confirmation & SLRN_CONFIRM_POST)
3246        && (slrn_get_yesno (1, _("Are you sure you want to followup")) == 0))
3247      return;
3248 
3249    if ((-1 == slrn_check_batch ()) ||
3250        (-1 == select_affected_article ()))
3251      return;
3252 
3253    if ((NULL == (newsgroups_hdr = slrn_extract_header ("Newsgroups: ", 12)))
3254        || (*newsgroups_hdr == 0))
3255      {
3256 	/* Must be a mailing list */
3257 	reply_to_mailing_list ();
3258 	return;
3259      }
3260 
3261    if (Slrn_Prefix_Arg_Ptr != NULL)
3262      {
3263 	prefix_arg = *Slrn_Prefix_Arg_Ptr;
3264 	Slrn_Prefix_Arg_Ptr = NULL;
3265      }
3266    else prefix_arg = -1;
3267 
3268    if (Slrn_Post_Obj->po_can_post == 0)
3269      {
3270 	slrn_error (_("Posting not allowed by server"));
3271 	return;
3272      }
3273 
3274    strip_sig = ((prefix_arg == -1) && Slrn_Followup_Strip_Sig);
3275 
3276    if (-1 == run_article_hook (HOOK_FOLLOWUP))
3277      return;
3278 
3279    /* Here is the logic:
3280     * If followup-to contains an email address, use that as a CC.
3281     * If followup-to contains 'poster', use poster's email address.
3282     * Otherwise, check for mail-copies-to header.  If its value is 'never'
3283     *  do not add cc header.  If it is 'always', add it.  If neither of these,
3284     *  assume it is an email address and use that.
3285     * Otherwise, use email addresss.
3286     */
3287    if (Slrn_Auto_CC_To_Poster) perform_cc = -1;
3288    else perform_cc = 0;
3289    cc_address = NULL;
3290    cc_address_t = NULL;
3291 
3292    if ((NULL != (newsgroups = slrn_extract_header ("Followup-To: ", 13))) &&
3293        (0 != *newsgroups))
3294      {
3295 	newsgroups = slrn_skip_whitespace (newsgroups);
3296 	cc_address = newsgroups;
3297 	cc_address_t = parse_from (cc_address, cc_address_buf,
3298 				   sizeof(cc_address_buf));
3299 	if (cc_address != NULL)
3300 	  {
3301 	     int is_poster;
3302 	     is_poster = (0 == slrn_case_strcmp ( cc_address,
3303 						  "poster"));
3304 	     if (is_poster
3305 		 || (NULL != slrn_strbyte (cc_address, '@')))
3306 	       /* The GNU newsgroups appear to have email addresses in the
3307 		* Followup-To header.  Yuk.
3308 		*/
3309 	       {
3310 		  if (is_poster) cc_address = extract_reply_address ();
3311 
3312 		  if ((Slrn_Warn_Followup_To == 0) ||
3313 		      slrn_get_yesno (1, _("Do you want to reply to POSTER as poster prefers")))
3314 		    {
3315 		       reply (cc_address, 0);
3316 		       return;
3317 		    }
3318 		  newsgroups = NULL;
3319 	       }
3320 	  } /* if (cc_address != NULL) */
3321 	/* There's a Followup-To to a normal Newsgroup */
3322 	if (Slrn_Warn_Followup_To && (newsgroups != NULL))
3323 	  {
3324 	     int warn = 0;
3325 	     if (Slrn_Warn_Followup_To == 1)
3326 	       /* Warn if "Followup-To:" does not include current newsgroup */
3327 	       {
3328 		  char* lpos;
3329 		  char* rpos = newsgroups;
3330 		  char* epos = rpos + strlen(rpos);
3331 		  warn = 1;
3332 		  while (rpos < epos)
3333 		    {
3334 		       unsigned int len;
3335 
3336 		       lpos = rpos;
3337 		       rpos = slrn_strbyte(lpos, ',');
3338 		       if (rpos == NULL)
3339 			 rpos = epos;
3340 
3341 		       len = (unsigned int) (rpos - lpos);
3342 		       if ((len == strlen(Slrn_Current_Group_Name))
3343 			   && (0 == strncmp(lpos, Slrn_Current_Group_Name, len)))
3344 			 {
3345 			    warn = 0;
3346 			    break;
3347 			 }
3348 		       rpos++;
3349 		    }
3350 	       }
3351 	     else
3352 	       warn = 1;
3353 
3354 	     if (warn &&
3355 		 (slrn_get_yesno (1, _("Followup to %s as poster prefers"), newsgroups) == 0))
3356 	       newsgroups = NULL;
3357 	  } /* if (Slrn_Warn_Followup_To) */
3358      }
3359 
3360    if ((newsgroups == NULL)
3361        /* Hmm..  I have also seen an empty Followup-To: header on a GNU
3362 	* newsgroup.
3363 	*/
3364        || (*newsgroups == 0))
3365      {
3366 	newsgroups = newsgroups_hdr;
3367 	if (newsgroups == NULL)
3368 	  newsgroups = "";
3369      }
3370 
3371    if (Slrn_Netiquette_Warnings
3372        && (NULL != slrn_strbyte(newsgroups, ',')))
3373      {
3374 	/* Note to translators: Here, "fF" means "Followup-To", "aA" is for
3375 	 * "all groups", "tT" is "this group only" and "cC" means "cancel".
3376 	 * Do not change the length of the string! You cannot use any of the
3377 	 * default characters for other fields than they originally stood for.
3378 	 */
3379         char *responses=_("fFaAtTcC");
3380 	char rsp;
3381 	if (strlen (responses) != 8)
3382 	  responses = "";
3383 	rsp = slrn_get_response ("fFaAtTcC", responses,
3384 				 _("Crosspost using: (\001F)ollowup-To, (\001A)ll groups, (\001T)his group, (\001C)ancel ?"));
3385 	rsp = slrn_map_translated_char ("fFaAtTcC", responses, rsp) | 0x20;
3386 	switch (rsp)
3387 	  {
3388 	   case 'a':
3389 	     break;
3390 
3391 	   case 'f':
3392 	     followupto = Slrn_Current_Group_Name;
3393 	     break;
3394 
3395 	   case 't':
3396 	     newsgroups = Slrn_Current_Group_Name;
3397 	     break;
3398 
3399 	   case 'c':
3400 	     return;
3401 	  }
3402      }
3403 
3404    if (perform_cc)
3405      {
3406 	if (((NULL != (cc_address = slrn_extract_header ("X-Mail-Copies-To: ", 18))) &&
3407 	     (0 != *cc_address)) ||
3408 	    ((NULL != (cc_address = slrn_extract_header ("Mail-Copies-To: ", 16))) &&
3409 	     (0 != *cc_address)))
3410 	  {
3411 	     /* Original poster has requested a certain cc-ing behaviour
3412 	      * which should override whatever default the user has set */
3413 	     perform_cc = 1;
3414 	     if ((0 == slrn_case_strcmp ( cc_address,
3415 					  "always"))
3416 		 || (0 == slrn_case_strcmp ( cc_address,
3417 					     "poster")))
3418 	       {
3419 		  cc_address = NULL;
3420 	       }
3421 	     else if ((0 == slrn_case_strcmp ( cc_address,
3422 					       "never"))
3423 		      || (0 == slrn_case_strcmp ( cc_address,
3424 						  "nobody")))
3425 	       {
3426 		  perform_cc = 0;
3427 		  cc_address = NULL;
3428 	       }
3429 	     else if (NULL == (cc_address_t = parse_from (cc_address,
3430 							  cc_address_buf,
3431 							  sizeof(cc_address_buf))))
3432 	       cc_address = NULL; /* do CC, but use "From" / "Reply-To:" address */
3433 
3434 	  }
3435 
3436 	if (prefix_arg == 2)
3437 	  perform_cc = 0;
3438      }
3439 
3440    if (cc_address == NULL)
3441      {
3442 	cc_address = extract_reply_address ();
3443 	cc_address_t = parse_from (cc_address, cc_address_buf,
3444 				   sizeof(cc_address_buf));
3445      }
3446 
3447    if ((perform_cc != 0)
3448        && (cc_address_t != NULL))
3449      {
3450 	int cc_hook_status;
3451 
3452 	if (-1 == (cc_hook_status = slrn_run_hooks (HOOK_CC, 1, cc_address)))
3453 	  return;
3454 	if (-1 == check_for_current_article ())
3455 	  return;
3456 	if (cc_hook_status == 1)
3457 	  {
3458 	     if (-1 == SLang_pop_slstring (&cc_address))
3459 	       return;
3460 	     cc_address_t = parse_from (cc_address, cc_address_buf,
3461 					sizeof(cc_address_buf));
3462 	     free_cc_string = 1;
3463 	     if (*cc_address == 0)
3464 	       perform_cc = 0;
3465 	  }
3466 	if ((perform_cc == 1) && (Slrn_Auto_CC_To_Poster & 0x01))
3467 	  {
3468 	     if (-1 == (perform_cc = slrn_get_yesno_cancel (1,"%s",_("Cc message as requested by poster"))))
3469 	       goto free_and_return;
3470 	  }
3471 	else if (perform_cc == -1)
3472 	  {
3473 	     if (Slrn_Auto_CC_To_Poster < 3) perform_cc = 0;
3474 	     else if (-1 == (perform_cc = slrn_get_yesno_cancel (1,"%s",_("Cc message to poster"))))
3475 	       goto free_and_return;
3476 	  }
3477 
3478 	if (perform_cc)
3479 	  {
3480 	     if ((NULL == cc_address_t)
3481 		 || ((strlen(cc_address_buf) > 8) &&
3482 		     !(slrn_case_strcmp(".invalid",
3483 					cc_address_buf+
3484 					strlen(cc_address_buf)-8))))
3485 	       {
3486 		  perform_cc = slrn_get_yesno_cancel (1,_("%s appears invalid.  CC anyway"), *cc_address_buf ? cc_address_buf : _("Email address"));
3487 		  if (perform_cc < 0)
3488 		     goto free_and_return;
3489 	       }
3490 	  }
3491      }
3492 
3493    msgid = slrn_extract_header ("Message-ID: ", 12);
3494 
3495    if (NULL != (subject = slrn_extract_header ("Subject: ", 9)))
3496      subject = subject_skip_re (subject);
3497    else subject = "";
3498 
3499    if (Slrn_Use_Tmpdir)
3500      fp = slrn_open_tmpfile (file, sizeof (file));
3501    else fp = slrn_open_home_file (SLRN_FOLLOWUP_FILENAME, "w", file,
3502 				  sizeof (file), 0);
3503 
3504    if (fp == NULL)
3505      {
3506 	slrn_error (_("Unable to open %s for writing."), file);
3507 	goto free_and_return;
3508      }
3509 
3510    fprintf (fp, "Newsgroups: %s\n", newsgroups);  n = 3;
3511 
3512 #if ! SLRN_HAS_STRICT_FROM
3513      {
3514 	char *from = slrn_make_from_header ();
3515 	if (from == NULL) return;
3516 	if (slrn_convert_fprintf(fp, Slrn_Editor_Charset, Slrn_Display_Charset, "%s\n", from) < 0)
3517 	  {
3518 	     slrn_free(from);
3519 	     goto free_and_return;
3520 	  }
3521 	slrn_free(from);
3522 	n++;
3523      }
3524 #endif
3525 
3526    if (NULL == (subject = slrn_safe_strmalloc (subject)))
3527      goto free_and_return;
3528 
3529    slrn_subject_strip_was (subject);
3530    if (slrn_convert_fprintf(fp, Slrn_Editor_Charset, Slrn_Display_Charset, "Subject: Re: %s\n", subject) < 0)
3531      {
3532 	slrn_free (subject);
3533 	goto free_and_return;
3534      }
3535    slrn_free (subject);
3536 
3537    xref = slrn_extract_header("References: ", 12);
3538    if ((msgid != NULL) && (*msgid != 0))
3539      {
3540 	if ((xref == NULL) || (*xref == 0))
3541 	  fprintf (fp, "References: %s\n", msgid);
3542 	else
3543 	  fprintf (fp, "References: %s %s\n", xref, msgid);
3544 	n++;
3545      }
3546 
3547    if (Slrn_User_Info.org != NULL)
3548      {
3549 	if (slrn_convert_fprintf(fp, Slrn_Editor_Charset, Slrn_Display_Charset, "Organization: %s\n", Slrn_User_Info.org) < 0)
3550 	  goto free_and_return;
3551 	n++;
3552      }
3553 
3554    if (perform_cc
3555        && (cc_address_t != NULL))
3556      {
3557 	if (slrn_convert_fprintf(fp, Slrn_Editor_Charset, Slrn_Display_Charset,  "Cc: %s\n", cc_address) < 0)
3558 	     goto free_and_return;
3559 	n++;
3560      }
3561 
3562    if (0 != *Slrn_User_Info.replyto)
3563      {
3564 	if (slrn_convert_fprintf(fp, Slrn_Editor_Charset, Slrn_Display_Charset, "Reply-To: %s\n", Slrn_User_Info.replyto) < 0)
3565 	     goto free_and_return;
3566 	n++;
3567      }
3568 
3569    fputs("Followup-To: ", fp);
3570    if (followupto != NULL)
3571      fputs(followupto, fp);
3572    fputs("\n", fp);
3573    n++;
3574 
3575    n += slrn_add_custom_headers (fp, Slrn_Followup_Custom_Headers, insert_followup_format);
3576 
3577    fputs ("\n", fp);
3578 
3579    if (prefix_arg != 2)
3580      {
3581 	if ((followupto != NULL) && (*Slrn_User_Info.followupto_string != 0))
3582 	  {
3583 	     insert_followup_format (Slrn_User_Info.followupto_string, fp);
3584 	     fputs ("\n", fp);
3585 	  }
3586 	insert_followup_format (Slrn_User_Info.followup_string, fp);
3587 	fputs ("\n", fp);
3588      }
3589    n += 1;			       /* by having + 1, the cursor will be
3590 					* placed on the first line of message.
3591 					*/
3592 
3593    wrap = Slrn_Current_Article->is_wrapped;
3594    (void) _slrn_art_unwrap_article (Slrn_Current_Article);
3595 
3596    /* skip header */
3597    l = Slrn_Current_Article->lines;
3598    if (prefix_arg == -1)
3599      {
3600 	while ((l != NULL) && (*l->buf != 0)) l = l->next;
3601 	if (l != NULL) l = l->next;
3602      }
3603 
3604    if (prefix_arg == 2) quote_str = "";
3605    else if (NULL == (quote_str = Slrn_Quote_String))
3606      quote_str = ">";
3607 
3608    while (l != NULL)
3609      {
3610 	int smart_space = (Slrn_Smart_Quote & 0x01) && ! (l->flags & QUOTE_LINE)
3611 	  && (prefix_arg != 2) && (*l->buf != 0);
3612 
3613 	if (strip_sig
3614 	    && (l->flags & SIGNATURE_LINE))
3615 	  break;
3616 
3617 	if (strip_sig && (l->flags & PGP_SIGNATURE_LINE))
3618 	  {
3619 	     l = l->next;
3620 	     continue;
3621 	  }
3622 
3623 	if ((*l->buf == 0) && (Slrn_Smart_Quote & 0x02))
3624 	  fputc ('\n', fp);
3625 	else
3626 	  {
3627 	     if (slrn_convert_fprintf(fp, Slrn_Editor_Charset, Slrn_Display_Charset, "%s%s%s\n", quote_str, (smart_space)? " " : "" , l->buf) < 0)
3628 		  goto free_and_return;
3629 	  }
3630 
3631 	l = l->next;
3632      }
3633 
3634    if (wrap)
3635      (void) _slrn_art_wrap_article (Slrn_Current_Article);
3636 
3637    if (prefix_arg != 2) slrn_add_signature (fp);
3638    slrn_fclose (fp);
3639 
3640    if (slrn_edit_file (Slrn_Editor_Post, file, n, 1) >= 0)
3641      {
3642 	(void) slrn_post_file (file, cc_address, 0);
3643      }
3644 
3645    if (Slrn_Use_Tmpdir) (void) slrn_delete_file (file);
3646 
3647    free_and_return:
3648    if (free_cc_string && (cc_address != NULL))
3649      SLang_free_slstring (cc_address);
3650 }
3651 
3652 /*}}}*/
3653 
3654 #if SLRN_HAS_CANLOCK
3655 /* Generate a key needed for canceling and superseding messages when
3656  * cancel-locking is used. Returns a malloced string or NULL on failure. */
gen_cancel_key(char * msgid)3657 static char* gen_cancel_key (char* msgid) /*{{{*/
3658 {
3659    FILE *cansecret;
3660    char *buf, *cankey;
3661    unsigned int filelen;
3662    char canfile[SLRN_MAX_PATH_LEN];
3663 
3664    if (0 == *Slrn_User_Info.cancelsecret)
3665      return NULL;
3666 
3667    if ((cansecret = slrn_open_home_file (Slrn_User_Info.cancelsecret, "r",
3668 					 canfile, SLRN_MAX_PATH_LEN, 0)) == NULL)
3669      {
3670 	slrn_error (_("Cannot open file: %s"), Slrn_User_Info.cancelsecret);
3671 	return NULL;
3672      }
3673 
3674    (void) fseek (cansecret, 0, SEEK_END);
3675    if ((filelen = ftell(cansecret)) == 0)
3676      {
3677         slrn_error (_("Zero length file: %s"), Slrn_User_Info.cancelsecret);
3678 	fclose (cansecret);
3679         return NULL;
3680      }
3681 
3682    if (NULL == (buf = slrn_malloc (filelen+1, 0, 1)))
3683      {
3684 	fclose (cansecret);
3685 	return NULL;
3686      }
3687    (void) fseek (cansecret, 0, SEEK_SET);
3688    (void) fread (buf, filelen, 1, cansecret);
3689 
3690 # if 0
3691    cankey = md5_key (buf, filelen, msgid, strlen(msgid));
3692 # else /* by default we use SHA-1 */
3693    cankey = sha_key ((unsigned char *) buf, filelen, (unsigned char *)msgid, strlen(msgid));
3694 # endif
3695 
3696    fclose (cansecret);
3697    slrn_free (buf);
3698    return cankey;
3699 }
3700 /*}}}*/
3701 #endif /* CANCEL_LOCKS */
3702 
3703 /* Copy a message, adding a "Supersedes: " header for the message it replaces.
3704  * Not all headers of original are preserved; notably Cc is discarded.
3705  */
supersede(void)3706 static void supersede (void) /*{{{*/
3707 {
3708    char *followupto, *msgid, *newsgroups, *subject, *xref;
3709    Slrn_Article_Line_Type *l;
3710    FILE *fp;
3711    char file[SLRN_MAX_PATH_LEN], from_buf[512];
3712    unsigned int n;
3713    int wrap;
3714    char *from, *me;
3715    char me_buf[512];
3716 
3717    if ((-1 == slrn_check_batch ()) ||
3718        (-1 == select_affected_article ()))
3719      return;
3720 
3721    if (Slrn_Post_Obj->po_can_post == 0)
3722      {
3723 	slrn_error (_("Posting not allowed by server"));
3724 	return;
3725      }
3726 
3727    from = slrn_extract_header ("From: ", 6);
3728    if (from != NULL)
3729       (void) parse_from (from, from_buf, sizeof(from_buf));
3730    else
3731       from_buf[0] = '\0';
3732    if (NULL == (me = slrn_make_from_header())) return;
3733    (void) parse_from (me+6, me_buf, sizeof(me_buf));
3734 
3735    if (slrn_case_strcmp ( from_buf,  me_buf))
3736      {
3737         slrn_error (_("Failed: Your name: '%s' is not '%s'"), me_buf, from_buf);
3738         return;
3739      }
3740 
3741    if ((Slrn_User_Wants_Confirmation & SLRN_CONFIRM_POST)
3742        && (slrn_get_yesno (1, _("Are you sure you want to supersede")) == 0))
3743      return;
3744 
3745    if (-1 == run_article_hook (HOOK_SUPERSEDE))
3746      return;
3747 
3748    if (NULL == (newsgroups = slrn_extract_header ("Newsgroups: ", 12)))
3749      newsgroups = "";
3750    if (NULL == (followupto = slrn_extract_header ("Followup-To: ", 12)))
3751      followupto = "";
3752    if (NULL == (subject = slrn_extract_header ("Subject: ", 9)))
3753      subject = "";
3754    if (NULL == (msgid = slrn_extract_header ("Message-ID: ", 12)))
3755      msgid = "";
3756    xref = slrn_extract_header("References: ", 12);
3757 
3758    if (Slrn_Use_Tmpdir)
3759      fp = slrn_open_tmpfile (file, sizeof (file));
3760    else fp = slrn_open_home_file (SLRN_FOLLOWUP_FILENAME, "w", file,
3761 				  sizeof (file), 0);
3762 
3763    if (fp == NULL)
3764      {
3765 	slrn_error (_("Unable to open %s for writing."), file);
3766 	slrn_free(me);
3767 	return;
3768      }
3769 
3770    fprintf (fp, "Newsgroups: %s\n", newsgroups); n = 5;
3771 #if ! SLRN_HAS_STRICT_FROM
3772    if (slrn_convert_fprintf(fp, Slrn_Editor_Charset, Slrn_Display_Charset, "%s\n", me) < 0)
3773      {
3774 	slrn_free(me);
3775 	return;
3776      }
3777    n++;
3778 #endif
3779    slrn_free(me);
3780 
3781    if (slrn_convert_fprintf(fp, Slrn_Editor_Charset, Slrn_Display_Charset, "Subject: %s\n", subject) < 0)
3782 	return;
3783 
3784    fprintf (fp, "Supersedes: %s\nFollowup-To: %s\n",
3785 	    msgid, followupto);
3786 #if SLRN_HAS_CANLOCK
3787    /* Abuse 'from', we don't need its content anymore */
3788    if (NULL != (from = gen_cancel_key(msgid)))
3789      {
3790 	fprintf (fp, "Cancel-Key: %s\n", from);
3791 	slrn_free (from);
3792      }
3793 #endif
3794 
3795    if ((xref != NULL) && (*xref != 0))
3796      {
3797 	fprintf (fp, "References: %s\n", xref);
3798 	n++;
3799      }
3800 
3801    if (Slrn_User_Info.org != NULL)
3802      {
3803 	if (slrn_convert_fprintf(fp, Slrn_Editor_Charset, Slrn_Display_Charset, "Organization: %s\n", Slrn_User_Info.org) < 0)
3804 	     return;
3805 	n++;
3806      }
3807 
3808    if (0 != *Slrn_User_Info.replyto)
3809      {
3810 	if (slrn_convert_fprintf(fp, Slrn_Editor_Charset, Slrn_Display_Charset,  "Reply-To: %s\n", Slrn_User_Info.replyto) < 0)
3811 	     return;
3812 	n++;
3813      }
3814 
3815    n += slrn_add_custom_headers (fp, Slrn_Supersedes_Custom_Headers, insert_followup_format);
3816 
3817    fputs ("\n", fp);
3818    n += 1;
3819 
3820    wrap = Slrn_Current_Article->is_wrapped;
3821    (void) _slrn_art_unwrap_article (Slrn_Current_Article);
3822 
3823    /* skip header */
3824    l = Slrn_Current_Article->lines;
3825    while ((l != NULL) && (*l->buf != 0)) l = l->next;
3826    if (l != NULL) l = l->next;
3827 
3828    while (l != NULL)
3829      {
3830 	if (slrn_convert_fprintf(fp, Slrn_Editor_Charset, Slrn_Display_Charset, "%s\n", l->buf) < 0)
3831 	     return;
3832 
3833 	l = l->next;
3834      }
3835 
3836    if (wrap)
3837      (void) _slrn_art_wrap_article (Slrn_Current_Article);
3838 
3839    slrn_fclose (fp);
3840 
3841    if (slrn_edit_file (Slrn_Editor_Post, file, n, 1) >= 0)
3842      {
3843 	(void) slrn_post_file (file, NULL, 0);
3844      }
3845    if (Slrn_Use_Tmpdir) (void) slrn_delete_file (file);
3846 }
3847 
3848 /*}}}*/
3849 
3850 /*}}}*/
3851 
3852 /*{{{ header movement functions */
3853 
slrn_header_cursor_pos(void)3854 int slrn_header_cursor_pos (void)
3855 {
3856    return Last_Cursor_Row;
3857 }
3858 
slrn_header_down_n(unsigned int n,int err)3859 unsigned int slrn_header_down_n (unsigned int n, int err) /*{{{*/
3860 {
3861    unsigned int m;
3862 
3863    m = SLscroll_next_n (&Slrn_Header_Window, n);
3864    Slrn_Current_Header = (Slrn_Header_Type *) Slrn_Header_Window.current_line;
3865 
3866    if (err && (m != n))
3867      slrn_error (_("End of buffer."));
3868 
3869    return m;
3870 }
3871 /*}}}*/
3872 
header_down(void)3873 static void header_down (void) /*{{{*/
3874 {
3875    slrn_header_down_n (1, 1);
3876 }
3877 
3878 /*}}}*/
3879 
slrn_header_up_n(unsigned int n,int err)3880 unsigned int slrn_header_up_n (unsigned int n, int err) /*{{{*/
3881 {
3882    unsigned int m;
3883 
3884    m = SLscroll_prev_n (&Slrn_Header_Window, n);
3885    Slrn_Current_Header = (Slrn_Header_Type *) Slrn_Header_Window.current_line;
3886 
3887    if (err && (m != n))
3888      slrn_error (_("Top of buffer."));
3889 
3890    return m;
3891 }
3892 
3893 /*}}}*/
3894 
header_up(void)3895 static void header_up (void) /*{{{*/
3896 {
3897    slrn_header_up_n (1, 1);
3898 }
3899 
3900 /*}}}*/
3901 
header_pageup(void)3902 static void header_pageup (void) /*{{{*/
3903 {
3904    Slrn_Full_Screen_Update = 1;
3905    if (-1 == SLscroll_pageup (&Slrn_Header_Window))
3906      slrn_error (_("Top of buffer."));
3907    Slrn_Current_Header = (Slrn_Header_Type *) Slrn_Header_Window.current_line;
3908 }
3909 
3910 /*}}}*/
3911 
header_pagedn(void)3912 static void header_pagedn (void) /*{{{*/
3913 {
3914    Slrn_Full_Screen_Update = 1;
3915    if (-1 == SLscroll_pagedown (&Slrn_Header_Window))
3916      slrn_error (_("End of buffer."));
3917    Slrn_Current_Header = (Slrn_Header_Type *) Slrn_Header_Window.current_line;
3918 }
3919 
3920 /*}}}*/
3921 
header_bob(void)3922 static void header_bob (void) /*{{{*/
3923 {
3924    Slrn_Current_Header = _art_Headers;
3925    _art_find_header_line_num ();
3926 }
3927 
3928 /*}}}*/
3929 
header_eob(void)3930 static void header_eob (void) /*{{{*/
3931 {
3932    while (0xFFFF == slrn_header_down_n (0xFFFF, 0));
3933 }
3934 
3935 /*}}}*/
3936 
prev_unread(void)3937 static int prev_unread (void) /*{{{*/
3938 {
3939    Slrn_Header_Type *h;
3940 
3941    h = Slrn_Current_Header -> prev;
3942 
3943    while (h != NULL)
3944      {
3945 	if (0 == (h->flags & (HEADER_READ|HEADER_WITHOUT_BODY))) break;
3946 	h = h->prev;
3947      }
3948 
3949    if (h == NULL)
3950      {
3951 	slrn_message (_("No previous unread articles."));
3952 	return 0;
3953      }
3954 
3955    Slrn_Current_Header = h;
3956 
3957    if (h->flags & HEADER_HIDDEN)
3958      slrn_uncollapse_this_thread (h, 0);
3959 
3960    _art_find_header_line_num ();
3961    return 1;
3962 }
3963 
3964 /*}}}*/
3965 
goto_last_read(void)3966 static void goto_last_read (void) /*{{{*/
3967 {
3968    if (Last_Read_Header == NULL) return;
3969    slrn_goto_header (Last_Read_Header, 1);
3970 }
3971 
3972 /*}}}*/
3973 
art_prev_unread(void)3974 static void art_prev_unread (void) /*{{{*/
3975 {
3976    if (prev_unread () && Article_Visible) art_pagedn  ();
3977 }
3978 
3979 /*}}}*/
3980 
slrn_next_unread_header(int skip_without_body)3981 int slrn_next_unread_header (int skip_without_body) /*{{{*/
3982 {
3983    Slrn_Header_Type *h;
3984 
3985    h = Slrn_Current_Header->next;
3986 
3987    while (h != NULL)
3988      {
3989 	if ((0 == (h->flags & HEADER_READ)) &&
3990 	    (!skip_without_body || (0 == (h->flags & HEADER_WITHOUT_BODY))))
3991 	  break;
3992 	h = h->next;
3993      }
3994 
3995    if (h == NULL)
3996      {
3997 	slrn_message (_("No following unread articles."));
3998 	return 0;
3999      }
4000 
4001    Slrn_Current_Header = h;
4002    if (h->flags & HEADER_HIDDEN)
4003      slrn_uncollapse_this_thread (h, 0);
4004 
4005    _art_find_header_line_num ();
4006 
4007    return 1;
4008 }
4009 
4010 /*}}}*/
4011 
art_next_unread(void)4012 static void art_next_unread (void) /*{{{*/
4013 {
4014    char ch;
4015    char *keyseq;
4016 
4017    if (slrn_next_unread_header (1))
4018      {
4019 	if (Article_Visible) art_pagedn  ();
4020 	return;
4021      }
4022 
4023    if (Slrn_Query_Next_Group == 0)
4024      {
4025 	skip_to_next_group ();
4026 	return;
4027      }
4028 
4029    if (Slrn_Batch) return;
4030 
4031    keyseq = slrn_help_keyseq_from_function ("next", Slrn_Article_Keymap);
4032    if (keyseq != NULL)
4033      {
4034 	SLang_Key_Type *key;
4035 	char *keystr = slrn_help_keyseq_to_string(keyseq+1,*keyseq-1);
4036 	if (keystr == NULL)
4037 	  keystr = SLang_make_keystring((unsigned char*)keyseq);
4038 	slrn_message_now (_("No following unread articles.  Press %s for next group."),
4039 			  keystr);
4040 	key = SLang_do_key (Slrn_Article_Keymap, slrn_getkey);
4041 	if (key == NULL) return;
4042 	if ((key->type != SLKEY_F_INTRINSIC) ||
4043 	    (key->f.f != (FVOID_STAR) art_next_unread))
4044 	  {
4045 	     unsigned int len = key->str[0];
4046 	     if (len != 0)
4047 	       SLang_ungetkey_string (key->str+1, len-1);
4048 	     return;
4049 	  }
4050      }
4051    else /* next is unbound */
4052      {
4053 	slrn_message_now (_("No following unread articles.  Press %s for next group."), "n");
4054 #if 0
4055 	ch = SLang_getkey ();
4056 #else
4057 	ch = slrn_getkey ();
4058 #endif
4059 	if (ch != 'n')
4060 	  {
4061 	     SLang_ungetkey (ch);
4062 	     return;
4063 	  }
4064      }
4065 
4066    skip_to_next_group ();
4067 }
4068 
4069 /*}}}*/
4070 
next_high_score(void)4071 static void next_high_score (void) /*{{{*/
4072 {
4073    Slrn_Header_Type *l;
4074 
4075    l = Slrn_Current_Header->next;
4076 
4077    while (l != NULL)
4078      {
4079 	if (l->flags & HEADER_HIGH_SCORE)
4080 	  {
4081 	     break;
4082 	  }
4083 	l = l->next;
4084      }
4085 
4086    if (l == NULL)
4087      {
4088 	slrn_error (_("No more high scoring articles."));
4089 	return;
4090      }
4091 
4092    if (l->flags & HEADER_HIDDEN) slrn_uncollapse_this_thread (l, 0);
4093 
4094    Slrn_Current_Header = l;
4095    _art_find_header_line_num ();
4096 
4097    if (Article_Visible)
4098      {
4099 	if (Header_Showing != Slrn_Current_Header)
4100 	  art_pagedn ();
4101      }
4102 }
4103 
4104 /*}}}*/
4105 
4106 static Slrn_Header_Type *Same_Subject_Start_Header;
next_header_same_subject(void)4107 static void next_header_same_subject (void) /*{{{*/
4108 {
4109 #if SLANG_VERSION < 20000
4110    SLsearch_Type st;
4111 #else
4112    SLsearch_Type *st;
4113    unsigned int flags = SLSEARCH_CASELESS;
4114 #endif
4115    Slrn_Header_Type *l;
4116    static char same_subject[SLRL_DISPLAY_BUFFER_SIZE];
4117 
4118    if ((Same_Subject_Start_Header == NULL)
4119        || (Slrn_Prefix_Arg_Ptr != NULL))
4120      {
4121 	Slrn_Prefix_Arg_Ptr = NULL;
4122 	if (slrn_read_input (_("Subject: "), same_subject, NULL, 0, 0) <= 0) return;
4123 	Same_Subject_Start_Header = Slrn_Current_Header;
4124      }
4125 #if SLANG_VERSION < 20000
4126    SLsearch_init (same_subject, 1, 0, &st);
4127 #else
4128    if (Slrn_UTF8_Mode) flags |= SLSEARCH_UTF8;
4129    if (NULL == (st = SLsearch_new ((SLuchar_Type *)same_subject, flags)))
4130      return;
4131 #endif
4132    l = Slrn_Current_Header->next;
4133 
4134    while (l != NULL)
4135      {
4136 	if (
4137 #if 0
4138 	    /* Do we want to do this?? */
4139 	    ((l->flags & HEADER_READ) == 0) &&
4140 #endif
4141 	    (l->subject != NULL)
4142 #if SLANG_VERSION < 20000
4143 	    && (NULL != SLsearch ((unsigned char *) l->subject,
4144 				  (unsigned char *) l->subject + strlen (l->subject),
4145 				  &st))
4146 #else
4147 	    && (NULL != SLsearch_forward (st, (SLuchar_Type *)l->subject,
4148 					  (SLuchar_Type *)l->subject + strlen (l->subject)))
4149 #endif
4150 	    )
4151 	  break;
4152 
4153 	l = l->next;
4154      }
4155 
4156    if (l == NULL)
4157      {
4158 	slrn_error (_("No more articles on that subject."));
4159 	l = Same_Subject_Start_Header;
4160 	Same_Subject_Start_Header = NULL;
4161      }
4162 
4163    if (l->flags & HEADER_HIDDEN) slrn_uncollapse_this_thread (l, 0);
4164    Slrn_Current_Header = l;
4165    _art_find_header_line_num ();
4166    if ((Same_Subject_Start_Header != NULL)
4167        && (Article_Visible))
4168      {
4169 	art_pagedn ();
4170      }
4171 #if SLANG_VERSION >= 20000
4172    SLsearch_delete (st);
4173 #endif
4174 }
4175 
4176 /*}}}*/
4177 
goto_header_number(void)4178 static void goto_header_number (void) /*{{{*/
4179 {
4180    int diff, i, ich;
4181 
4182    if (Slrn_Batch) return;
4183 
4184    i = 0;
4185    ich = SLang_Last_Key_Char;
4186    do
4187      {
4188 	i = i * 10 + (ich - '0');
4189 	if (10 * i > Largest_Header_Number)
4190 	  {
4191 	     ich = '\r';
4192 	     break;
4193 	  }
4194 	slrn_message_now (_("Goto Header: %d"), i);
4195      }
4196 #if 0
4197    while ((ich = SLang_getkey ()), (ich <= '9') && (ich >= '0'));
4198 #else
4199    while ((ich = slrn_getkey ()), (ich <= '9') && (ich >= '0'));
4200 #endif
4201 
4202    if (SLKeyBoard_Quit) return;
4203 
4204    if (ich != '\r')
4205      SLang_ungetkey (ich);
4206 
4207    diff = i - Last_Cursor_Row;
4208    if (diff > 0) slrn_header_down_n (diff, 0); else slrn_header_up_n (-diff, 0);
4209    slrn_run_hooks (HOOK_HEADER_NUMBER, 0);
4210    Slrn_Full_Screen_Update = 1;
4211 }
4212 
4213 /*}}}*/
4214 
4215 /*}}}*/
4216 
4217 /*{{{ article save/decode functions */
4218 
write_article_line(Slrn_Article_Line_Type * l,FILE * fp,int convert)4219 static int write_article_line (Slrn_Article_Line_Type *l, FILE *fp, int convert)
4220 {
4221    while (l != NULL)
4222      {
4223 	char *buf;
4224 	Slrn_Article_Line_Type *next = l->next;
4225 
4226 	buf = l->buf;
4227 	if (l->flags & WRAPPED_LINE) buf++;   /* skip space */
4228 
4229 	if (convert)
4230 	  {
4231 	     if (slrn_convert_fprintf(fp, Slrn_Editor_Charset, Slrn_Display_Charset, "%s", buf)< 0)
4232 	       return -1;
4233 	  }
4234 	else
4235 	  {
4236 	     if (EOF == fputs (buf, fp))
4237 		  return -1;
4238 	  }
4239 
4240 	if ((next == NULL) || (0 == (next->flags & WRAPPED_LINE)))
4241 	  {
4242 	     if (EOF == putc ('\n', fp))
4243 	       return -1;
4244 	  }
4245 	l = next;
4246      }
4247 
4248    return 0;
4249 }
4250 
4251 /* returns the header that should be affected by interactive commands. */
affected_header(void)4252 static Slrn_Header_Type *affected_header (void) /*{{{*/
4253 {
4254    if ((Header_Showing != NULL) && Article_Visible)
4255      return Header_Showing;
4256    else
4257      return Slrn_Current_Header;
4258 }
4259 
4260 /*}}}*/
4261 
slrn_save_current_article(char * file)4262 int slrn_save_current_article (char *file) /*{{{*/
4263 {
4264    FILE *fp = NULL;
4265    Slrn_Header_Type *h;
4266    Slrn_Article_Line_Type *lines;
4267    int retval = 0;
4268 
4269    /* We're saving raw_lines, this saves the re-encoding later */
4270    if (NULL == (h = affected_header ()) ||
4271        select_header (h, Slrn_Del_Article_Upon_Read) < 0)
4272      return -1;
4273 
4274    lines = Slrn_Current_Article->raw_lines;
4275 
4276    fp = fopen (file, "w");
4277 
4278    if (fp == NULL)
4279      {
4280 	slrn_error (_("Unable to open %s."), file);
4281 	retval = -1;
4282      }
4283    else
4284      {
4285 	if (-1 == (retval = write_article_line (lines, fp, 0)))
4286 	  slrn_error (_("Error writing to %s."), file);
4287 	fclose (fp);
4288      }
4289 
4290    return retval;
4291 }
4292 
4293 /*}}}*/
4294 
save_article_as_unix_mail(Slrn_Header_Type * h,FILE * fp)4295 static int save_article_as_unix_mail (Slrn_Header_Type *h, FILE *fp) /*{{{*/
4296 {
4297    int is_wrapped = 0;
4298    Slrn_Article_Line_Type *l = NULL;
4299    Slrn_Article_Type *a = Slrn_Current_Article;
4300    char *from;
4301    char from_buf[256];
4302    time_t now;
4303 
4304    if ((Header_Showing != h) || (a == NULL))
4305      {
4306 	if (NULL == (a = read_article (h, Slrn_Del_Article_Upon_Read)))
4307 	  return -1;
4308      }
4309 
4310    l = a->raw_lines;
4311    from = h->from;
4312    if (from != NULL) from = parse_from (from, from_buf, sizeof(from_buf));
4313    else from_buf[0] = '\0';
4314    if ((from == NULL) || (*from_buf == 0))
4315       strcpy (from_buf, "nobody@nowhere"); /* safe */
4316 
4317    time (&now);
4318    fprintf (fp, "From %s %s", from_buf, ctime(&now));
4319 
4320    while (l != NULL)
4321      {
4322 	if ((*l->buf == 'F')
4323 	    && !strncmp ("From", l->buf, 4)
4324 	    && ((unsigned char)(l->buf[4]) <= ' '))
4325 	  {
4326 	     putc ('>', fp);
4327 	  }
4328 
4329 	fputs (l->buf, fp);
4330 	putc ('\n', fp);
4331 	l = l->next;
4332      }
4333 
4334    fputs ("\n", fp); /* one empty line as a separator */
4335 
4336    if (a != Slrn_Current_Article) slrn_art_free_article (a);
4337    else if (is_wrapped) _slrn_art_wrap_article (Slrn_Current_Article);
4338 
4339    return 0;
4340 }
4341 
4342 /*}}}*/
4343 
save_article_to_file(char * defdir,int for_decoding)4344 static char *save_article_to_file (char *defdir, int for_decoding) /*{{{*/
4345 {
4346    char file[SLRL_DISPLAY_BUFFER_SIZE];
4347    char name[SLRN_MAX_PATH_LEN];
4348    char *input_string;
4349    int save_tagged = 0;
4350    int save_thread = 0;
4351    int save_simple;
4352    FILE *fp;
4353 
4354    if (-1 == slrn_check_batch ())
4355      return NULL;
4356 
4357    if (Num_Tag_List.len)
4358      {
4359 	save_tagged = slrn_get_yesno_cancel (1,"%s",_("Save tagged articles"));
4360 	if (save_tagged < 0) return NULL;
4361      }
4362 
4363    if ((save_tagged == 0)
4364        && (Slrn_Current_Header->child != NULL)
4365        && (Slrn_Current_Header->child->flags & HEADER_HIDDEN))
4366      {
4367 	save_thread = slrn_get_yesno_cancel (1,"%s",_("Save this thread"));
4368 	if (save_thread == -1) return NULL;
4369      }
4370 
4371    save_simple = !(save_tagged || save_thread);
4372 
4373    if (*Output_Filename == 0)
4374      {
4375 #ifdef VMS
4376 	char *p;
4377 #endif
4378 	char *filename = Slrn_Current_Group_Name;
4379 #if !defined(VMS) && !defined(IBMPC_SYSTEM)
4380 	unsigned int defdir_len;
4381 #endif
4382 	if (defdir == NULL) defdir = "News";
4383 
4384 	slrn_make_home_dirname (defdir, file, sizeof (file));
4385 
4386 #if !defined(VMS) && !defined(IBMPC_SYSTEM)
4387 	defdir_len = strlen (file);
4388 #endif
4389 	switch (slrn_file_exists (file))
4390 	  {
4391 	   case 0:
4392 	     if (slrn_get_yesno (1, _("Do you want to create directory %s"), file) &&
4393 		 (-1 == slrn_mkdir (file)))
4394 	       {
4395 		  slrn_error_now (2, _("Unable to create directory. (errno = %d)"), errno);
4396 		  slrn_clear_message ();
4397 	       }
4398 	     break;
4399 	   case 1:
4400 	     slrn_error_now (2, _("Warning: %s is a regular file."), file);
4401 	     slrn_clear_message ();
4402 	  }
4403 
4404 #ifdef VMS
4405 	slrn_snprintf (name, sizeof (name) - 5, "%s/%s", file,
4406 		       Slrn_Current_Group_Name);
4407 	p = name + defdir_len + 1;
4408 	while (*p != 0)
4409 	  {
4410 	     if (*p == '.') *p = '_';
4411 	     p++;
4412 	  }
4413 	strcpy (p, ".txt"); /* safe */
4414 #else
4415 	if ((slrn_run_hooks (HOOK_MAKE_SAVE_FILENAME, 0) <= 0)
4416 	    || (0 != SLang_pop_slstring (&filename))
4417 	    || (*filename == 0))
4418 	  filename = Slrn_Current_Group_Name;
4419 	if ((filename == Slrn_Current_Group_Name) ||
4420 	    (0 == slrn_is_absolute_path(filename)))
4421 	  slrn_snprintf (name, sizeof (name), "%s/%s", file,
4422 			 filename);
4423 	else
4424 	  slrn_strncpy (name, filename, sizeof (name));
4425 #endif
4426 
4427 #if !defined(VMS) && !defined(IBMPC_SYSTEM)
4428 	if (filename == Slrn_Current_Group_Name)
4429 	  {
4430 	     /* Uppercase first letter and see if it exists. */
4431 	     name[defdir_len + 1] = UPPER_CASE(name[defdir_len + 1]);
4432 	  }
4433 #endif
4434 	slrn_make_home_filename (name, file, sizeof (file));
4435 
4436 	if (filename == Slrn_Current_Group_Name)
4437 	  {
4438 #if !defined(VMS) && !defined(IBMPC_SYSTEM)
4439 	     if (1 != slrn_file_exists (file))
4440 	       {
4441 		  /* Uppercase version does not exist so use lowercase form. */
4442 		  name[defdir_len + 1] = LOWER_CASE(name[defdir_len + 1]);
4443 		  slrn_make_home_filename (name, file, sizeof (file));
4444 	       }
4445 #endif
4446 	  }
4447 	else
4448 	  SLang_free_slstring (filename);
4449      }
4450    else slrn_strncpy (file, Output_Filename, sizeof (file));
4451 
4452    if (for_decoding) input_string = _("Temporary file (^G aborts): ");
4453    else input_string = _("Save to file (^G aborts): ");
4454    if (slrn_read_filename (input_string, NULL, file, 1, -1) <= 0)
4455      {
4456 	slrn_error (_("Aborted."));
4457 	return NULL;
4458      }
4459 
4460    if (NULL == (fp = fopen (file, "a")))
4461      {
4462 	slrn_error (_("Unable to open %s"), file);
4463 	return NULL;
4464      }
4465 
4466    slrn_strncpy (Output_Filename, file, sizeof (Output_Filename));
4467 
4468    if (save_simple)
4469      {
4470 	Slrn_Header_Type *h;
4471 
4472 	if (NULL != (h = affected_header ()))
4473 	  save_article_as_unix_mail (h, fp);
4474      }
4475    else if (save_tagged)
4476      {
4477 	unsigned int i;
4478 	unsigned int num_saved = 0;
4479 
4480 	for (i = 0; i < Num_Tag_List.len; i++)
4481 	  {
4482 	     if (-1 == save_article_as_unix_mail (Num_Tag_List.headers[i], fp))
4483 	       {
4484 		  slrn_smg_refresh ();
4485 		  if (SLang_get_error() == SL_USER_BREAK)
4486 		    break;
4487 
4488 		  SLang_set_error (0);
4489 		  (void) SLang_input_pending (5);   /* half second delay */
4490 		  slrn_clear_message ();
4491 	       }
4492 	     else num_saved++;
4493 	  }
4494 	if (num_saved == 0)
4495 	  {
4496 	     slrn_fclose (fp);
4497 	     return NULL;
4498 	  }
4499      }
4500    else
4501      {
4502 	Slrn_Header_Type *h = Slrn_Current_Header;
4503 	unsigned int num_saved = 0;
4504 	do
4505 	  {
4506 	     if (-1 == save_article_as_unix_mail (h, fp))
4507 	       {
4508 		  slrn_smg_refresh ();
4509 		  SLang_set_error (0);
4510 		  (void) SLang_input_pending (5);   /* half second delay */
4511 	       }
4512 	     else num_saved++;
4513 
4514 	     h = h->next;
4515 	  }
4516 	while ((h != NULL) && (h->parent != NULL));
4517 	if (num_saved == 0)
4518 	  {
4519 	     slrn_fclose (fp);
4520 	     return NULL;
4521 	  }
4522      }
4523    slrn_fclose (fp);
4524 
4525    if (SLang_get_error ()) return NULL;
4526 
4527    return Output_Filename;
4528 }
4529 
4530 /*}}}*/
4531 
save_article(void)4532 static void save_article (void) /*{{{*/
4533 {
4534    (void) save_article_to_file (Slrn_Save_Directory, 0);
4535 }
4536 
4537 /*}}}*/
4538 
4539 #if SLRN_HAS_DECODE
4540 #if SLRN_HAS_UUDEVIEW
the_uudeview_busy_callback(void * param,uuprogress * progress)4541 static int the_uudeview_busy_callback (void *param, uuprogress *progress)/*{{{*/
4542 {
4543    char stuff[26];
4544    unsigned int count, count_max;
4545    int pcts;
4546    char *ptr;
4547 
4548    if (progress->action != UUACT_DECODING)
4549      return 0;
4550 
4551    pcts = (int)((100 * progress->partno + progress->percent - 100) / progress->numparts);
4552 
4553    count_max = sizeof (stuff) - 1;
4554 
4555    for (count = 0; count < count_max; count++)
4556      stuff[count] = (count < pcts/4) ? '#' : '.';
4557 
4558    stuff [count_max] = 0;
4559 
4560    slrn_message_now (_("decoding %10s (%3d/%3d) %s"),
4561 		     progress->curfile,
4562 		     progress->partno, progress->numparts,
4563 		     stuff);
4564    return 0;
4565 }
4566 
4567 /*}}}*/
4568 
do_slrn_uudeview(char * uu_dir,char * file)4569 static int do_slrn_uudeview (char *uu_dir, char *file)/*{{{*/
4570 {
4571    uulist *item;
4572    char where [SLRN_MAX_PATH_LEN];
4573    int i, ret;
4574 
4575    slrn_make_home_dirname (uu_dir, where, sizeof (where));
4576    /* this is expecting a '/' at the end...so we put one there */
4577    if (strlen (where) + 1 >= sizeof (where))
4578      slrn_error (_("Filename buffer not large enough."));
4579 
4580    strcat (where, "/"); /* safe */
4581 
4582    slrn_message_now (_("Calling uudeview ..."));
4583    ret = UUInitialize ();
4584    ret = UUSetBusyCallback (NULL, the_uudeview_busy_callback, 100);
4585    ret = UUSetOption (UUOPT_DESPERATE, 1, NULL);
4586    ret = UUSetOption (UUOPT_SAVEPATH, 0, where);
4587    if (UURET_OK != (ret = UULoadFile (file, NULL, 0)))
4588      {
4589 	/* Not all systems have strerror... */
4590 	if (ret == UURET_IOERR)
4591 	  slrn_error (_("could not load %s: errno = %d"),
4592 		      file, UUGetOption (UUOPT_ERRNO, NULL, NULL, 0));
4593 	else
4594 	  slrn_error (_("could not load %s: %s"),  file, UUstrerror (ret));
4595      }
4596 
4597    i = 0;
4598    while (NULL != (item = UUGetFileListItem (i)))
4599      {
4600 	i++;
4601 
4602 	/* When decoding yEnc messages, uudeview inserts bogus items into the
4603 	 * file list. Processing them should not trigger an error, so we need
4604 	 * to ignore errors where no data was found. */
4605 	ret = UUDecodeFile (item, NULL);
4606 	if ((ret != UURET_OK) && (ret != UURET_NODATA))
4607 	  {
4608 	     char *f;
4609 	     char *err;
4610 
4611 	     if (NULL == (f = item->filename)) f = "oops";
4612 
4613 	     if (ret == UURET_IOERR) err = _("I/O error.");
4614 	     else err = UUstrerror (ret);
4615 
4616 	     slrn_error (_("error decoding %s: %s"), f, err);
4617 	  }
4618      }
4619 
4620    UUCleanUp ();
4621 }
4622 
4623 /*}}}*/
4624 #endif
4625 
decode_article(void)4626 static void decode_article (void) /*{{{*/
4627 {
4628    char *uu_dir;
4629    char *file;
4630 
4631    if (NULL == (uu_dir = Slrn_Decode_Directory))
4632      {
4633 	if (NULL == (uu_dir = Slrn_Save_Directory))
4634 	  uu_dir = "News";
4635      }
4636    else *Output_Filename = 0;	       /* force it to use this directory */
4637 
4638    file = save_article_to_file(uu_dir, 1);
4639 
4640    if (file == NULL) return;
4641 
4642    if (1 == slrn_get_yesno (1, _("Decode %s"), file))
4643      {
4644 # if SLRN_HAS_UUDEVIEW
4645 	if (Slrn_Use_Uudeview)
4646 	  (void) do_slrn_uudeview (uu_dir, file);
4647 	else
4648 # endif
4649 	(void) slrn_uudecode_file (file, NULL, 0, NULL);
4650 
4651 	if (0 == SLang_get_error ())
4652 	  {
4653 	     if (1 == slrn_get_yesno (1, _("Delete %s"), file))
4654 	       {
4655 		  if (-1 == slrn_delete_file (file))
4656 		    slrn_error (_("Unable to delete %s"), file);
4657 	       }
4658 	  }
4659      }
4660    /* Since we have a decode directory, do not bother saving this */
4661    if (NULL != Slrn_Decode_Directory)
4662      *Output_Filename = 0;
4663 }
4664 
4665 /*}}}*/
4666 #endif  /* SLRN_HAS_DECODE */
4667 
4668 /*}}}*/
4669 /*{{{ pipe_article functions */
4670 
slrn_pipe_article_to_cmd(char * cmd)4671 int slrn_pipe_article_to_cmd (char *cmd) /*{{{*/
4672 {
4673 #if SLRN_HAS_PIPING
4674    FILE *fp;
4675    int retval;
4676    int convert=0;
4677    Slrn_Article_Line_Type *lines;
4678 
4679    if (-1 == (retval = select_affected_article ()))
4680      return -1;
4681    else if (retval == 1)
4682      select_header (Header_Showing, 0);
4683 
4684    switch (Slrn_Pipe_Type)
4685      {
4686       default:
4687       case PIPE_RAW:
4688 	lines = Slrn_Current_Article->raw_lines;
4689 	convert=0;
4690 	break;
4691       case PIPE_DECODED:
4692 	return -1;
4693 
4694       case PIPE_CONVERTED:
4695 	lines = Slrn_Current_Article->lines;
4696 	convert = 1;
4697 	break;
4698      }
4699 
4700    if (NULL == (fp = slrn_popen (cmd, "w")))
4701      {
4702 	slrn_error (_("Unable to open pipe to %s"), cmd);
4703 	retval = -1;
4704      }
4705    else
4706      {
4707 	retval = write_article_line (lines, fp, convert);
4708 	slrn_pclose (fp);
4709      }
4710 
4711    return retval;
4712 #else
4713    slrn_error (_("Piping not implemented on this system."));
4714    return -1;
4715 #endif
4716 }
4717 
4718 /*}}}*/
4719 
pipe_article(void)4720 static void pipe_article (void) /*{{{*/
4721 {
4722 #if SLRN_HAS_PIPING
4723    static char cmd[SLRL_DISPLAY_BUFFER_SIZE];
4724 
4725    if (slrn_read_filename (_("Pipe to command: "), NULL, cmd, 1, -1) <= 0)
4726      {
4727 	slrn_error (_("Aborted.  Command name is required."));
4728 	return;
4729      }
4730 
4731    if (-1 == slrn_pipe_article_to_cmd (cmd))
4732      slrn_message (_("Error piping to %s."), cmd);
4733 #else
4734    slrn_error (_("Piping not implemented on this system."));
4735 #endif
4736 }
4737 
4738 /*}}}*/
4739 
print_article(int full)4740 static int print_article (int full) /*{{{*/
4741 {
4742    Slrn_Print_Type *p;
4743    Slrn_Article_Line_Type *l;
4744    int was_wrapped = 0;
4745 
4746    if (-1 == select_affected_article ()) return -1;
4747 
4748    slrn_message_now (_("Printing article..."));
4749 
4750    p = slrn_open_printer ();
4751    if (p == NULL)
4752      return -1;
4753 
4754    if (full && (was_wrapped = (Slrn_Wrap_Method && Slrn_Current_Article->is_wrapped)))
4755      _slrn_art_unwrap_article (Slrn_Current_Article);
4756    l = Slrn_Current_Article->lines;
4757 
4758    while (l != NULL)
4759      {
4760 	if ((full || (0 == (l->flags & HIDDEN_LINE))) &&
4761 	    ((-1 == slrn_write_to_printer (p, l->buf, strlen (l->buf)))
4762 	     || (-1 == slrn_write_to_printer (p, "\n", 1))))
4763 	  {
4764 	     if (was_wrapped)
4765 	       _slrn_art_wrap_article (Slrn_Current_Article);
4766 	     slrn_close_printer (p);
4767 	     return -1;
4768 	  }
4769 
4770 	l = l->next;
4771      }
4772 
4773    if (was_wrapped)
4774      _slrn_art_wrap_article (Slrn_Current_Article);
4775 
4776    if (-1 == slrn_close_printer (p))
4777      return -1;
4778 
4779    slrn_message_now (_("Printing article...done"));
4780    return 0;
4781 }
4782 
4783 /*}}}*/
4784 
print_article_cmd(void)4785 static void print_article_cmd (void) /*{{{*/
4786 {
4787    int prefix = 0;
4788    if (Slrn_Prefix_Arg_Ptr != NULL)
4789      {
4790 	prefix = *Slrn_Prefix_Arg_Ptr;
4791 	Slrn_Prefix_Arg_Ptr = NULL;
4792      }
4793 
4794    if ((Slrn_User_Wants_Confirmation & SLRN_CONFIRM_PRINT)
4795        && (slrn_get_yesno (1, _("Are you sure you want to print the article")) == 0))
4796      return;
4797 
4798    (void) print_article (prefix);
4799 }
4800 
4801 /*}}}*/
4802 
4803 /*}}}*/
4804 
4805 /*{{{ Thread related functions */
4806 
find_non_hidden_header(void)4807 static void find_non_hidden_header (void) /*{{{*/
4808 {
4809    Slrn_Header_Type *h = Slrn_Current_Header;
4810 
4811    while ((h != NULL) && (h->flags & HEADER_HIDDEN))
4812      h = h->prev;
4813 
4814    if (h == NULL)
4815      {
4816 	h = Slrn_Current_Header;
4817 	while ((h != NULL) && (h->flags & HEADER_HIDDEN))
4818 	  h = h->next;
4819      }
4820 
4821    Slrn_Current_Header = h;
4822 }
4823 
4824 /*}}}*/
4825 
4826 /* This function cannot depend upon routines which call SLscroll functions if
4827  * sync_now is non-zero.
4828  */
slrn_collapse_threads(int sync_now)4829 void slrn_collapse_threads (int sync_now) /*{{{*/
4830 {
4831    Slrn_Header_Type *h = Slrn_First_Header;
4832 
4833    if ((h == NULL)
4834        || (_art_Threads_Collapsed == 1))
4835      return;
4836 
4837    while (h != NULL)
4838      {
4839 	if (h->parent != NULL) h->flags |= HEADER_HIDDEN;
4840 	else
4841 	  {
4842 	     h->flags &= ~HEADER_HIDDEN;
4843 	  }
4844 	h = h->real_next;
4845      }
4846 
4847    find_non_hidden_header ();
4848 
4849    if (sync_now) _art_find_header_line_num ();
4850 
4851    Slrn_Full_Screen_Update = 1;
4852    _art_Threads_Collapsed = 1;
4853 }
4854 
4855 /*}}}*/
4856 
slrn_uncollapse_threads(int sync_now)4857 void slrn_uncollapse_threads (int sync_now) /*{{{*/
4858 {
4859    Slrn_Header_Type *h = Slrn_First_Header;
4860 
4861    if ((h == NULL)
4862        || (0 == _art_Threads_Collapsed))
4863      return;
4864 
4865    while (h != NULL)
4866      {
4867 	h->flags &= ~HEADER_HIDDEN;
4868 	h = h->real_next;
4869      }
4870    Slrn_Full_Screen_Update = 1;
4871    _art_Threads_Collapsed = 0;
4872    if (sync_now) _art_find_header_line_num ();
4873 }
4874 
4875 /*}}}*/
4876 
uncollapse_header(Slrn_Header_Type * h)4877 static void uncollapse_header (Slrn_Header_Type *h) /*{{{*/
4878 {
4879    h->flags &= ~HEADER_HIDDEN;
4880 }
4881 
4882 /*}}}*/
4883 
collapse_header(Slrn_Header_Type * h)4884 static void collapse_header (Slrn_Header_Type *h) /*{{{*/
4885 {
4886    h->flags |= HEADER_HIDDEN;
4887 }
4888 
4889 /*}}}*/
4890 
for_this_tree(Slrn_Header_Type * h,void (* f)(Slrn_Header_Type *))4891 static void for_this_tree (Slrn_Header_Type *h, void (*f)(Slrn_Header_Type *)) /*{{{*/
4892 {
4893    Slrn_Header_Type *child = h->child;
4894    while (child != NULL)
4895      {
4896 	for_this_tree (child, f);
4897 	child = child->sister;
4898      }
4899    (*f) (h);
4900    Slrn_Full_Screen_Update = 1;
4901 }
4902 
4903 /*}}}*/
4904 
for_this_family(Slrn_Header_Type * h,void (* f)(Slrn_Header_Type *))4905 static void for_this_family (Slrn_Header_Type *h, void (*f)(Slrn_Header_Type *)) /*{{{*/
4906 {
4907    while (h != NULL)
4908      {
4909 	for_this_tree (h, f);
4910 	h = h->sister;
4911      }
4912 }
4913 
4914 /*}}}*/
4915 
4916 /* For efficiency, these functions only sync their own changes if sync_linenum
4917  * is non-zero, so do not expect them to clean up a "dirty" window */
slrn_uncollapse_this_thread(Slrn_Header_Type * h,int sync_linenum)4918 void slrn_uncollapse_this_thread (Slrn_Header_Type *h, int sync_linenum) /*{{{*/
4919 {
4920    Slrn_Header_Type *child;
4921    int back = 0;
4922 
4923    /* if (_art_Threads_Collapsed == 0) return; */
4924 
4925    while ((h->parent != NULL) && (h->prev != NULL))
4926      {
4927 	h = h->prev;
4928 	back++;
4929      }
4930    if ((child = h->child) == NULL) return;
4931    if (0 == (child->flags & HEADER_HIDDEN)) return;
4932 
4933    for_this_family (child, uncollapse_header);
4934 
4935    if (sync_linenum)
4936      {
4937 	Slrn_Full_Screen_Update = 1;
4938 	Slrn_Current_Header = h;
4939 	Slrn_Header_Window.current_line = (SLscroll_Type *) h;
4940 	/* SLscroll_find_line_num scales poorly on large groups,
4941 	 * so we update this information ourselves: */
4942 	Slrn_Header_Window.line_num -= back;
4943 	Slrn_Header_Window.num_lines += h->num_children;
4944      }
4945 
4946    _art_Threads_Collapsed = -1;	       /* uncertain */
4947 }
4948 
4949 /*}}}*/
4950 
slrn_collapse_this_thread(Slrn_Header_Type * h,int sync_linenum)4951 void slrn_collapse_this_thread (Slrn_Header_Type *h, int sync_linenum) /*{{{*/
4952 {
4953    Slrn_Header_Type *child;
4954    int back = 0;
4955 
4956    /* if (_art_Threads_Collapsed == 1) return; */
4957 
4958    while ((h->parent != NULL) && (h->prev != NULL))
4959      {
4960 	h = h->prev;
4961 	back++;
4962      }
4963 
4964    if ((child = h->child) == NULL) return;
4965    if (child->flags & HEADER_HIDDEN) return;
4966 
4967    for_this_family (child, collapse_header);
4968 
4969    if (sync_linenum)
4970      {
4971 	Slrn_Full_Screen_Update = 1;
4972 	Slrn_Current_Header = h;
4973 	Slrn_Header_Window.current_line = (SLscroll_Type *) h;
4974 	Slrn_Header_Window.line_num -= back;
4975 	Slrn_Header_Window.num_lines -= h->num_children;
4976      }
4977 
4978    _art_Threads_Collapsed = -1;	       /* uncertain */
4979 }
4980 
4981 /*}}}*/
4982 
toggle_collapse_threads(void)4983 static void toggle_collapse_threads (void) /*{{{*/
4984 {
4985    if (Slrn_Prefix_Arg_Ptr != NULL)
4986      {
4987 	if (_art_Threads_Collapsed == 1)
4988 	  {
4989 	     slrn_uncollapse_threads (0);
4990 	  }
4991 	else slrn_collapse_threads (0);
4992 	Slrn_Prefix_Arg_Ptr = NULL;
4993      }
4994    else
4995      {
4996 	if (0 == slrn_is_thread_collapsed (Slrn_Current_Header))
4997 	  slrn_collapse_this_thread (Slrn_Current_Header, 0);
4998 	else
4999 	  slrn_uncollapse_this_thread (Slrn_Current_Header, 0);
5000 
5001 	find_non_hidden_header ();
5002      }
5003    _art_find_header_line_num ();
5004 }
5005 
5006 /*}}}*/
5007 
slrn_thread_size(Slrn_Header_Type * h)5008 unsigned int slrn_thread_size (Slrn_Header_Type *h)
5009 {
5010    if (h == NULL) return 0;
5011    return 1 + h->num_children;
5012 }
5013 
slrn_is_thread_collapsed(Slrn_Header_Type * h)5014 int slrn_is_thread_collapsed (Slrn_Header_Type *h)
5015 {
5016    if (h == NULL) return 1;
5017    while (h->parent != NULL) h = h->parent;
5018    if (h->child == NULL) return 0;
5019    return (h->child->flags & HEADER_HIDDEN);
5020 }
5021 
5022 /*}}}*/
5023 
5024 /*{{{ select_article */
5025 
5026 /* returns 0 if article selected, -1 if something went wrong or 1 if article
5027  * already selected.
5028  */
select_article(int check_mime)5029 static int select_article (int check_mime) /*{{{*/
5030 {
5031    int ret = 1;
5032 
5033    slrn_uncollapse_this_thread (Slrn_Current_Header, 1);
5034 
5035    if (Slrn_Current_Header != Header_Showing)
5036      ret = 0;
5037 
5038    if (((ret == 0) || check_mime) &&
5039        select_header (Slrn_Current_Header, Slrn_Del_Article_Upon_Read) < 0)
5040      return -1;
5041 
5042    if ((0 == ret) && Slrn_Current_Article->mime.needs_metamail)
5043      {
5044 	if (slrn_mime_call_metamail ())
5045 	  return -1;
5046      }
5047 
5048    /* The article gets synced here */
5049    set_article_visibility (1);
5050    return ret;
5051 }
5052 
5053 /*}}}*/
5054 
5055 /*}}}*/
5056 
5057 /*{{{ mark_spot and exchange_mark */
5058 
mark_spot(void)5059 static void mark_spot (void) /*{{{*/
5060 {
5061    Mark_Header = Slrn_Current_Header;
5062    slrn_message (_("Mark set."));
5063 }
5064 
5065 /*}}}*/
5066 
exchange_mark(void)5067 static void exchange_mark (void) /*{{{*/
5068 {
5069    Slrn_Header_Type *h;
5070 
5071    if ((h = Mark_Header) == NULL)
5072      {
5073 	slrn_error (_("Mark not set."));
5074 	return;
5075      }
5076 
5077    mark_spot ();
5078    slrn_goto_header (h, 0);
5079 }
5080 
5081 /*}}}*/
5082 
5083 /*}}}*/
5084 /*{{{ subject/author header searching commands */
5085 
header_generic_search(int dir,int type)5086 static void header_generic_search (int dir, int type) /*{{{*/
5087 {
5088    static char search_str[SLRL_DISPLAY_BUFFER_SIZE];
5089 #if SLANG_VERSION < 20000
5090    SLsearch_Type st;
5091 #else
5092    SLsearch_Type *st;
5093    unsigned int flags = SLSEARCH_CASELESS;
5094 #endif
5095    Slrn_Header_Type *l;
5096    char* prompt;
5097    int ret;
5098 
5099    prompt = slrn_strdup_strcat ((type == 's' ? _("Subject search ") : _("Author search ")),
5100 				(dir > 0 ? _("(forward)") : _("(backward)")), ": ",
5101 				NULL);
5102 
5103    ret = slrn_read_input (prompt, search_str, NULL, 0, 0);
5104    slrn_free (prompt);
5105    if (ret <= 0) return;
5106 
5107 #if SLANG_VERSION < 20000
5108    SLsearch_init (search_str, 1, 0, &st);
5109 #else
5110    if (Slrn_UTF8_Mode) flags |= SLSEARCH_UTF8;
5111    st = SLsearch_new ((SLuchar_Type *)search_str, flags);
5112    if (NULL == st)
5113      return;
5114 #endif
5115 
5116    if (dir > 0) l = Slrn_Current_Header->next;
5117    else l = Slrn_Current_Header->prev;
5118 
5119    while (l != NULL)
5120      {
5121 	if (type == 's')
5122 	  {
5123 	     if ((l->subject != NULL)
5124 #if SLANG_VERSION < 20000
5125 		 && (NULL != SLsearch ((unsigned char *) l->subject,
5126 				       (unsigned char *) l->subject + strlen (l->subject),
5127 				       &st))
5128 #else
5129 		 && (NULL != SLsearch_forward (st, (SLuchar_Type *) l->subject,
5130 					       (SLuchar_Type *) l->subject + strlen (l->subject)))
5131 #endif
5132 		     )
5133 	       break;
5134 	  }
5135 	else if ((l->from != NULL)
5136 #if SLANG_VERSION < 20000
5137 		 && (NULL != SLsearch ((unsigned char *) l->from,
5138 				       (unsigned char *) l->from + strlen (l->from),
5139 				       &st))
5140 #else
5141 		 && (NULL != SLsearch_forward (st, (SLuchar_Type *) l->from,
5142 					       (SLuchar_Type *) l->from + strlen (l->from)))
5143 #endif
5144 		     )
5145 	  break;
5146 
5147 	if (dir > 0) l = l->next; else l = l->prev;
5148      }
5149 
5150 #if SLANG_VERSION >= 20000
5151    SLsearch_delete (st);
5152 #endif
5153    if (l == NULL)
5154      {
5155 	slrn_error (_("Not found."));
5156 	return;
5157      }
5158 
5159    if (l->flags & HEADER_HIDDEN) slrn_uncollapse_this_thread (l, 0);
5160    Slrn_Current_Header = l;
5161    _art_find_header_line_num ();
5162 }
5163 
5164 /*}}}*/
5165 
subject_search_forward(void)5166 static void subject_search_forward (void) /*{{{*/
5167 {
5168    header_generic_search (1, 's');
5169 }
5170 
5171 /*}}}*/
5172 
subject_search_backward(void)5173 static void subject_search_backward (void) /*{{{*/
5174 {
5175    header_generic_search (-1, 's');
5176 }
5177 
5178 /*}}}*/
5179 
author_search_forward(void)5180 static void author_search_forward (void) /*{{{*/
5181 {
5182    header_generic_search (1, 'a');
5183 }
5184 
5185 /*}}}*/
5186 
author_search_backward(void)5187 static void author_search_backward (void) /*{{{*/
5188 {
5189    header_generic_search (-1, 'a');
5190 }
5191 
5192 /*}}}*/
5193 
5194 /*}}}*/
5195 
5196 /*{{{ score header support */
5197 
5198 /*{{{ kill list functions */
5199 
5200 typedef struct Kill_List_Type /*{{{*/
5201 {
5202 #define MAX_DKILLS 50
5203    NNTP_Artnum_Type range_min[MAX_DKILLS];
5204    NNTP_Artnum_Type range_max[MAX_DKILLS];
5205    unsigned int active_index;
5206    struct Kill_List_Type *next;
5207 }
5208 
5209 /*}}}*/
5210 
5211 Kill_List_Type;
5212 
5213 static Kill_List_Type *Kill_List;
5214 static Kill_List_Type *Missing_Article_List;
5215 
add_to_specified_kill_list(NNTP_Artnum_Type num,Kill_List_Type * root)5216 static Kill_List_Type *add_to_specified_kill_list (NNTP_Artnum_Type num, Kill_List_Type *root) /*{{{*/
5217 {
5218    unsigned int active_index;
5219 
5220    if (num < 0) return root;
5221 
5222    if (root != NULL)
5223      {
5224 	active_index = root->active_index;
5225 	if (num == root->range_max[active_index] + 1)
5226 	  {
5227 	     root->range_max[active_index] = num;   /* extend range */
5228 	     return root;
5229 	  }
5230 	active_index++;		       /* need new range */
5231      }
5232    else active_index = MAX_DKILLS;
5233 
5234    if (active_index == MAX_DKILLS)
5235      {
5236 	Kill_List_Type *k;
5237 	k = (Kill_List_Type *) SLMALLOC (sizeof (Kill_List_Type));
5238 	if (k == NULL) return root;
5239 	k->next = root;
5240 	root = k;
5241 	active_index = 0;
5242      }
5243 
5244    root->active_index = active_index;
5245    root->range_min[active_index] = root->range_max[active_index] = num;
5246    return root;
5247 }
5248 
5249 /*}}}*/
5250 
add_to_kill_list(NNTP_Artnum_Type num)5251 static void add_to_kill_list (NNTP_Artnum_Type num) /*{{{*/
5252 {
5253    Kill_List = add_to_specified_kill_list (num, Kill_List);
5254    Number_Killed++;
5255 }
5256 
5257 /*}}}*/
5258 
add_to_missing_article_list(NNTP_Artnum_Type num)5259 static void add_to_missing_article_list (NNTP_Artnum_Type num) /*{{{*/
5260 {
5261    Missing_Article_List = add_to_specified_kill_list (num, Missing_Article_List);
5262 }
5263 
5264 /*}}}*/
5265 
free_specific_kill_list_and_update(Kill_List_Type * k)5266 static void free_specific_kill_list_and_update (Kill_List_Type *k) /*{{{*/
5267 {
5268    while (k != NULL)
5269      {
5270 	Kill_List_Type *next = k->next;
5271 	unsigned int i, imax = k->active_index;
5272 	NNTP_Artnum_Type *mins = k->range_min;
5273 	NNTP_Artnum_Type *maxs = k->range_max;
5274 
5275 	if (User_Aborted_Group_Read == 0)
5276 	  {
5277 	     for (i = 0; i <= imax; i++)
5278 	       {
5279 		  slrn_mark_articles_as_read (NULL, mins[i], maxs[i]);
5280 	       }
5281 	  }
5282 	SLFREE (k);
5283 	k = next;
5284      }
5285 }
5286 
5287 /*}}}*/
5288 
free_kill_lists_and_update(void)5289 static void free_kill_lists_and_update (void) /*{{{*/
5290 {
5291    free_specific_kill_list_and_update (Kill_List);
5292    Kill_List = NULL;
5293    Number_Killed = 0;
5294    free_specific_kill_list_and_update (Missing_Article_List);
5295    Missing_Article_List = NULL;
5296 }
5297 
5298 /*}}}*/
5299 
5300 /*}}}*/
5301 
slrn_set_header_score(Slrn_Header_Type * h,int score,int apply_kill,Slrn_Score_Debug_Info_Type * sdi)5302 Slrn_Header_Type *slrn_set_header_score (Slrn_Header_Type *h,
5303 					 int score, int apply_kill,
5304 					 Slrn_Score_Debug_Info_Type *sdi)
5305 {
5306    if (h == NULL) return NULL;
5307 
5308    if (h->flags & HEADER_HIGH_SCORE)
5309      Number_High_Scored--;
5310    if (h->flags & HEADER_LOW_SCORE)
5311      Number_Low_Scored--;
5312 
5313    h->flags &= ~(HEADER_HIGH_SCORE|HEADER_LOW_SCORE);
5314 
5315    if (score >= Slrn_High_Score_Min)
5316      {
5317 	h->flags |= HEADER_HIGH_SCORE;
5318 	Number_High_Scored++;
5319      }
5320    else if (score < Slrn_Low_Score_Max)
5321      {
5322 	if ((score <= Slrn_Kill_Score_Max) && apply_kill)
5323 	  {
5324 	     NNTP_Artnum_Type number = h->number;
5325 	     if (Slrn_Kill_Log_FP != NULL)
5326 	       {
5327 		  Slrn_Score_Debug_Info_Type *hlp = sdi;
5328 		  fprintf (Slrn_Kill_Log_FP, _("Score %d killed article %s\n"), score, h->msgid);
5329 		  while (hlp != NULL)
5330 		    {
5331 		       if (hlp->description [0] != 0)
5332 			 fprintf (Slrn_Kill_Log_FP, _(" Score %c%5i: %s (%s:%i)\n"),
5333 				  (hlp->stop_here ? '=' : ' '), hlp->score,
5334 				  hlp->description, hlp->filename, hlp->linenumber);
5335 		       else
5336 			 fprintf (Slrn_Kill_Log_FP, _(" Score %c%5i: %s:%i\n"),
5337 				  (hlp->stop_here ? '=' : ' '), hlp->score,
5338 				  hlp->filename, hlp->linenumber);
5339 		       hlp = hlp->next;
5340 		    }
5341 		  fprintf (Slrn_Kill_Log_FP, _("  Newsgroup: %s\n  From: %s\n  Subject: %s\n\n"),
5342 			   Slrn_Current_Group_Name, h->from, h->subject);
5343 	       }
5344 	     free_killed_header (h);
5345 	     add_to_kill_list (number);
5346 	     Number_Score_Killed++;
5347 	     h = NULL;
5348 	     goto free_and_return;
5349 	  }
5350 
5351 	if (0 == (h->flags & HEADER_DONT_DELETE_MASK))
5352 	  {
5353 	     if (0 == (h->flags & HEADER_READ))
5354 	       Number_Read++;
5355 	     h->flags |= (HEADER_READ | HEADER_LOW_SCORE);
5356 	  }
5357 	else
5358 	  h->flags |= HEADER_LOW_SCORE;
5359 	Number_Low_Scored++;
5360 	/* The next line should be made configurable */
5361 	kill_cross_references (h);
5362      }
5363    h->thread_score = h->score = score;
5364 
5365    free_and_return:
5366    while (sdi != NULL)
5367      {
5368 	Slrn_Score_Debug_Info_Type *hlp = sdi->next;
5369 	slrn_free ((char*)sdi);
5370 	sdi = hlp;
5371      }
5372    return h;
5373 }
5374 
5375 /*{{{ apply_score */
apply_score(Slrn_Header_Type * h,int apply_kill)5376 static Slrn_Header_Type *apply_score (Slrn_Header_Type *h, int apply_kill) /*{{{*/
5377 {
5378    int score;
5379    Slrn_Score_Debug_Info_Type *sdi = NULL;
5380 
5381    if ((h == NULL) || (-1 == h->number)) return h;
5382 
5383    if (Slrn_Apply_Score && Perform_Scoring)
5384      score = slrn_score_header (h, Slrn_Current_Group_Name,
5385 				((Slrn_Kill_Log_FP != NULL) &&
5386 				 apply_kill) ? &sdi : NULL);
5387    else score = 0;
5388 
5389    return slrn_set_header_score (h, score, apply_kill, sdi);
5390 }
5391 
5392 /*}}}*/
5393 
5394 /*}}}*/
5395 
5396 /*{{{ score_headers */
score_headers(int apply_kill)5397 static void score_headers (int apply_kill) /*{{{*/
5398 {
5399    Slrn_Header_Type *h = Slrn_First_Header;
5400    int percent, last_percent, delta_percent;
5401    int num;
5402 
5403    if ((h == NULL) || (Slrn_Apply_Score == 0)) return;
5404 
5405    /* slrn_set_suspension (1); */
5406 
5407    percent = num = 0;
5408    delta_percent = (30 * 100) / Total_Num_Headers + 1;
5409    last_percent = -delta_percent;
5410 
5411    slrn_message_now (_("Scoring articles ..."));
5412 
5413    while ((h != NULL) && (SLang_get_error() != USER_BREAK))
5414      {
5415 	Slrn_Header_Type *prev, *next;
5416 	prev = h->real_prev;
5417 	next = h->real_next;
5418 
5419 	num++;
5420 	h = apply_score (h, apply_kill);
5421 	percent = (100 * num) / Total_Num_Headers;
5422 	if (percent >= last_percent + delta_percent)
5423 	  {
5424 	     slrn_message_now (_("Scoring articles: %2d%%, Killed: %u, High: %u, Low: %u"),
5425 			       percent, Number_Score_Killed, Number_High_Scored, Number_Low_Scored);
5426 	     last_percent = percent;
5427 	  }
5428 
5429 	if (h == NULL)
5430 	  {
5431 	     num--;
5432 	     if (prev == NULL)
5433 	       Slrn_First_Header = next;
5434 	     else
5435 	       prev->next = prev->real_next = next;
5436 
5437 	     if (next != NULL)
5438 	       next->prev = next->real_prev = prev;
5439 	  }
5440 	h = next;
5441      }
5442    if (SLang_get_error () == USER_BREAK)
5443      {
5444 	slrn_error ("Scoring aborted.");
5445 	SLang_set_error (0);
5446      }
5447    if (apply_kill)
5448      Slrn_Current_Header = _art_Headers = Slrn_First_Header;
5449    /* slrn_set_suspension (0); */
5450 }
5451 
5452 /*}}}*/
5453 
5454 /*}}}*/
5455 
init_scoring(void)5456 static void init_scoring (void)
5457 {
5458    Number_Score_Killed = Number_High_Scored = Number_Low_Scored = 0;
5459 }
5460 
slrn_apply_scores(int apply_now)5461 void slrn_apply_scores (int apply_now) /*{{{*/
5462 {
5463    if ((apply_now == -1) &&
5464        (1 != slrn_get_yesno (1, _("Apply scorefile now"))))
5465      return;
5466 
5467    slrn_close_score ();
5468 
5469    if (-1 == slrn_open_score (Slrn_Current_Group_Name))
5470      return;
5471 
5472    /* high / low scoring counters get updated in slrn_set_header_score */
5473    Number_Score_Killed = 0;
5474    Slrn_Apply_Score = 1;	       /* force even if there are no scores */
5475    Perform_Scoring = 1;
5476    get_missing_headers ();
5477    score_headers (0);
5478    slrn_sort_headers ();
5479    slrn_goto_header (Slrn_Current_Header, 0);
5480 }
5481 
5482 /*}}}*/
5483 
create_score(void)5484 static void create_score (void)
5485 {
5486    Slrn_Header_Type *h;
5487 
5488    if (NULL == (h = affected_header ()))
5489      return;
5490 
5491    if ((Slrn_Batch == 0) &&
5492        (-1 != slrn_edit_score (h, Slrn_Current_Group_Name)))
5493      slrn_apply_scores (-1);
5494 }
5495 
5496 /*}}}*/
5497 
5498 /*{{{ view_scores */
view_scores(void)5499 static void view_scores (void) /*{{{*/
5500 {
5501    int selection, i;
5502    unsigned int scorenum = 0, homelen;
5503    char line [1024], file[SLRN_MAX_PATH_LEN], *home;
5504    char **scores;
5505 
5506    Slrn_Score_Debug_Info_Type *sdi = NULL, *hlp;
5507 
5508    if (!Perform_Scoring)
5509      {
5510 	slrn_message(_("No scorefile loaded."));
5511 	return;
5512      }
5513 
5514    slrn_uncollapse_this_thread (affected_header (), 1);
5515    slrn_score_header (affected_header (), Slrn_Current_Group_Name, &sdi);
5516 
5517    if ((hlp = sdi) == NULL)
5518      {
5519 	slrn_message (_("This article is not matched by any scorefile entries."));
5520 	return;
5521      }
5522 
5523    while (hlp != NULL)
5524      {
5525 	scorenum++;
5526 	hlp = hlp->next;
5527      }
5528 
5529    hlp = sdi;
5530    if ((NULL == (home = getenv ("SLRNHOME"))) &&
5531        (NULL == (home = getenv ("HOME"))))
5532      {
5533 	home = "";
5534 	homelen = 1;
5535      }
5536    else
5537      homelen = strlen (home);
5538 
5539    scores = (char **)slrn_safe_malloc
5540      ((unsigned int)(sizeof (char *) * scorenum));
5541 
5542    i = 0;
5543    while (hlp != NULL)
5544      {
5545 	if (!strncmp (hlp->filename, home, homelen))
5546 	  {
5547 	     *file = '~';
5548 	     slrn_strncpy (file+1, hlp->filename + homelen, sizeof (file) - 1);
5549 	  }
5550 	else
5551 	  slrn_strncpy (file, hlp->filename, sizeof (file));
5552 	if (hlp->description [0] != 0)
5553 	  slrn_snprintf (line, sizeof (line), "%c%5i: %s (%s:%i)",
5554 			 (hlp->stop_here ? '=' : ' '), hlp->score,
5555 			 hlp->description, file, hlp->linenumber);
5556 	else
5557 	  slrn_snprintf (line, sizeof (line), "%c%5i: %s:%i",
5558 			 (hlp->stop_here ? '=' : ' '), hlp->score,
5559 			 file, hlp->linenumber);
5560 	scores [i] = slrn_safe_strmalloc (line);
5561 	i++;
5562 	hlp = hlp->next;
5563      }
5564 
5565    selection = slrn_select_list_mode (_("This article is matched by the following scores"),
5566 				      scorenum, scores, 0, 0, NULL);
5567 
5568    if (selection >= 0)
5569      {
5570 	hlp = sdi;
5571 	for (i = 0; i < selection; i++)
5572 	  hlp = hlp->next;
5573 
5574 	if (slrn_edit_file (Slrn_Editor_Score, (char *) hlp->filename,
5575 			    hlp->linenumber, 0) >= 0)
5576 	  {
5577 	     slrn_make_home_filename (Slrn_Score_File, file, sizeof (file));
5578 
5579 	     if (Slrn_Scorefile_Open != NULL)
5580 	       slrn_free (Slrn_Scorefile_Open);
5581 	     Slrn_Scorefile_Open = slrn_safe_strmalloc (file);
5582 
5583 	     slrn_apply_scores (-1);
5584 	  }
5585      }
5586 
5587    i = 0;
5588    while (sdi != NULL)
5589      {
5590 	hlp = sdi->next;
5591 	slrn_free ((char *)scores [i++]);
5592 	slrn_free ((char *)sdi);
5593 	sdi = hlp;
5594      }
5595    slrn_free ((char *)scores);
5596 }
5597 /*}}}*/
5598 
5599 /*}}}*/
5600 
5601 /*{{{ get headers from server and process_xover */
5602 
process_xover(Slrn_XOver_Type * xov)5603 static Slrn_Header_Type *process_xover (Slrn_XOver_Type *xov)
5604 {
5605    Slrn_Header_Type *h;
5606    int err;
5607 
5608    h = (Slrn_Header_Type *) slrn_safe_malloc (sizeof (Slrn_Header_Type));
5609 
5610    slrn_map_xover_to_header (xov, h, 1);
5611    Number_Total++;
5612 
5613    err = SLang_get_error ();
5614 
5615    if ((-1 == slrn_rfc1522_decode_header ("Subject", &h->subject))
5616        && (err == 0))
5617      {
5618 	h->flags |= HEADER_HAS_PARSE_PROBLEMS;
5619 	slrn_clear_error ();
5620      }
5621 
5622    if ((-1 == slrn_rfc1522_decode_header ("From", &h->from))
5623        && (err == 0))
5624      {
5625 	h->flags |= HEADER_HAS_PARSE_PROBLEMS;
5626 	slrn_clear_error ();
5627      }
5628 
5629    get_header_real_name (h);
5630 
5631 #if SLRN_HAS_GROUPLENS
5632    if (Slrn_Use_Group_Lens)
5633      {
5634 	h->gl_rating = h->gl_pred = -1;
5635      }
5636 #endif
5637    return h;
5638 }
5639 
5640 /*}}}*/
5641 
5642 /*{{{ get_headers from server */
get_add_headers(NNTP_Artnum_Type min,NNTP_Artnum_Type max)5643 static int get_add_headers (NNTP_Artnum_Type min, NNTP_Artnum_Type max) /*{{{*/
5644 {
5645    char *meter_chars = "|/-\\";
5646    static unsigned int last_meter_char;
5647    int reads_per_update, num = 0;
5648 
5649    if ((reads_per_update = Slrn_Reads_Per_Update) < 5)
5650      reads_per_update = 50;
5651 
5652    while (slrn_open_add_xover (min, max) == 1)
5653      {
5654 	Slrn_Header_Type *h;
5655 	Slrn_Header_Line_Type *l;
5656 	NNTP_Artnum_Type this_num;
5657 
5658 	h = Slrn_First_Header;
5659 
5660 	while (-1 != slrn_read_add_xover(&l, &this_num))
5661 	  {
5662 	     if (SLang_get_error () == USER_BREAK)
5663 	       {
5664 		  if (Slrn_Server_Obj->sv_reset != NULL)
5665 		    Slrn_Server_Obj->sv_reset ();
5666 		  return -1;
5667 	       }
5668 
5669 	     if (1 == (++num % reads_per_update))
5670 	       {
5671 		  if (meter_chars[last_meter_char] == 0)
5672 		    last_meter_char = 0;
5673 
5674 		  slrn_message_now (_("%s: receiving additional headers...[%c]"),
5675 				    Slrn_Current_Group_Name,
5676 				    meter_chars[last_meter_char++]);
5677 	       }
5678 
5679 	     while ((NULL != h) && (this_num > h->number))
5680 	       h = h->real_next;
5681 
5682 	     if ((NULL != h) && (this_num == h->number))
5683 	       slrn_append_add_xover_to_header (h, l);
5684 	  }
5685      }
5686    slrn_close_add_xover (0);
5687 
5688    return 0;
5689 }
5690 
5691 /*}}}*/
5692 /* gets the headers of article number min-max, decrementing *totalp for each
5693  * downloaded article */
get_headers(NNTP_Artnum_Type min,NNTP_Artnum_Type max,NNTP_Artnum_Type * totalp)5694 static int get_headers (NNTP_Artnum_Type min, NNTP_Artnum_Type max, NNTP_Artnum_Type *totalp) /*{{{*/
5695 {
5696    Slrn_Header_Type *h;
5697    /* int percent, last_percent, dpercent, */
5698    NNTP_Artnum_Type expected_num;
5699    NNTP_Artnum_Type total = *totalp;
5700    int reads_per_update;
5701    int num_processed;
5702    int num, err;
5703    Slrn_XOver_Type xov;
5704 
5705    if (total == 0)
5706      return 0;
5707 
5708    if (SLang_get_error () == USER_BREAK)
5709      return -1;
5710 
5711    if ((reads_per_update = Slrn_Reads_Per_Update) < 5)
5712      reads_per_update = 50;
5713 
5714    /* slrn_set_suspension (1); */
5715 
5716    err = slrn_open_xover (min, max);
5717    if (err != OK_XOVER)
5718      {
5719 	if ((err == ERR_NOCRNT) || /* no articles in the range */
5720 	    (err == ERR_NOARTIG))  /* this one is not RFC 2980 compliant */
5721 	  return 0;
5722 
5723 	return -1;
5724      }
5725 
5726    num_processed = 0;
5727    expected_num = min;
5728    num = Total_Num_Headers + Number_Killed;
5729    while (slrn_read_xover(&xov) > 0)
5730      {
5731 	NNTP_Artnum_Type this_num;
5732 
5733 	if (SLang_get_error () == USER_BREAK)
5734 	  {
5735 	     slrn_free_xover_data (&xov);
5736 	     if (Slrn_Server_Obj->sv_reset != NULL)
5737 	       Slrn_Server_Obj->sv_reset ();
5738 	     return -1;
5739 	  }
5740 
5741 	this_num = xov.id;
5742 
5743 	if (expected_num != this_num)
5744 	  {
5745 	     NNTP_Artnum_Type bad_num;
5746 
5747 	     total -= (this_num - expected_num);
5748 
5749 	     for (bad_num = expected_num; bad_num < this_num; bad_num++)
5750 	       add_to_missing_article_list (bad_num);
5751 	  }
5752 
5753 	expected_num = this_num + 1;
5754 	num++;
5755 	h = process_xover (&xov);      /* steals the xover data -- nothing to free */
5756 
5757 	if ((1 == (num % reads_per_update))
5758 	    && (SLang_get_error () == 0))
5759 	  {
5760 	     slrn_message_now (_("%s: headers received: %2d/" NNTP_FMT_ARTNUM),
5761 			       Slrn_Current_Group_Name, num, total);
5762 	  }
5763 
5764 	if (Slrn_First_Header == NULL)
5765 	  Slrn_First_Header = _art_Headers = h;
5766 	else
5767 	  {
5768 	     h->real_next = Slrn_Current_Header->real_next;
5769 	     h->real_prev = Slrn_Current_Header;
5770 	     Slrn_Current_Header->real_next = h;
5771 
5772 	     if (h->real_next != NULL)
5773 	       {
5774 		  h->real_next->real_prev = h;
5775 	       }
5776 	  }
5777 
5778 	Slrn_Current_Header = h;
5779 	num_processed++;
5780      }
5781 
5782    slrn_close_xover ();
5783 
5784    if (expected_num != max + 1)
5785      {
5786 	NNTP_Artnum_Type bad_num;
5787 
5788 	total -= (max - expected_num) + 1;
5789 
5790 	for (bad_num = expected_num; bad_num <= max; bad_num++)
5791 	  add_to_missing_article_list (bad_num);
5792      }
5793 
5794    if (-1 == get_add_headers (min, max))
5795      return -1;
5796 
5797    /* slrn_set_suspension (0); */
5798    *totalp = total;
5799 
5800    Total_Num_Headers += num_processed;
5801 
5802    return (int) num_processed;
5803 }
5804 
5805 /*}}}*/
get_missing_headers()5806 static void get_missing_headers () /*{{{*/
5807 {
5808    Slrn_Header_Type *h;
5809    NNTP_Artnum_Type min, max;
5810 
5811    if (0 == slrn_add_xover_missing())
5812      return;
5813 
5814    h = Slrn_First_Header;
5815 
5816    while ((NULL != h) && (-1 == h->number))
5817      h = h->real_next;
5818 
5819    while (NULL != h)
5820      {
5821 	max = min = h->number;
5822 	while ((NULL != (h = h->real_next)) && (h->number == max + 1))
5823 	  max++;
5824 	get_add_headers (min, max);
5825      }
5826 
5827    slrn_close_add_xover (1);
5828 }
5829 
5830 /*}}}*/
5831 
5832 /*}}}*/
5833 
5834 /*}}}*/
5835 /*{{{ get parent/children headers, etc... */
5836 
5837 /* Nothing is synced by this routine.  It is up to the calling routine. */
insert_header(Slrn_Header_Type * ref)5838 static void insert_header (Slrn_Header_Type *ref) /*{{{*/
5839 {
5840    NNTP_Artnum_Type n, id;
5841    Slrn_Header_Type *h;
5842 
5843    ref->hash_next = Header_Table[ref->hash % HEADER_TABLE_SIZE];
5844    Header_Table[ref->hash % HEADER_TABLE_SIZE] = ref;
5845 
5846    n = ref->number;
5847    h = Slrn_First_Header;
5848    while (h != NULL)
5849      {
5850 	if (h->number >= n)
5851 	  {
5852 	     ref->real_next = h;
5853 	     ref->real_prev = h->real_prev;
5854 	     if (h->real_prev != NULL) h->real_prev->real_next = ref;
5855 	     h->real_prev = ref;
5856 
5857 	     if (h == Slrn_First_Header) Slrn_First_Header = ref;
5858 	     if (h == _art_Headers) _art_Headers = ref;
5859 
5860 	     break;
5861 	  }
5862 	h = h->real_next;
5863      }
5864 
5865    if (h == NULL)
5866      {
5867 	h = Slrn_First_Header;
5868 	while (h->real_next != NULL) h = h->real_next;
5869 
5870 	ref->real_next = NULL;
5871 	ref->real_prev = h;
5872 	h->real_next = ref;
5873      }
5874 
5875    if ((id = ref->number) <= 0) return;
5876 
5877    /* Set the flags for this guy. */
5878    if (!(ref->flags & HEADER_REQUEST_BODY) &&
5879        slrn_ranges_is_member (Current_Group->range.next, id))
5880      {
5881 	if (!(ref->flags & HEADER_READ))
5882 	  {
5883 	     ref->flags |= HEADER_READ;
5884 	     Number_Read++;
5885 	  }
5886      }
5887    else if (ref->flags & HEADER_READ)
5888      {
5889 	ref->flags &= ~HEADER_READ;
5890 	Number_Read--;
5891      }
5892 }
5893 
5894 /*}}}*/
5895 
5896 /* line number is not synced. */
get_header_by_message_id(char * msgid,int reconstruct_thread,int query_server,Slrn_Range_Type * no_body)5897 static int get_header_by_message_id (char *msgid,
5898 				     int reconstruct_thread,
5899 				     int query_server,
5900 				     Slrn_Range_Type *no_body) /*{{{*/
5901 {
5902    Slrn_Header_Type *ref;
5903    Slrn_XOver_Type xov;
5904 
5905    if ((msgid == NULL) || (*msgid == 0)) return -1;
5906 
5907    ref = _art_find_header_from_msgid (msgid, msgid + strlen (msgid));
5908    if (ref != NULL)
5909      {
5910 	Slrn_Current_Header = ref;
5911 	if (reconstruct_thread == 0)
5912 	  _art_find_header_line_num ();
5913 	Slrn_Full_Screen_Update = 1;
5914 	return 0;
5915      }
5916 
5917    if (query_server == 0)
5918      return -1;
5919 
5920    slrn_message_now (_("Retrieving %s from server..."), msgid);
5921 
5922    /* Try reading it from the server */
5923    if (-1 == slrn_xover_for_msgid (msgid, &xov))
5924      return 1;
5925 
5926    ref = process_xover (&xov);
5927 
5928    if ((ref->number!=0) &&
5929        (slrn_ranges_is_member (no_body, ref->number)))
5930      {
5931 	ref->flags |= HEADER_WITHOUT_BODY;
5932 	if (slrn_ranges_is_member (Current_Group->requests, ref->number))
5933 	  ref->flags |= HEADER_REQUEST_BODY;
5934      }
5935 
5936    ref = apply_score (ref, 0);
5937 
5938    if (ref == NULL) return -1;
5939 
5940    insert_header (ref);
5941 
5942    Slrn_Current_Header = ref;
5943    if (reconstruct_thread == 0)
5944      {
5945 	slrn_sort_headers ();
5946      }
5947    return 0;
5948 }
5949 
5950 /*}}}*/
5951 
5952 /* returns -1 if not implemented or the number of children returned from
5953  * the server.  It does not sync line number.
5954  */
find_children_headers(Slrn_Header_Type * parent,Slrn_Range_Type * no_body,unsigned int * numidsp)5955 static int find_children_headers (Slrn_Header_Type *parent, /*{{{*/
5956 				  Slrn_Range_Type *no_body,
5957 				  unsigned int *numidsp)
5958 {
5959    char buf[NNTP_BUFFER_SIZE];
5960    NNTP_Artnum_Type *id_array;
5961    NNTP_Artnum_Type id;
5962    unsigned int i, num_ids, id_array_size;
5963    char *fmt = _("Retrieving children from server...[%c]");
5964    char *meter_chars = "|/-\\";
5965    static unsigned int last_meter_char;
5966 
5967    if (OK_HEAD != Slrn_Server_Obj->sv_xpat_cmd ("References",
5968 						Slrn_Server_Min, Slrn_Server_Max,
5969 						parent->msgid))
5970      {
5971 	slrn_error (_("Your server does not provide support for this feature."));
5972 	return -1;
5973      }
5974 
5975    if (meter_chars[last_meter_char] == 0)
5976      last_meter_char = 0;
5977 
5978    slrn_message_now (fmt, meter_chars[last_meter_char]);
5979    last_meter_char++;
5980 
5981    num_ids = 0;
5982    id_array_size = 0;
5983    id_array = NULL;
5984 
5985    while (1)
5986      {
5987 	int status;
5988 	char *p;
5989 
5990 	status = Slrn_Server_Obj->sv_read_line (buf, sizeof (buf) - 1);
5991 	if (status <= 0)
5992 	  break;
5993 
5994 	if (meter_chars[last_meter_char] == 0)
5995 	  last_meter_char = 0;
5996 
5997 	slrn_message_now (fmt, meter_chars[last_meter_char]);
5998 	last_meter_char++;
5999 
6000 	id = NNTP_STR_TO_ARTNUM (buf);
6001 	if (id <= 0) continue;
6002 
6003 	p = buf;
6004 	while ((*p != 0) && (*p != ' '))
6005 	  p++;
6006 	if ((*p == 0) || (*(p+1) == 0))
6007 	  continue; /* work around a bug in Typhoon servers */
6008 
6009 	if (NULL != find_header_from_serverid (id))
6010 	  continue;
6011 
6012 	if (num_ids == id_array_size)
6013 	  {
6014 	     NNTP_Artnum_Type *new_ids;
6015 	     unsigned int new_size = 2*(id_array_size + 1);
6016 	     new_ids = (NNTP_Artnum_Type *)slrn_realloc ((char *)id_array, new_size*sizeof(NNTP_Artnum_Type), 1);
6017 	     if (new_ids == NULL)
6018 	       break;
6019 	     id_array = new_ids;
6020 	     id_array_size = new_size;
6021 	  }
6022 	id_array[num_ids] = id;
6023 	num_ids++;
6024      }
6025 
6026    for (i = 0; i < num_ids; i++)
6027      {
6028 	Slrn_XOver_Type xov;
6029 	Slrn_Header_Type *h = NULL;
6030 
6031 	id = id_array[i];
6032 
6033 	if (OK_XOVER != slrn_open_xover (id, id))
6034 	  break;
6035 
6036 	/* This will loop once. */
6037 	while (slrn_read_xover (&xov) > 0)
6038 	  {
6039 	     Slrn_Header_Type *bad_h;
6040 
6041 	     h = process_xover (&xov);
6042 
6043 	     if ((h->number!=0) &&
6044 		 (slrn_ranges_is_member (no_body, h->number)))
6045 	       {
6046 		  h->flags |= HEADER_WITHOUT_BODY;
6047 		  if (slrn_ranges_is_member (Current_Group->requests, h->number))
6048 		    h->flags |= HEADER_REQUEST_BODY;
6049 	       }
6050 
6051 	     h = apply_score (h, 0);
6052 	     if (h == NULL) continue;
6053 
6054 	     /* We may already have this header.  How is this possible?
6055 	      * If the header was retrieved sometime earlier via HEAD
6056 	      * <msgid>, the server may not have returned the article
6057 	      * number.  As a result, that header may have a number
6058 	      * of -1.  Here, we really have the correct article
6059 	      * number since the previous while loop made sure of that.
6060 	      * So, before inserting it, check to see whether or not we
6061 	      * have it and if so, fixup the id.
6062 	      */
6063 
6064 	     bad_h = slrn_find_header_with_msgid (h->msgid);
6065 	     if (bad_h != NULL)
6066 	       {
6067 		  bad_h->number = h->number;
6068 		  free_killed_header (h);
6069 		  h = bad_h;
6070 		  continue;
6071 	       }
6072 	     insert_header (h);
6073 	  }
6074 	slrn_close_xover ();
6075 
6076 	if (h == NULL) continue;
6077 
6078 	slrn_open_all_add_xover ();
6079 	while (slrn_open_add_xover (id, id) == 1)
6080 	  {
6081 	     Slrn_Header_Line_Type *l;
6082 	     NNTP_Artnum_Type unused;
6083 
6084 	     while (-1 != slrn_read_add_xover (&l, &unused)) /* loops once */
6085 	       slrn_append_add_xover_to_header (h, l);
6086 	  }
6087 	slrn_close_add_xover (0);
6088      }
6089    if (numidsp != NULL)
6090      *numidsp = num_ids;
6091    slrn_free ((char *) id_array);
6092    return 0;
6093 }
6094 
6095 /*}}}*/
6096 
6097 /* Line number not synced. */
get_children_headers_1(Slrn_Header_Type * h,Slrn_Range_Type * no_body)6098 static void get_children_headers_1 (Slrn_Header_Type *h, /*{{{*/
6099 				    Slrn_Range_Type *no_body)
6100 {
6101    while (h != NULL)
6102      {
6103 	(void) find_children_headers (h, no_body, NULL);
6104 	if (h->child != NULL)
6105 	  {
6106 	     get_children_headers_1 (h->child, no_body);
6107 	  }
6108 	h = h->sister;
6109      }
6110 }
6111 
6112 /*}}}*/
6113 
get_children_headers(void)6114 static void get_children_headers (void) /*{{{*/
6115 {
6116    Slrn_Header_Type *h;
6117    int thorough_search;
6118    Slrn_Range_Type *no_body = NULL;
6119 
6120 #if SLRN_HAS_SPOOL_SUPPORT
6121    if (Slrn_Server_Id == SLRN_SERVER_ID_SPOOL)
6122      no_body = slrn_spool_get_no_body_ranges (Slrn_Current_Group_Name);
6123 #endif
6124 
6125    if (Slrn_Prefix_Arg_Ptr == NULL) thorough_search = 1;
6126    else
6127      {
6128 	Slrn_Prefix_Arg_Ptr = NULL;
6129 	thorough_search = 0;
6130      }
6131 
6132    /* slrn_set_suspension (1); */
6133 
6134    if (find_children_headers (Slrn_Current_Header, no_body, NULL) < 0)
6135      {
6136 	/* slrn_set_suspension (0); */
6137 	slrn_ranges_free (no_body);
6138 	return;
6139      }
6140 
6141    slrn_sort_headers ();
6142 
6143    h = Slrn_Current_Header->child;
6144    if ((h != NULL) && thorough_search)
6145      {
6146 	/* Now walk the tree getting children headers.  For efficiency,
6147 	 * only children currently threaded will be searched.  Hopefully the
6148 	 * above attempt got everything.  If other newsreaders did not chop off
6149 	 * headers, this would be unnecessary!
6150 	 */
6151 	get_children_headers_1 (h, no_body);
6152 	slrn_sort_headers ();
6153      }
6154    slrn_uncollapse_this_thread (Slrn_Current_Header, 1);
6155    slrn_ranges_free (no_body);
6156 
6157    /* slrn_set_suspension (0); */
6158 }
6159 
6160 /*}}}*/
6161 
mark_headers_unprocessed(void)6162 static void mark_headers_unprocessed (void)
6163 {
6164    Slrn_Header_Type *h = Slrn_First_Header;
6165 
6166    while (h != NULL)
6167      {
6168 	h->flags &= ~HEADER_PROCESSED;
6169 	h = h->real_next;
6170      }
6171 }
6172 
reference_loop_error(void)6173 static void reference_loop_error (void)
6174 {
6175    slrn_error (_("Header is part of a reference loop"));
6176    mark_headers_unprocessed ();
6177 }
6178 
get_parent_header(void)6179 static void get_parent_header (void) /*{{{*/
6180 {
6181    char *r1, *r0, *rmin;
6182    unsigned int len;
6183    char buf[512];
6184    int no_error_no_thread;
6185    Slrn_Header_Type *last_header;
6186    Slrn_Range_Type *no_body = NULL;
6187 
6188    if (Slrn_Current_Header == NULL) return;
6189 
6190    if (Slrn_Prefix_Arg_Ptr == NULL) no_error_no_thread = 0;
6191    else
6192      {
6193 	if (*Slrn_Prefix_Arg_Ptr != 2) Slrn_Prefix_Arg_Ptr = NULL;
6194 	/* else: leave it for get_children_headers() */
6195 	no_error_no_thread = 1;
6196      }
6197 
6198    last_header = NULL;
6199    r1 = rmin = NULL;
6200 #if SLRN_HAS_SPOOL_SUPPORT
6201    if (Slrn_Server_Id == SLRN_SERVER_ID_SPOOL)
6202      no_body = slrn_spool_get_no_body_ranges (Slrn_Current_Group_Name);
6203 #endif
6204    do
6205      {
6206 	rmin = Slrn_Current_Header->refs;
6207 	if (rmin == NULL) break;
6208 
6209 	if (last_header != Slrn_Current_Header)
6210 	  {
6211 	     if (Slrn_Current_Header->flags & HEADER_PROCESSED)
6212 	       {
6213 		  reference_loop_error ();
6214 		  slrn_ranges_free (no_body);
6215 		  return;
6216 	       }
6217 	     last_header = Slrn_Current_Header;
6218 	     last_header->flags |= HEADER_PROCESSED;
6219 	     r1 = rmin + strlen (rmin);
6220 	  }
6221 
6222 	while ((r1 > rmin) && (*r1 != '>')) r1--;
6223 	r0 = r1 - 1;
6224 	while ((r0 >= rmin) && (*r0 != '<')) r0--;
6225 
6226 	if ((r0 < rmin) || (r1 == rmin))
6227 	  {
6228 	     if (no_error_no_thread) break;
6229 	     slrn_error (_("Article has no parent reference."));
6230 	     mark_headers_unprocessed ();
6231 	     slrn_ranges_free (no_body);
6232 	     return;
6233 	  }
6234 
6235 	len = (unsigned int) ((r1 + 1) - r0);
6236 	strncpy (buf, r0, len);
6237 	buf[len] = 0;
6238 
6239 	r1 = r0;
6240      }
6241    while (no_error_no_thread
6242 	  && (get_header_by_message_id (buf, 1, 1, no_body) >= 0));
6243 
6244    mark_headers_unprocessed ();
6245    slrn_ranges_free (no_body);
6246 
6247    if (no_error_no_thread)
6248      {
6249 	slrn_sort_headers ();
6250 	if (SLKeyBoard_Quit == 0) get_children_headers ();
6251 	else Slrn_Prefix_Arg_Ptr = NULL;
6252      }
6253    else (void) slrn_locate_header_by_msgid (buf, 0, 1); /* syncs the line */
6254 
6255    slrn_uncollapse_this_thread (Slrn_Current_Header, 1);
6256 }
6257 
6258 /*}}}*/
6259 
slrn_locate_header_by_msgid(char * msgid,int no_error,int query_server)6260 int slrn_locate_header_by_msgid (char *msgid, int no_error, int query_server)
6261 {
6262    Slrn_Range_Type *no_body = NULL;
6263 #if SLRN_HAS_SPOOL_SUPPORT
6264    if (Slrn_Server_Id == SLRN_SERVER_ID_SPOOL)
6265      no_body = slrn_spool_get_no_body_ranges (Slrn_Current_Group_Name);
6266 #endif
6267 
6268    if (0 == get_header_by_message_id (msgid, 0, query_server, no_body))
6269      {
6270 	/* The actual header might be part of a collapsed thread.  If so, then
6271 	 * the current header may not be the one we are seeking.
6272 	 * Check the message-id and retry with the thread uncollapsed
6273 	 * if this is the case.
6274 	 */
6275 	if ((Slrn_Current_Header->msgid == NULL)
6276 	    || (0 != strcmp (Slrn_Current_Header->msgid, msgid)))
6277 	  {
6278 	     slrn_uncollapse_this_thread (Slrn_Current_Header, 1);
6279 	     (void) get_header_by_message_id (msgid, 0, 0, no_body);
6280 	  }
6281 	slrn_ranges_free (no_body);
6282 	return 0;
6283      }
6284    slrn_ranges_free (no_body);
6285    if (0 == no_error)
6286      slrn_error (_("Article %s not available."), msgid);
6287    return -1;
6288 }
6289 
locate_header_by_msgid(void)6290 static void locate_header_by_msgid (void) /*{{{*/
6291 {
6292    char buf[SLRL_DISPLAY_BUFFER_SIZE+2];
6293    char *msgid = buf + 1;
6294    *buf = 0;
6295    *msgid = 0;
6296 
6297    if (slrn_read_input (_("Enter Message-ID: "), NULL, msgid, 1, 0) <= 0) return;
6298 
6299    if (*msgid != '<')
6300      {
6301 	*buf = '<';
6302 	msgid = buf;
6303      }
6304    if (!strncmp (msgid+1, "news:", 5))
6305      {
6306 	msgid += 5;
6307 	*msgid = '<';
6308      }
6309 
6310    if (msgid [strlen(msgid) - 1] != '>')
6311      strcat (msgid, ">"); /* safe */
6312 
6313    (void) slrn_locate_header_by_msgid (msgid, 0, 1);
6314 }
6315 
6316 /*}}}*/
6317 
6318 /*}}}*/
6319 
6320 /*{{{ article window display modes */
6321 
hide_article(void)6322 static void hide_article (void) /*{{{*/
6323 {
6324    Slrn_Full_Screen_Update = 1;
6325    if (Article_Visible == 0)
6326      {
6327 	select_article (1);
6328 	return;
6329      }
6330 
6331    set_article_visibility (0);
6332    Article_Window_HScroll = 0;
6333 }
6334 
6335 /*}}}*/
6336 
6337 #define ZOOM_OFFSET 4
zoom_article_window(void)6338 static void zoom_article_window (void)
6339 {
6340    static int old_rows;
6341    int zoomed_rows;
6342 
6343    if (Article_Visible == 0)
6344      hide_article ();
6345 
6346    if (Article_Visible == 0)
6347      return;
6348 
6349    zoomed_rows = SLtt_Screen_Rows - ZOOM_OFFSET;
6350    if (zoomed_rows == Article_Window_Nrows)
6351      /* already zoomed.  Unzoom */
6352      Article_Window_Nrows = old_rows;
6353    else
6354      {
6355 	old_rows = Article_Window_Nrows;
6356 	Article_Window_Nrows = zoomed_rows;
6357      }
6358 
6359    art_winch ();
6360 }
6361 
slrn_is_article_win_zoomed(void)6362 int slrn_is_article_win_zoomed (void)
6363 {
6364    return (Article_Window_Nrows == SLtt_Screen_Rows - ZOOM_OFFSET);
6365 }
6366 
art_left(void)6367 static void art_left (void) /*{{{*/
6368 {
6369    if ((Article_Visible == 0)
6370        || (Article_Window_HScroll == 0))
6371      {
6372 	if (Header_Window_HScroll == 0) return;
6373 	Header_Window_HScroll -= SLtt_Screen_Cols / 5;
6374 	if (Header_Window_HScroll < 0) Header_Window_HScroll = 0;
6375      }
6376    else
6377      {
6378 	if (Article_Window_HScroll == 0) return;
6379 	Article_Window_HScroll -= (SLtt_Screen_Cols * 2) / 3;
6380 	if (Article_Window_HScroll < 0) Article_Window_HScroll = 0;
6381      }
6382    Slrn_Full_Screen_Update = 1;
6383 }
6384 
6385 /*}}}*/
6386 
art_right(void)6387 static void art_right (void) /*{{{*/
6388 {
6389    if (Article_Visible == 0)
6390      Header_Window_HScroll += SLtt_Screen_Cols / 5;
6391    else
6392      Article_Window_HScroll += (SLtt_Screen_Cols * 2) / 3;
6393 
6394    Slrn_Full_Screen_Update = 1;
6395 }
6396 
6397 /*}}}*/
6398 
6399 /*{{{ rot13 and spoilers */
toggle_rot13(void)6400 static void toggle_rot13 (void) /*{{{*/
6401 {
6402    Do_Rot13 = !Do_Rot13;
6403    Slrn_Full_Screen_Update = 1;
6404 }
6405 
6406 /*}}}*/
6407 
6408 #if SLRN_HAS_SPOILERS
show_spoilers(void)6409 static void show_spoilers (void) /*{{{*/
6410 {
6411    Slrn_Article_Line_Type *l1 = NULL;
6412    Slrn_Article_Line_Type *l;
6413 
6414    if (Slrn_Current_Article == NULL)
6415      return;
6416 
6417    l = Slrn_Current_Article->lines;
6418 
6419    /* find the first spoiler-ed line */
6420    while ((l != NULL) && (0 == (l->flags & SPOILER_LINE)))
6421      l = l->next;
6422    if (NULL == (l1 = l))
6423      return;
6424 
6425    /* Prefix arg means un-spoiler the whole article */
6426    if ((Slrn_Prefix_Arg_Ptr != NULL) || (Slrn_Spoiler_Display_Mode & 2))
6427      {
6428 	Slrn_Prefix_Arg_Ptr = NULL;
6429 	while (l != NULL)
6430 	  {
6431 	     l->flags &= ~SPOILER_LINE;
6432 	     l = l->next;
6433 	  }
6434      }
6435    else
6436      {
6437 	char *s, n;
6438 
6439 	s = l->buf;
6440 	n = Num_Spoilers_Visible + 1;
6441 
6442 	while (n-- != 0)
6443 	  {
6444 	     if (NULL == (s = slrn_strbyte (s, 12)))
6445 	       break;
6446 	     else
6447 	       s++;
6448 	  }
6449 
6450 	if (NULL != s)
6451 	  Num_Spoilers_Visible++;
6452 	else
6453 	  {
6454 	     Num_Spoilers_Visible = 1;
6455 	     /* un-spoiler until we hit another formfeed */
6456 	     do
6457 	       {
6458 		  l->flags &= ~SPOILER_LINE;
6459 		  l = l->next;
6460 	       }
6461 	     while ((l != NULL) && (NULL == slrn_strbyte (l->buf, 12)));
6462 	  }
6463      }
6464 
6465    if (Slrn_Spoiler_Display_Mode & 1)
6466      {
6467 	Slrn_Current_Article->cline = l1;
6468 	find_article_line_num ();
6469      }
6470 
6471    Slrn_Full_Screen_Update = 1;
6472 }
6473 
6474 /*}}}*/
6475 
6476 #endif
6477 
6478 /*}}}*/
6479 
6480 /*{{{ hide/toggle quotes */
6481 
6482 /* This function does not update the line number */
hide_or_unhide_quotes(void)6483 static void hide_or_unhide_quotes (void) /*{{{*/
6484 {
6485    if (Slrn_Current_Article == NULL)
6486      return;
6487 
6488    if (Slrn_Current_Article->quotes_hidden)
6489      _slrn_art_hide_quotes (Slrn_Current_Article, 1);
6490    else
6491      _slrn_art_unhide_quotes (Slrn_Current_Article);
6492 }
6493 
6494 /*}}}*/
6495 
toggle_quotes(void)6496 static void toggle_quotes (void) /*{{{*/
6497 {
6498    Slrn_Article_Type *a = Slrn_Current_Article;
6499 
6500    if (a == NULL)
6501      return;
6502 
6503    if (Slrn_Prefix_Arg_Ptr != NULL)
6504      {
6505 	Slrn_Quotes_Hidden_Mode = *Slrn_Prefix_Arg_Ptr + 1;
6506 	Slrn_Prefix_Arg_Ptr = NULL;
6507 	_slrn_art_hide_quotes (Slrn_Current_Article, 0);
6508      }
6509    else if (a->quotes_hidden)
6510      _slrn_art_unhide_quotes (a);
6511    else
6512      _slrn_art_hide_quotes (a, 1);
6513 
6514    Slrn_Quotes_Hidden_Mode = Slrn_Current_Article->quotes_hidden;
6515 
6516    find_article_line_num ();
6517 }
6518 
6519 /*}}}*/
6520 
6521 /*}}}*/
6522 
slrn_is_hidden_headers_mode()6523 int slrn_is_hidden_headers_mode ()
6524 {
6525    return Headers_Hidden_Mode;
6526 }
6527 
toggle_headers(void)6528 static void toggle_headers (void) /*{{{*/
6529 {
6530    Slrn_Article_Type *a = Slrn_Current_Article;
6531 
6532    if (a == NULL)
6533      return;
6534 
6535    if (a->headers_hidden)
6536      {
6537 	_slrn_art_unhide_headers (a);
6538 	a->cline = a->lines;
6539 	find_article_line_num ();
6540 	Headers_Hidden_Mode = 0;
6541 	return;
6542      }
6543 
6544    _slrn_art_hide_headers (a);
6545    Headers_Hidden_Mode = 1;
6546 }
6547 
6548 /*}}}*/
6549 
toggle_signature(void)6550 static void toggle_signature (void) /*{{{*/
6551 {
6552    Slrn_Article_Type *a = Slrn_Current_Article;
6553 
6554    if (a == NULL)
6555      return;
6556 
6557    if (a->signature_hidden)
6558      _slrn_art_unhide_signature (a);
6559    else _slrn_art_hide_signature (a);
6560    find_article_line_num ();
6561 
6562    Slrn_Signature_Hidden = a->signature_hidden;
6563 }
6564 
6565 /*}}}*/
6566 
toggle_pgp_signature(void)6567 static void toggle_pgp_signature (void) /*{{{*/
6568 {
6569    Slrn_Article_Type *a = Slrn_Current_Article;
6570 
6571    if (a == NULL)
6572      return;
6573 
6574    if (a->pgp_signature_hidden)
6575      _slrn_art_unhide_pgp_signature (a);
6576    else _slrn_art_hide_pgp_signature (a);
6577    find_article_line_num ();
6578 
6579    Slrn_Pgp_Signature_Hidden = a->pgp_signature_hidden;
6580 }
6581 
6582 /*}}}*/
6583 
toggle_verbatim_marks(void)6584 static void toggle_verbatim_marks (void) /*{{{*/
6585 {
6586    Slrn_Article_Type *a = Slrn_Current_Article;
6587 
6588    if (a == NULL)
6589      return;
6590 
6591    a->verbatim_marks_hidden = !a->verbatim_marks_hidden;
6592    Slrn_Verbatim_Marks_Hidden = a->verbatim_marks_hidden;
6593 }
6594 /*}}}*/
6595 
toggle_verbatim(void)6596 static void toggle_verbatim (void) /*{{{*/
6597 {
6598    Slrn_Article_Type *a = Slrn_Current_Article;
6599 
6600    if (a == NULL)
6601      return;
6602 
6603    if (a->verbatim_hidden)
6604      _slrn_art_unhide_verbatim (a);
6605    else
6606      _slrn_art_hide_verbatim (a);
6607 
6608    find_article_line_num();
6609    Slrn_Verbatim_Hidden = a->verbatim_hidden;
6610 }
6611 /*}}}*/
6612 /*}}}*/
6613 
6614 /*{{{ leave/suspend article mode and support functions */
update_ranges(void)6615 static void update_ranges (void) /*{{{*/
6616 {
6617    NNTP_Artnum_Type bmin, bmax;
6618    unsigned int is_read;
6619    Slrn_Range_Type *r_new;
6620    Slrn_Header_Type *h;
6621 
6622    if (User_Aborted_Group_Read) return;
6623 
6624    h = Slrn_First_Header;
6625    /* skip articles for which the numeric id was not available */
6626    while ((h != NULL) && (h->number < 0)) h = h->real_next;
6627    if (h == NULL) return;
6628 
6629    /* We do our work on a copy of the ranges first; this way, we can
6630     * find out whether we need to mark the group dirty later. */
6631    r_new = slrn_ranges_clone (Current_Group->range.next);
6632 
6633    /* Mark old (unavailable) articles as read */
6634    if (Slrn_Server_Min > 1)
6635      r_new = slrn_ranges_add (r_new, 1, Slrn_Server_Min - 1);
6636 
6637    /* Now, mark blocks of articles read / unread */
6638    is_read = h->flags & HEADER_READ;
6639    bmin = bmax = h->number;
6640    while (h != NULL)
6641      {
6642 	h = h->real_next;
6643 	if ((h==NULL) || (h->number > bmax+1) ||
6644 	    (is_read != (h->flags & HEADER_READ)))
6645 	  {
6646 	     if (is_read)
6647 	       r_new = slrn_ranges_add (r_new, bmin, bmax);
6648 	     else
6649 	       r_new = slrn_ranges_remove (r_new, bmin, bmax);
6650 	     if (h!=NULL)
6651 	       {
6652 		  bmin = bmax = h->number;
6653 		  is_read = h->flags & HEADER_READ;
6654 	       }
6655 	  }
6656 	else
6657 	  bmax++;
6658      }
6659 
6660    if (slrn_ranges_compare(r_new, Current_Group->range.next))
6661      Slrn_Groups_Dirty = 1;
6662 
6663    /* Finally delete the old ranges and replace them with the new one */
6664    slrn_ranges_free (Current_Group->range.next);
6665    Current_Group->range.next = r_new;
6666    slrn_group_recount_unread (Current_Group);
6667 }
6668 /*}}}*/
6669 
update_requests(void)6670 static void update_requests (void) /*{{{*/
6671 {
6672 #if SLRN_HAS_SPOOL_SUPPORT
6673    NNTP_Artnum_Type bmin, bmax;
6674    unsigned int is_req;
6675    Slrn_Range_Type *r = Current_Group->requests;
6676    Slrn_Header_Type *h;
6677 
6678    if ((User_Aborted_Group_Read)||
6679        (Slrn_Server_Id != SLRN_SERVER_ID_SPOOL))
6680      return;
6681 
6682    h = Slrn_First_Header;
6683    /* skip articles for which the numeric id was not available */
6684    while ((h != NULL) && (h->number < 0)) h = h->real_next;
6685    if (h == NULL) return;
6686 
6687    /* Mark old (unavailable) articles as unrequested */
6688    r = slrn_ranges_remove (r, 1, Slrn_Server_Min - 1);
6689 
6690    /* Update the requested article ranges list. */
6691    is_req = h->flags & HEADER_REQUEST_BODY;
6692    bmin = bmax = h->number;
6693    while (h != NULL)
6694      {
6695 	h = h->real_next;
6696 	if ((h==NULL) || (h->number > bmax+1) ||
6697 	    (is_req != (h->flags & HEADER_REQUEST_BODY)))
6698 	  {
6699 	     if (is_req)
6700 	       r = slrn_ranges_add (r, bmin, bmax);
6701 	     else
6702 	       r = slrn_ranges_remove (r, bmin, bmax);
6703 	     if (h!=NULL)
6704 	       {
6705 		  bmin = bmax = h->number;
6706 		  is_req = h->flags & HEADER_REQUEST_BODY;
6707 	       }
6708 	  }
6709 	else
6710 	  bmax++;
6711      }
6712 
6713    if ((-1 == slrn_spool_set_requested_ranges (Slrn_Current_Group_Name, r)) &&
6714        (r != NULL)) /* if r == NULL, don't bother user */
6715      slrn_error_now (2, _("Warning: Could not save list of requested bodies."));
6716    Current_Group->requests = r;
6717 #endif
6718 }
6719 /*}}}*/
6720 
6721 /*{{{ art_quit */
art_quit(void)6722 static void art_quit (void) /*{{{*/
6723 {
6724    Slrn_Header_Type *h = _art_Headers;
6725 
6726    (void) slrn_run_hooks (HOOK_ARTICLE_MODE_QUIT, 0);
6727 
6728    slrn_init_hangup_signals (0);
6729 
6730 #if SLRN_HAS_GROUPLENS
6731    if (Slrn_Use_Group_Lens) slrn_put_grouplens_scores ();
6732 #endif
6733 
6734    free_article ();
6735 
6736    free_kill_lists_and_update ();
6737    free_tag_list ();
6738 
6739    slrn_close_score ();
6740    slrn_clear_requested_headers ();
6741 
6742    if (h != NULL)
6743      {
6744 	update_ranges ();
6745 	update_requests ();
6746 	free_all_headers ();
6747      }
6748 
6749    Slrn_First_Header = _art_Headers = Slrn_Current_Header = NULL;
6750    SLMEMSET ((char *) &Slrn_Header_Window, 0, sizeof (SLscroll_Window_Type));
6751    Total_Num_Headers = 0;
6752 
6753    Current_Group = NULL;
6754    Last_Read_Header = NULL;
6755    *Output_Filename = 0;
6756 
6757    Same_Subject_Start_Header = NULL;
6758    Slrn_Current_Group_Name = NULL;
6759 
6760    /* Since this function may get called before the mode is pushed,
6761     * only pop it if the mode is really article mode.
6762     */
6763    if ((Slrn_Current_Mode != NULL)
6764        && (Slrn_Current_Mode->mode == SLRN_ARTICLE_MODE))
6765      slrn_pop_mode ();
6766 
6767    slrn_write_newsrc (1); /* calls slrn_init_hangup_signals (1);*/
6768 }
6769 
6770 /*}}}*/
6771 
6772 /*}}}*/
6773 
skip_to_next_group_1(void)6774 static void skip_to_next_group_1 (void)
6775 {
6776    art_quit ();
6777    slrn_select_next_group ();
6778 }
6779 
skip_to_next_group(void)6780 static void skip_to_next_group (void) /*{{{*/
6781 {
6782    int tmp = Slrn_Startup_With_Article;
6783    Slrn_Startup_With_Article = 0;
6784 
6785    art_quit ();
6786    slrn_select_next_group ();
6787    if ((Slrn_Current_Mode != NULL) &&
6788        (Slrn_Current_Mode->mode == SLRN_ARTICLE_MODE))
6789      {
6790 	if ((Slrn_Current_Header->flags & (HEADER_READ|HEADER_WITHOUT_BODY)) &&
6791 	    (0 == slrn_next_unread_header (1)))
6792 	  slrn_clear_message ();
6793 	else if (tmp)
6794 	  art_pagedn ();
6795      }
6796 
6797    Slrn_Startup_With_Article = tmp;
6798 }
6799 
6800 /*}}}*/
6801 
skip_to_prev_group(void)6802 static void skip_to_prev_group (void) /*{{{*/
6803 {
6804    art_quit ();
6805    slrn_select_prev_group ();
6806 }
6807 
6808 /*}}}*/
6809 
fast_quit(void)6810 static void fast_quit (void) /*{{{*/
6811 {
6812    art_quit ();
6813    slrn_group_quit ();
6814 }
6815 
6816 /*}}}*/
6817 
art_suspend_cmd(void)6818 static void art_suspend_cmd (void) /*{{{*/
6819 {
6820    int rows = SLtt_Screen_Rows;
6821    slrn_suspend_cmd ();
6822    if (rows != SLtt_Screen_Rows)
6823      art_winch_sig (rows, -1);
6824 }
6825 
6826 /*}}}*/
6827 
6828 /*}}}*/
6829 /*{{{ art_xpunge */
art_xpunge(void)6830 static void art_xpunge (void) /*{{{*/
6831 {
6832    Slrn_Header_Type *save, *next, *h;
6833 
6834    free_article ();
6835    free_kill_lists_and_update ();
6836 
6837    save = _art_Headers;
6838    if (_art_Headers != NULL)
6839      {
6840 	update_ranges ();
6841      }
6842 
6843    /* If there are no unread headers, quit to group mode. */
6844    while (_art_Headers != NULL)
6845      {
6846 	if ((0 == (_art_Headers->flags & HEADER_READ)) ||
6847 	    (_art_Headers->flags & HEADER_DONT_DELETE_MASK))
6848 	  break;
6849 	_art_Headers = _art_Headers->next;
6850      }
6851 
6852    if (_art_Headers == NULL)
6853      {
6854 	_art_Headers = save;
6855 	art_quit ();
6856 	return;
6857      }
6858 
6859    /* Remove numerical tags from all read headers. */
6860    if ((Num_Tag_List.len != 0)
6861        && (Num_Tag_List.headers != NULL))
6862      {
6863 	unsigned int i, j;
6864 	Slrn_Header_Type *th;
6865 
6866 	j = 0;
6867 	for (i = 0; i < Num_Tag_List.len; i++)
6868 	  {
6869 	     th = Num_Tag_List.headers[i];
6870 	     if ((th->flags & HEADER_READ) &&
6871 		 (0 == (th->flags & HEADER_DONT_DELETE_MASK)))
6872 	       {
6873 		  th->tag_number = 0;
6874 		  th->flags &= ~HEADER_NTAGGED;
6875 		  continue;
6876 	       }
6877 
6878 	     Num_Tag_List.headers [j] = th;
6879 	     j++;
6880 	     th->tag_number = j;
6881 	  }
6882 	Num_Tag_List.len = j;
6883      }
6884 
6885    /* Check if Last_Read_Header and Mark_Header are unread */
6886    if ((Last_Read_Header != NULL) &&
6887        (Last_Read_Header->flags & HEADER_READ) &&
6888        (0 == (Last_Read_Header->flags & HEADER_DONT_DELETE_MASK)))
6889      Last_Read_Header = NULL;
6890 
6891    if ((Mark_Header != NULL) &&
6892        (Mark_Header->flags & HEADER_READ) &&
6893        (0 == (Mark_Header->flags & HEADER_DONT_DELETE_MASK)))
6894      Mark_Header = NULL;
6895 
6896    /* Find an unread message for Slrn_Current_Header; we made sure that
6897     * at least one unread message exists, so this can never fail. */
6898    next = Slrn_Current_Header;
6899    while (next != NULL)
6900      {
6901 	if ((0 == (next->flags & HEADER_READ)) ||
6902 	    (next->flags & HEADER_DONT_DELETE_MASK))
6903 	  break;
6904 	next = next->next;
6905      }
6906 
6907    if (next == NULL)
6908      {
6909 	next = Slrn_Current_Header;
6910 	while (next != NULL)
6911 	  {
6912 	     if ((0 == (next->flags & HEADER_READ)) ||
6913 		 (next->flags & HEADER_DONT_DELETE_MASK))
6914 	       break;
6915 	     next = next->prev;
6916 	  }
6917      }
6918 
6919    Slrn_Current_Header = next; /* cannot be NULL (see above) */
6920 
6921    /* Free all headers up to the first unread one; set Slrn_First_Header
6922     * to it. */
6923    h = Slrn_First_Header; /* Slrn_First_Header != NULL */
6924    while (1)
6925      {
6926 	next = h->real_next;
6927 	if ((0 == (h->flags & HEADER_READ)) ||
6928 	    (h->flags & HEADER_DONT_DELETE_MASK))
6929 	  break;
6930 	free_killed_header (h);
6931 	h = next;
6932      }
6933    Slrn_First_Header = h;
6934    h->real_prev = NULL;
6935 
6936    /* Free the rest of the read headers, linking up the unread ones. */
6937    while (h != NULL)
6938      {
6939 	Slrn_Header_Type *next_next;
6940 
6941 	next = h->real_next;
6942 	while (next != NULL)
6943 	  {
6944 	     next_next = next->real_next;
6945 	     if ((0 == (next->flags & HEADER_READ)) ||
6946 		 (next->flags & HEADER_DONT_DELETE_MASK))
6947 	       break;
6948 	     free_killed_header (next);
6949 	     next = next_next;
6950 	  }
6951 	h->real_next = next;
6952 	if (next != NULL)
6953 	  next->real_prev = h;
6954 	h = next;
6955      }
6956 
6957    /* Fix the prev / next linking */
6958    h = _art_Headers = Slrn_First_Header;
6959    _art_Headers->prev = NULL;
6960 
6961    while (h != NULL)
6962      {
6963 	h->next = next = h->real_next;
6964 	if (next != NULL)
6965 	  {
6966 	     next->prev = h;
6967 	  }
6968 	h = next;
6969      }
6970 
6971    slrn_sort_headers ();
6972    slrn_write_newsrc (1);
6973 
6974    Slrn_Full_Screen_Update = 1;
6975 }
6976 /*}}}*/
6977 
6978 /*}}}*/
6979 /*{{{ cancel_article */
6980 
cancel_article(void)6981 static void cancel_article (void) /*{{{*/
6982 {
6983    char *me, *msgid, *newsgroups, *dist;
6984 #if SLRN_HAS_CANLOCK
6985    char *can_key;
6986 #endif
6987    char from_buf[512], me_buf[512], *from;
6988    Slrn_Mime_Error_Obj *err;
6989 
6990    if ((-1 == slrn_check_batch ()) ||
6991        (-1 == select_affected_article ()))
6992      return;
6993    slrn_update_screen ();
6994 
6995    /* TO cancel, we post a cancel message with a 'control' header.  First, check to
6996     * see if this is really the owner of the message.
6997     */
6998 
6999    from = slrn_extract_header ("From: ", 6);
7000    if (from != NULL) (void) parse_from (from, from_buf, sizeof(from_buf));
7001    else from[0] = '\0';
7002 
7003    if (NULL == (me = slrn_make_from_header ())) return;
7004    (void) parse_from (me+6, me_buf, sizeof(me_buf));
7005 
7006    if (slrn_case_strcmp ( from_buf,  me_buf))
7007      {
7008         slrn_error (_("Failed: Your name: '%s' is not '%s'"), me_buf, from_buf);
7009         slrn_free(me);
7010 	return;
7011      }
7012 
7013    if (slrn_get_yesno (0, _("Are you sure that you want to cancel this article")) <= 0)
7014      {
7015 	slrn_free(me);
7016 	return;
7017      }
7018 
7019    slrn_message_now (_("Cancelling..."));
7020 
7021    if ((err = slrn_mime_header_encode(&me, Slrn_Display_Charset)) != NULL)
7022      {
7023 	slrn_error (_("error during From: encoding: %s"), err->msg);
7024 	slrn_free(me);
7025 	slrn_free_mime_error(err);
7026 	return;
7027      }
7028 
7029    if (NULL == (newsgroups = slrn_extract_header ("Newsgroups: ", 12)))
7030      newsgroups = "";
7031 
7032    if ((NULL == (msgid = slrn_extract_header ("Message-ID: ", 12))) ||
7033        (0 == *msgid))
7034      {
7035 	slrn_error (_("No message id."));
7036 	slrn_free(me);
7037 	return;
7038      }
7039 
7040    dist = slrn_extract_header("Distribution: ", 14);
7041 
7042    if (Slrn_Post_Obj->po_start () < 0) return;
7043 
7044    Slrn_Post_Obj->po_printf ("%s\nNewsgroups: %s\nSubject: cmsg cancel %s\nControl: cancel %s\n",
7045 			     me, newsgroups, msgid, msgid);
7046 
7047    slrn_free(me);
7048 
7049    if ((dist != NULL) && (*dist != 0))
7050      {
7051 	Slrn_Post_Obj->po_printf ("Distribution: %s\n", dist);
7052      }
7053 
7054 #if SLRN_HAS_CANLOCK
7055    if (NULL != (can_key = gen_cancel_key(msgid)))
7056      {
7057 	Slrn_Post_Obj->po_printf ("Cancel-Key: %s\n", can_key);
7058 	SLFREE (can_key);
7059      }
7060 #endif
7061 
7062    Slrn_Post_Obj->po_printf("\nignore\nArticle cancelled by slrn %s\n", Slrn_Version_String);
7063 
7064    if (0 == Slrn_Post_Obj->po_end ())
7065      {
7066 	slrn_message (_("Done."));
7067      }
7068 }
7069 
7070 /*}}}*/
7071 
7072 /*}}}*/
7073 
7074 /*{{{ header/thread (un)deletion/(un)catchup */
delete_header(Slrn_Header_Type * h)7075 static void delete_header (Slrn_Header_Type *h) /*{{{*/
7076 {
7077    if (h->flags & HEADER_DONT_DELETE_MASK) return;
7078    if (0 == (h->flags & HEADER_READ))
7079      {
7080 	kill_cross_references (h);
7081 	h->flags |= HEADER_READ;
7082 	Number_Read++;
7083      }
7084 }
7085 
7086 /*}}}*/
7087 
undelete_header(Slrn_Header_Type * h)7088 static void undelete_header (Slrn_Header_Type *h) /*{{{*/
7089 {
7090    if (h->flags & HEADER_READ)
7091      {
7092 	h->flags &= ~HEADER_READ;
7093 	Number_Read--;
7094      }
7095 }
7096 
7097 /*}}}*/
7098 
slrn_request_header(Slrn_Header_Type * h)7099 void slrn_request_header (Slrn_Header_Type *h) /*{{{*/
7100 {
7101    if (0 == (h->flags & HEADER_WITHOUT_BODY)) return;
7102    if (h->number < 0)
7103      {
7104 	slrn_error (_("Warning: Can only request article bodies from this group."));
7105 	return;
7106      }
7107    h->flags |= HEADER_REQUEST_BODY;
7108    undelete_header (h);
7109 }
7110 /*}}}*/
7111 
slrn_unrequest_header(Slrn_Header_Type * h)7112 void slrn_unrequest_header (Slrn_Header_Type *h) /*{{{*/
7113 {
7114    h->flags &= ~HEADER_REQUEST_BODY;
7115 }
7116 /*}}}*/
7117 
catch_up_all(void)7118 static void catch_up_all (void) /*{{{*/
7119 {
7120    if ((Slrn_User_Wants_Confirmation & SLRN_CONFIRM_CATCHUP)
7121        && slrn_get_yesno (1, _("Mark all articles as read")) <= 0)
7122      return;
7123    for_all_headers (delete_header, 1);
7124 }
7125 
7126 /*}}}*/
7127 
un_catch_up_all(void)7128 static void un_catch_up_all (void) /*{{{*/
7129 {
7130    if ((Slrn_User_Wants_Confirmation & SLRN_CONFIRM_CATCHUP)
7131        && slrn_get_yesno (1, _("Mark all articles as unread")) <= 0)
7132      return;
7133    for_all_headers (undelete_header, 1);
7134 }
7135 
7136 /*}}}*/
7137 
catch_up_to_here(void)7138 static void catch_up_to_here (void) /*{{{*/
7139 {
7140    if ((Slrn_User_Wants_Confirmation & SLRN_CONFIRM_CATCHUP)
7141        && slrn_get_yesno (1, _("Mark all articles up to here as read")) <= 0)
7142      return;
7143    for_all_headers (delete_header, 0);
7144 }
7145 
7146 /*}}}*/
7147 
un_catch_up_to_here(void)7148 static void un_catch_up_to_here (void) /*{{{*/
7149 {
7150    if ((Slrn_User_Wants_Confirmation & SLRN_CONFIRM_CATCHUP)
7151        && slrn_get_yesno (1, _("Mark all articles up to here as unread")) <= 0)
7152      return;
7153    for_all_headers (undelete_header, 0);
7154 }
7155 
7156 /*}}}*/
7157 
undelete_header_cmd(void)7158 static void undelete_header_cmd (void) /*{{{*/
7159 {
7160    if ((Slrn_Current_Header->parent != NULL)/* in middle of thread */
7161        || (Slrn_Current_Header->child == NULL)/* At top with no child */
7162        /* or at top with child showing */
7163        || (0 == (Slrn_Current_Header->child->flags & HEADER_HIDDEN)))
7164      {
7165 	undelete_header (Slrn_Current_Header);
7166      }
7167    else
7168      {
7169 	for_this_tree (Slrn_Current_Header, undelete_header);
7170      }
7171    slrn_header_down_n (1, 0);
7172    Slrn_Full_Screen_Update = 1;
7173 }
7174 
7175 /*}}}*/
7176 
delete_header_cmd(void)7177 static void delete_header_cmd (void) /*{{{*/
7178 {
7179    if ((Slrn_Current_Header->parent != NULL)/* in middle of thread */
7180        || (Slrn_Current_Header->child == NULL)/* At top with no child */
7181        /* or at top with child showing */
7182        || (0 == (Slrn_Current_Header->child->flags & HEADER_HIDDEN)))
7183      {
7184 	delete_header (Slrn_Current_Header);
7185      }
7186    else
7187      {
7188 	for_this_tree (Slrn_Current_Header, delete_header);
7189      }
7190    slrn_next_unread_header (0);
7191    Slrn_Full_Screen_Update = 1;
7192 }
7193 
7194 /*}}}*/
7195 
request_header_cmd(void)7196 static void request_header_cmd (void) /*{{{*/
7197 {
7198    if ((Slrn_Current_Header->parent != NULL)/* in middle of thread */
7199        || (Slrn_Current_Header->child == NULL)/* At top with no child */
7200        /* or at top with child showing */
7201        || (0 == (Slrn_Current_Header->child->flags & HEADER_HIDDEN)))
7202      {
7203 	if (Slrn_Current_Header->flags & HEADER_REQUEST_BODY)
7204 	  slrn_unrequest_header (Slrn_Current_Header);
7205 	else
7206 	  slrn_request_header (Slrn_Current_Header);
7207      }
7208    else
7209      {
7210 	/* Unrequest only if all bodies in the thread were requested */
7211 	Slrn_Header_Type *h=Slrn_Current_Header, *next;
7212 	next = h->sister;
7213 	while (h != next)
7214 	  {
7215 	     if ((h->flags & HEADER_WITHOUT_BODY) &&
7216 		 !(h->flags & HEADER_REQUEST_BODY))
7217 	       {
7218 		  for_this_tree (Slrn_Current_Header, slrn_request_header);
7219 		  break;
7220 	       }
7221 	     h = h->next;
7222 	  }
7223 	if (h == next)
7224 	  for_this_tree (Slrn_Current_Header, slrn_unrequest_header);
7225      }
7226    slrn_header_down_n (1, 0);
7227    Slrn_Full_Screen_Update = 1;
7228 }
7229 /*}}}*/
7230 
thread_delete_cmd(void)7231 static void thread_delete_cmd (void) /*{{{*/
7232 {
7233    for_this_tree (Slrn_Current_Header, delete_header);
7234    delete_header_cmd ();
7235 }
7236 
7237 /*}}}*/
7238 
7239 /*}}}*/
7240 
7241 /*{{{ group_lens functions */
7242 #if SLRN_HAS_GROUPLENS
grouplens_rate_article(void)7243 static void grouplens_rate_article (void) /*{{{*/
7244 {
7245    int ch;
7246 
7247    if ((Slrn_Current_Header == NULL)
7248        || (Num_GroupLens_Rated == -1))
7249      return;
7250 
7251    slrn_message_now (_("Rate article (1-5):"));
7252 #if 0
7253    ch = SLang_getkey ();
7254 #else
7255    ch = slrn_getkey ();
7256 #endif
7257    if ((ch < '1') || (ch > '5'))
7258      {
7259 	slrn_error (_("Rating must be in range 1 to 5."));
7260 	return;
7261      }
7262 
7263    slrn_group_lens_rate_article (Slrn_Current_Header, ch - '0',
7264 				 (Article_Visible && (Header_Showing == Slrn_Current_Header)));
7265 }
7266 
7267 /*}}}*/
7268 
7269 #endif
7270 /*}}}*/
7271 /*{{{ mouse commands */
7272 
7273 /* actions for different regions:
7274  *	- top status line (help)
7275  *	- header status line
7276  *	- above header status line
7277  *	- below header status line
7278  *	- bottom status line
7279  */
art_mouse(void (* top_status)(void),void (* header_status)(void),void (* bot_status)(void),void (* normal_region)(void))7280 static void art_mouse (void (*top_status)(void), /*{{{*/
7281 		       void (*header_status)(void),
7282 		       void (*bot_status)(void),
7283 		       void (*normal_region)(void)
7284 		       )
7285 {
7286    int r, c;
7287    slrn_get_mouse_rc (&r, &c);
7288 
7289    /* take top status line into account */
7290    if (r == 1)
7291      {
7292 	if (Slrn_Use_Mouse)
7293 	  (void) slrn_execute_menu (c);
7294 	else if (NULL != top_status) (*top_status) ();
7295  	return;
7296      }
7297 
7298    if (r >= SLtt_Screen_Rows)
7299      return;
7300 
7301    /* On header status line */
7302    if (r - 2 == Header_Window_Nrows)
7303      {
7304 	if (NULL != header_status) (*header_status) ();
7305  	return;
7306      }
7307 
7308    /* bottom status line */
7309    if (r == SLtt_Screen_Rows - 1)
7310      {
7311 	if (NULL != bot_status) (*bot_status) ();
7312 	return;
7313      }
7314 
7315    if (r - 2 > Header_Window_Nrows)
7316      {
7317 	if (Slrn_Highlight_Urls && (c < 255))
7318 	  {
7319 	     SLsmg_Char_Type buf[256];
7320 	     char line[512];
7321 	     char *url = NULL;
7322 	     unsigned int len, i;
7323 
7324 	     SLsmg_gotorc (r - 1, 0);
7325 	     len = SLsmg_read_raw (buf, 256);
7326 
7327 	     i = (unsigned int) c;
7328 	     while (c && (SLSMG_EXTRACT_COLOR(buf[--c]) == URL_COLOR))
7329 	       {
7330 		  line[c] = (char) SLSMG_EXTRACT_CHAR(buf[c]);
7331 		  url = line + c;
7332 	       }
7333 	     while ((i < len) && (SLSMG_EXTRACT_COLOR(buf[i]) == URL_COLOR))
7334 	       {
7335 		  line[i] = (char) SLSMG_EXTRACT_CHAR(buf[i]);
7336 		  i++;
7337 	       }
7338 
7339 	     line[i] = '\0';
7340 
7341 	     if (url != NULL)
7342 	       {
7343 		  launch_url (url, (normal_region != hide_article));
7344 		  /* Hack: Don't ask when middle mouse key is used */
7345 		  return;
7346 	       }
7347 	  }
7348 
7349 	if (NULL != normal_region) (*normal_region) ();
7350  	return;
7351      }
7352 
7353    r -= (1 + Last_Cursor_Row);
7354    if (r < 0)
7355      {
7356 	r = -r;
7357 	if (r != (int) slrn_header_up_n (r, 0)) return;
7358      }
7359    else if (r != (int) slrn_header_down_n (r, 0)) return;
7360 
7361    select_article (0);
7362    /* if (NULL != normal_region) (*normal_region) (); */
7363 
7364 }
7365 
7366 /*}}}*/
7367 
art_mouse_left(void)7368 static void art_mouse_left (void) /*{{{*/
7369 {
7370    art_mouse (slrn_article_help, header_pagedn,
7371 	      art_next_unread, art_pagedn);
7372 }
7373 
7374 /*}}}*/
7375 
art_mouse_middle(void)7376 static void art_mouse_middle (void) /*{{{*/
7377 {
7378    art_mouse (toggle_header_formats, hide_article,
7379 	      toggle_quotes, hide_article);
7380 #if 1
7381    /* Make up for buggy rxvt which have problems with the middle key. */
7382    if (NULL != getenv ("COLORTERM"))
7383      {
7384 	if (SLang_input_pending (7))
7385 	  {
7386 	     while (SLang_input_pending (0))
7387 	       (void) SLang_getkey ();
7388 	  }
7389      }
7390 #endif
7391 }
7392 
7393 /*}}}*/
7394 
art_mouse_right(void)7395 static void art_mouse_right (void) /*{{{*/
7396 {
7397    art_mouse (slrn_article_help, header_pageup,
7398 	      art_prev_unread, art_pageup);
7399 }
7400 
7401 /*}}}*/
7402 
7403 /*}}}*/
7404 
7405 /*{{{ slrn_init_article_mode */
7406 
7407 #define A_KEY(s, f)  {s, (int (*)(void)) f}
7408 
7409 static SLKeymap_Function_Type Art_Functions [] = /*{{{*/
7410 {
7411    A_KEY("article_bob", art_bob),
7412    A_KEY("article_eob", art_eob),
7413    A_KEY("article_left", art_left),
7414    A_KEY("article_line_down", art_linedn),
7415    A_KEY("article_line_up", art_lineup),
7416    A_KEY("article_page_down", art_pagedn),
7417    A_KEY("article_page_up", art_pageup),
7418    A_KEY("article_right", art_right),
7419    A_KEY("article_search", article_search),
7420    A_KEY("author_search_backward", author_search_backward),
7421    A_KEY("author_search_forward", author_search_forward),
7422    A_KEY("browse_url", browse_url),
7423    A_KEY("cancel", cancel_article),
7424    A_KEY("catchup", catch_up_to_here),
7425    A_KEY("catchup_all", catch_up_all),
7426    A_KEY("create_score", create_score),
7427 #if SLRN_HAS_DECODE
7428    A_KEY("decode", decode_article),
7429 #endif
7430    A_KEY("delete", delete_header_cmd),
7431    A_KEY("delete_thread", thread_delete_cmd),
7432    A_KEY("digit_arg", slrn_digit_arg),
7433    A_KEY("enlarge_article_window", enlarge_window),
7434    A_KEY("evaluate_cmd", slrn_evaluate_cmd),
7435    A_KEY("exchange_mark", exchange_mark),
7436    A_KEY("expunge", art_xpunge),
7437    A_KEY("fast_quit", fast_quit),
7438    A_KEY("followup", followup),
7439    A_KEY("forward", forward_article),
7440    A_KEY("forward_digest", skip_digest_forward),
7441    A_KEY("get_children_headers", get_children_headers),
7442    A_KEY("get_parent_header", get_parent_header),
7443 #if SLRN_HAS_GROUPLENS
7444    A_KEY("grouplens_rate_article", grouplens_rate_article),
7445 #endif
7446    A_KEY("goto_article", goto_article),
7447    A_KEY("goto_last_read", goto_last_read),
7448    A_KEY("header_bob", header_bob),
7449    A_KEY("header_eob", header_eob),
7450    A_KEY("header_line_down", header_down),
7451    A_KEY("header_line_up", header_up),
7452    A_KEY("header_page_down", header_pagedn),
7453    A_KEY("header_page_up", header_pageup),
7454    A_KEY("help", slrn_article_help),
7455    A_KEY("hide_article", hide_article),
7456    A_KEY("locate_article", locate_header_by_msgid),
7457    A_KEY("mark_spot", mark_spot),
7458    A_KEY("next", art_next_unread),
7459    A_KEY("next_high_score", next_high_score),
7460    A_KEY("next_same_subject", next_header_same_subject),
7461    A_KEY("pipe", pipe_article),
7462    A_KEY("post", slrn_post_cmd),
7463    A_KEY("post_postponed", slrn_post_postponed),
7464    A_KEY("previous", art_prev_unread),
7465    A_KEY("print", print_article_cmd),
7466    A_KEY("quit", art_quit),
7467    A_KEY("redraw", slrn_redraw),
7468    A_KEY("repeat_last_key", slrn_repeat_last_key),
7469    A_KEY("reply", reply_cmd),
7470    A_KEY("request", request_header_cmd),
7471    A_KEY("save", save_article),
7472 #if SLRN_HAS_SPOILERS
7473    A_KEY("show_spoilers", show_spoilers),
7474 #endif
7475    A_KEY("shrink_article_window", shrink_window),
7476    A_KEY("skip_quotes", skip_quoted_text),
7477    A_KEY("skip_to_next_group", skip_to_next_group_1),
7478    A_KEY("skip_to_previous_group", skip_to_prev_group),
7479    A_KEY("subject_search_backward", subject_search_backward),
7480    A_KEY("subject_search_forward", subject_search_forward),
7481    A_KEY("supersede", supersede),
7482    A_KEY("suspend", art_suspend_cmd),
7483    A_KEY("tag_header", num_tag_header),
7484    A_KEY("toggle_collapse_threads", toggle_collapse_threads),
7485    A_KEY("toggle_header_formats", toggle_header_formats),
7486    A_KEY("toggle_header_tag", toggle_header_tag),
7487    A_KEY("toggle_headers", toggle_headers),
7488    A_KEY("toggle_pgpsignature", toggle_pgp_signature),
7489    A_KEY("toggle_quotes", toggle_quotes),
7490    A_KEY("toggle_rot13", toggle_rot13),
7491    A_KEY("toggle_signature", toggle_signature),
7492    A_KEY("toggle_verbatim_text", toggle_verbatim),
7493    A_KEY("toggle_verbatim_marks", toggle_verbatim_marks),
7494    A_KEY("uncatchup", un_catch_up_to_here),
7495    A_KEY("uncatchup_all", un_catch_up_all),
7496    A_KEY("undelete", undelete_header_cmd),
7497    A_KEY("untag_headers", num_untag_headers),
7498    A_KEY("wrap_article", toggle_wrap_article),
7499    A_KEY("zoom_article_window", zoom_article_window),
7500    A_KEY("view_scores", view_scores),
7501 #if 1 /* FIXME: These ones are going to be deleted before 1.0 */
7502    A_KEY("art_bob", art_bob),
7503    A_KEY("art_eob", art_eob),
7504    A_KEY("art_xpunge", art_xpunge),
7505    A_KEY("article_linedn", art_linedn),
7506    A_KEY("article_lineup", art_lineup),
7507    A_KEY("article_pagedn", art_pagedn),
7508    A_KEY("article_pageup", art_pageup),
7509    A_KEY("down", header_down),
7510    A_KEY("enlarge_window", enlarge_window),
7511    A_KEY("goto_beginning", art_bob),
7512    A_KEY("goto_end", art_eob),
7513    A_KEY("left", art_left),
7514    A_KEY("locate_header_by_msgid", locate_header_by_msgid),
7515    A_KEY("pagedn", header_pagedn),
7516    A_KEY("pageup", header_pageup),
7517    A_KEY("pipe_article", pipe_article),
7518    A_KEY("prev", art_prev_unread),
7519    A_KEY("print_article", print_article_cmd),
7520    A_KEY("right", art_right),
7521    A_KEY("scroll_dn", art_pagedn),
7522    A_KEY("scroll_up", art_pageup),
7523    A_KEY("shrink_window", shrink_window),
7524    A_KEY("skip_to_prev_group", skip_to_prev_group),
7525    A_KEY("toggle_show_author", toggle_header_formats),
7526    A_KEY("toggle_sort", _art_toggle_sort),
7527    A_KEY("up", header_up),
7528 #endif
7529    A_KEY(NULL, NULL)
7530 };
7531 
7532 /*}}}*/
7533 
7534 static Slrn_Mode_Type Art_Mode_Cap = /*{{{*/
7535 {
7536    NULL,			       /* keymap */
7537    art_update_screen,
7538    art_winch_sig,		       /* sigwinch_fun */
7539    slrn_art_hangup,
7540    NULL,			       /* enter_mode_hook */
7541    SLRN_ARTICLE_MODE,
7542 };
7543 
7544 /*}}}*/
7545 
slrn_init_article_mode(void)7546 void slrn_init_article_mode (void) /*{{{*/
7547 {
7548    char  *err = _("Unable to create Article keymap!");
7549    char numbuf[2];
7550    char ch;
7551 
7552    if (NULL == (Slrn_Article_Keymap = SLang_create_keymap ("article", NULL)))
7553      slrn_exit_error ("%s", err);
7554 
7555    Art_Mode_Cap.keymap = Slrn_Article_Keymap;
7556 
7557    Slrn_Article_Keymap->functions = Art_Functions;
7558 
7559    numbuf[1] = 0;
7560 
7561    for (ch = '0'; ch <= '9'; ch++)
7562      {
7563 	numbuf[0] = ch;
7564 	SLkm_define_key (numbuf, (FVOID_STAR) goto_header_number, Slrn_Article_Keymap);
7565      }
7566 #if SLRN_HAS_GROUPLENS
7567    numbuf[0] = '0';
7568    /* Steal '0' for use as a prefix for rating. */
7569    SLkm_define_key  (numbuf, (FVOID_STAR) grouplens_rate_article, Slrn_Article_Keymap);
7570 #endif
7571 
7572    SLkm_define_key  ("\033l", (FVOID_STAR) locate_header_by_msgid, Slrn_Article_Keymap);
7573    SLkm_define_key ("\0331", (FVOID_STAR) slrn_digit_arg, Slrn_Article_Keymap);
7574    SLkm_define_key ("\0332", (FVOID_STAR) slrn_digit_arg, Slrn_Article_Keymap);
7575    SLkm_define_key ("\0333", (FVOID_STAR) slrn_digit_arg, Slrn_Article_Keymap);
7576    SLkm_define_key ("\0334", (FVOID_STAR) slrn_digit_arg, Slrn_Article_Keymap);
7577    SLkm_define_key ("\0335", (FVOID_STAR) slrn_digit_arg, Slrn_Article_Keymap);
7578    SLkm_define_key ("\0336", (FVOID_STAR) slrn_digit_arg, Slrn_Article_Keymap);
7579    SLkm_define_key ("\0337", (FVOID_STAR) slrn_digit_arg, Slrn_Article_Keymap);
7580    SLkm_define_key ("\0338", (FVOID_STAR) slrn_digit_arg, Slrn_Article_Keymap);
7581    SLkm_define_key ("\0339", (FVOID_STAR) slrn_digit_arg, Slrn_Article_Keymap);
7582    SLkm_define_key ("\0330", (FVOID_STAR) slrn_digit_arg, Slrn_Article_Keymap);
7583    SLkm_define_key  ("*", (FVOID_STAR) toggle_header_tag, Slrn_Article_Keymap);
7584    SLkm_define_key  ("#", (FVOID_STAR) num_tag_header, Slrn_Article_Keymap);
7585    SLkm_define_key  ("\033#", (FVOID_STAR) num_untag_headers, Slrn_Article_Keymap);
7586 #if SLRN_HAS_DECODE
7587    SLkm_define_key  (":", (FVOID_STAR) decode_article, Slrn_Article_Keymap);
7588 #endif
7589    SLkm_define_key  (" ", (FVOID_STAR) art_pagedn, Slrn_Article_Keymap);
7590    SLkm_define_key  ("!", (FVOID_STAR) next_high_score, Slrn_Article_Keymap);
7591    SLkm_define_key  (",", (FVOID_STAR) exchange_mark, Slrn_Article_Keymap);
7592    SLkm_define_key  (".", (FVOID_STAR) slrn_repeat_last_key, Slrn_Article_Keymap);
7593    SLkm_define_key  ("/", (FVOID_STAR) article_search, Slrn_Article_Keymap);
7594    SLkm_define_key  ("\\", (FVOID_STAR) toggle_signature, Slrn_Article_Keymap);
7595    SLkm_define_key  ("{", (FVOID_STAR) toggle_verbatim, Slrn_Article_Keymap);
7596    SLkm_define_key  ("[", (FVOID_STAR) toggle_verbatim_marks, Slrn_Article_Keymap);
7597    SLkm_define_key  ("]", (FVOID_STAR) toggle_pgp_signature, Slrn_Article_Keymap);
7598    SLkm_define_key  (";", (FVOID_STAR) mark_spot, Slrn_Article_Keymap);
7599    SLkm_define_key  ("<", (FVOID_STAR) art_bob, Slrn_Article_Keymap);
7600    SLkm_define_key  ("=", (FVOID_STAR) next_header_same_subject, Slrn_Article_Keymap);
7601    SLkm_define_key  (">", (FVOID_STAR) art_eob, Slrn_Article_Keymap);
7602    SLkm_define_key  ("?", (FVOID_STAR) slrn_article_help, Slrn_Article_Keymap);
7603    SLkm_define_key  ("A", (FVOID_STAR) author_search_backward, Slrn_Article_Keymap);
7604    SLkm_define_key  ("F", (FVOID_STAR) forward_article, Slrn_Article_Keymap);
7605    SLkm_define_key  ("H", (FVOID_STAR) hide_article, Slrn_Article_Keymap);
7606    SLkm_define_key  ("K", (FVOID_STAR) create_score, Slrn_Article_Keymap);
7607    SLkm_define_key  ("L", (FVOID_STAR) goto_last_read, Slrn_Article_Keymap);
7608    SLkm_define_key  ("N", (FVOID_STAR) skip_to_next_group_1, Slrn_Article_Keymap);
7609    SLkm_define_key  ("P", (FVOID_STAR) slrn_post_cmd, Slrn_Article_Keymap);
7610    SLkm_define_key  ("Q", (FVOID_STAR) fast_quit, Slrn_Article_Keymap);
7611    SLkm_define_key  ("S", (FVOID_STAR) subject_search_backward, Slrn_Article_Keymap);
7612    SLkm_define_key  ("T", (FVOID_STAR) toggle_quotes, Slrn_Article_Keymap);
7613    SLkm_define_key  ("U", (FVOID_STAR) browse_url, Slrn_Article_Keymap);
7614    SLkm_define_key  ("W", (FVOID_STAR) toggle_wrap_article, Slrn_Article_Keymap);
7615    SLkm_define_key  ("\033^C", (FVOID_STAR) cancel_article, Slrn_Article_Keymap);
7616    SLkm_define_key  ("\033^P", (FVOID_STAR) get_children_headers, Slrn_Article_Keymap);
7617    SLkm_define_key  ("\033^S", (FVOID_STAR) supersede, Slrn_Article_Keymap);
7618    SLkm_define_key  ("\033a", (FVOID_STAR) toggle_header_formats, Slrn_Article_Keymap);
7619    SLkm_define_key  ("\033d", (FVOID_STAR) thread_delete_cmd, Slrn_Article_Keymap);
7620    SLkm_define_key  ("\033p", (FVOID_STAR) get_parent_header, Slrn_Article_Keymap);
7621    SLkm_define_key  ("\033S", (FVOID_STAR) _art_toggle_sort, Slrn_Article_Keymap);
7622    SLkm_define_key  ("\033t", (FVOID_STAR) toggle_collapse_threads, Slrn_Article_Keymap);
7623    SLkm_define_key  ("\r", (FVOID_STAR) art_linedn, Slrn_Article_Keymap);
7624    SLkm_define_key  ("\t", (FVOID_STAR) skip_quoted_text, Slrn_Article_Keymap);
7625    SLkm_define_key  ("^L", (FVOID_STAR) slrn_redraw, Slrn_Article_Keymap);
7626    SLkm_define_key  ("^P", (FVOID_STAR) header_up, Slrn_Article_Keymap);
7627    SLkm_define_key  ("^R", (FVOID_STAR) slrn_redraw, Slrn_Article_Keymap);
7628    SLkm_define_key  ("^X^[", (FVOID_STAR) slrn_evaluate_cmd, Slrn_Article_Keymap);
7629    SLkm_define_key  ("^Z", (FVOID_STAR) art_suspend_cmd, Slrn_Article_Keymap);
7630    SLkm_define_key  ("a", (FVOID_STAR) author_search_forward, Slrn_Article_Keymap);
7631    SLkm_define_key  ("b", (FVOID_STAR) art_pageup, Slrn_Article_Keymap);
7632    SLkm_define_key  ("d", (FVOID_STAR) delete_header_cmd, Slrn_Article_Keymap);
7633    SLkm_define_key  ("f", (FVOID_STAR) followup, Slrn_Article_Keymap);
7634    SLkm_define_key  ("g", (FVOID_STAR) skip_digest_forward, Slrn_Article_Keymap);
7635    SLkm_define_key  ("j", (FVOID_STAR) goto_article, Slrn_Article_Keymap);
7636    SLkm_define_key  ("m", (FVOID_STAR) request_header_cmd, Slrn_Article_Keymap);
7637    SLkm_define_key  ("n", (FVOID_STAR) art_next_unread, Slrn_Article_Keymap);
7638    SLkm_define_key  ("o", (FVOID_STAR) save_article, Slrn_Article_Keymap);
7639    SLkm_define_key  ("p", (FVOID_STAR) art_prev_unread, Slrn_Article_Keymap);
7640    SLkm_define_key  ("q", (FVOID_STAR) art_quit, Slrn_Article_Keymap);
7641    SLkm_define_key  ("r", (FVOID_STAR) reply_cmd, Slrn_Article_Keymap);
7642    SLkm_define_key  ("s", (FVOID_STAR) subject_search_forward, Slrn_Article_Keymap);
7643    SLkm_define_key  ("t", (FVOID_STAR) toggle_headers, Slrn_Article_Keymap);
7644    SLkm_define_key  ("u", (FVOID_STAR) undelete_header_cmd, Slrn_Article_Keymap);
7645    SLkm_define_key  ("v", (FVOID_STAR) view_scores, Slrn_Article_Keymap);
7646    SLkm_define_key  ("x", (FVOID_STAR) art_xpunge, Slrn_Article_Keymap);
7647    SLkm_define_key  ("y", (FVOID_STAR) print_article_cmd, Slrn_Article_Keymap);
7648    SLkm_define_key  ("|", (FVOID_STAR) pipe_article, Slrn_Article_Keymap);
7649 #if defined(IBMPC_SYSTEM)
7650    SLkm_define_key  ("^@S", (FVOID_STAR) art_pageup, Slrn_Article_Keymap);
7651    SLkm_define_key  ("\xE0S", (FVOID_STAR) art_pageup, Slrn_Article_Keymap);
7652 #else
7653    SLkm_define_key  ("^?", (FVOID_STAR) art_pageup, Slrn_Article_Keymap);
7654 #endif
7655 #if defined(IBMPC_SYSTEM)
7656    SLkm_define_key  ("\033^@H", (FVOID_STAR) art_lineup, Slrn_Article_Keymap);
7657    SLkm_define_key  ("\033\xE0H", (FVOID_STAR) art_lineup, Slrn_Article_Keymap);
7658    SLkm_define_key  ("\033^@P", (FVOID_STAR) art_linedn, Slrn_Article_Keymap);
7659    SLkm_define_key  ("\033\xE0P", (FVOID_STAR) art_linedn, Slrn_Article_Keymap);
7660    SLkm_define_key  ("\033^@M", (FVOID_STAR) skip_to_next_group_1, Slrn_Article_Keymap);
7661    SLkm_define_key  ("\033\xE0M", (FVOID_STAR) skip_to_next_group_1, Slrn_Article_Keymap);
7662    SLkm_define_key  ("\033^@K", (FVOID_STAR) skip_to_prev_group, Slrn_Article_Keymap);
7663    SLkm_define_key  ("\033\xE0K", (FVOID_STAR) skip_to_prev_group, Slrn_Article_Keymap);
7664    SLkm_define_key  ("^@H", (FVOID_STAR) header_up, Slrn_Article_Keymap);
7665    SLkm_define_key  ("\xE0H", (FVOID_STAR) header_up, Slrn_Article_Keymap);
7666    SLkm_define_key  ("^@P", (FVOID_STAR) header_down, Slrn_Article_Keymap);
7667    SLkm_define_key  ("\xE0P", (FVOID_STAR) header_down, Slrn_Article_Keymap);
7668    SLkm_define_key  ("^@M", (FVOID_STAR) art_right, Slrn_Article_Keymap);
7669    SLkm_define_key  ("\xE0M", (FVOID_STAR) art_right, Slrn_Article_Keymap);
7670    SLkm_define_key  ("^@K", (FVOID_STAR) art_left, Slrn_Article_Keymap);
7671    SLkm_define_key  ("\xE0K", (FVOID_STAR) art_left, Slrn_Article_Keymap);
7672 #else
7673    SLkm_define_key  ("\033\033[A", (FVOID_STAR) art_lineup, Slrn_Article_Keymap);
7674    SLkm_define_key  ("\033\033OA", (FVOID_STAR) art_lineup, Slrn_Article_Keymap);
7675    SLkm_define_key  ("\033\033[B", (FVOID_STAR) art_linedn, Slrn_Article_Keymap);
7676    SLkm_define_key  ("\033\033OB", (FVOID_STAR) art_linedn, Slrn_Article_Keymap);
7677    SLkm_define_key  ("\033\033[C", (FVOID_STAR) skip_to_next_group_1, Slrn_Article_Keymap);
7678    SLkm_define_key  ("\033\033OC", (FVOID_STAR) skip_to_next_group_1, Slrn_Article_Keymap);
7679    SLkm_define_key  ("\033\033[D", (FVOID_STAR) skip_to_prev_group, Slrn_Article_Keymap);
7680    SLkm_define_key  ("\033\033OD", (FVOID_STAR) skip_to_prev_group, Slrn_Article_Keymap);
7681    SLkm_define_key  ("\033[A", (FVOID_STAR) header_up, Slrn_Article_Keymap);
7682    SLkm_define_key  ("\033OA", (FVOID_STAR) header_up, Slrn_Article_Keymap);
7683    SLkm_define_key  ("\033[B", (FVOID_STAR) header_down, Slrn_Article_Keymap);
7684    SLkm_define_key  ("\033OB", (FVOID_STAR) header_down, Slrn_Article_Keymap);
7685    SLkm_define_key  ("\033[C", (FVOID_STAR) art_right, Slrn_Article_Keymap);
7686    SLkm_define_key  ("\033OC", (FVOID_STAR) art_right, Slrn_Article_Keymap);
7687    SLkm_define_key  ("\033[D", (FVOID_STAR) art_left, Slrn_Article_Keymap);
7688    SLkm_define_key  ("\033OD", (FVOID_STAR) art_left, Slrn_Article_Keymap);
7689 #endif
7690    SLkm_define_key  ("^U", (FVOID_STAR) header_pageup, Slrn_Article_Keymap);
7691    SLkm_define_key  ("\033V", (FVOID_STAR) header_pageup, Slrn_Article_Keymap);
7692 #if defined(IBMPC_SYSTEM)
7693    SLkm_define_key  ("^@I", (FVOID_STAR) header_pageup, Slrn_Article_Keymap);
7694    SLkm_define_key  ("\xE0I", (FVOID_STAR) header_pageup, Slrn_Article_Keymap);
7695    SLkm_define_key  ("^@Q", (FVOID_STAR) header_pagedn, Slrn_Article_Keymap);
7696    SLkm_define_key  ("\xE0Q", (FVOID_STAR) header_pagedn, Slrn_Article_Keymap);
7697 #else
7698    SLkm_define_key  ("\033[5~", (FVOID_STAR) header_pageup, Slrn_Article_Keymap);
7699    SLkm_define_key  ("\033[I", (FVOID_STAR) header_pageup, Slrn_Article_Keymap);
7700    SLkm_define_key  ("\033[6~", (FVOID_STAR) header_pagedn, Slrn_Article_Keymap);
7701    SLkm_define_key  ("\033[G", (FVOID_STAR) header_pagedn, Slrn_Article_Keymap);
7702 #endif
7703    SLkm_define_key  ("^D", (FVOID_STAR) header_pagedn, Slrn_Article_Keymap);
7704    SLkm_define_key  ("^V", (FVOID_STAR) header_pagedn, Slrn_Article_Keymap);
7705    SLkm_define_key  ("\033>", (FVOID_STAR) header_eob, Slrn_Article_Keymap);
7706    SLkm_define_key  ("\033<", (FVOID_STAR) header_bob, Slrn_Article_Keymap);
7707    SLkm_define_key  ("c", (FVOID_STAR) catch_up_all, Slrn_Article_Keymap);
7708    SLkm_define_key  ("\033c", (FVOID_STAR) catch_up_all, Slrn_Article_Keymap);
7709    SLkm_define_key  ("\033u", (FVOID_STAR) un_catch_up_all, Slrn_Article_Keymap);
7710    SLkm_define_key  ("\033C", (FVOID_STAR) catch_up_to_here, Slrn_Article_Keymap);
7711    SLkm_define_key  ("C", (FVOID_STAR) catch_up_to_here, Slrn_Article_Keymap);
7712    SLkm_define_key  ("\033U", (FVOID_STAR) un_catch_up_to_here, Slrn_Article_Keymap);
7713    SLkm_define_key  ("\033R", (FVOID_STAR) toggle_rot13, Slrn_Article_Keymap);
7714 #if 1
7715 #if SLRN_HAS_SPOILERS
7716    SLkm_define_key  ("\033?", (FVOID_STAR) show_spoilers, Slrn_Article_Keymap);
7717 #endif
7718 #endif
7719    SLkm_define_key  ("^N", (FVOID_STAR) header_down, Slrn_Article_Keymap);
7720    SLkm_define_key  ("^", (FVOID_STAR) enlarge_window, Slrn_Article_Keymap);
7721    SLkm_define_key  ("^^", (FVOID_STAR) shrink_window, Slrn_Article_Keymap);
7722    SLkm_define_key  ("\033P", (FVOID_STAR) slrn_post_postponed, Slrn_Article_Keymap);
7723 
7724    /* mouse (left/middle/right) */
7725    SLkm_define_key  ("\033[M\040", (FVOID_STAR) art_mouse_left, Slrn_Article_Keymap);
7726    SLkm_define_key  ("\033[M\041", (FVOID_STAR) art_mouse_middle, Slrn_Article_Keymap);
7727    SLkm_define_key  ("\033[M\042", (FVOID_STAR) art_mouse_right, Slrn_Article_Keymap);
7728 
7729    SLkm_define_key  ("z", (FVOID_STAR) zoom_article_window, Slrn_Article_Keymap);
7730 
7731    if (SLang_get_error ()) slrn_exit_error ("%s", err);
7732 }
7733 
7734 /*}}}*/
7735 
7736 /*}}}*/
7737 /*{{{ slrn_article_mode and support functions */
7738 
slrn_art_hangup(int sig)7739 static void slrn_art_hangup (int sig) /*{{{*/
7740 {
7741    (void) sig;
7742    if (Slrn_Current_Header != NULL)
7743      undelete_header_cmd ();		       /* in case we are reading one */
7744    art_quit ();
7745 }
7746 
7747 /*}}}*/
7748 
mark_ranges(Slrn_Range_Type * r,int flag)7749 static void mark_ranges (Slrn_Range_Type *r, int flag) /*{{{*/
7750 {
7751    Slrn_Header_Type *h = Slrn_First_Header;
7752 
7753    while ((r != NULL) && (h != NULL))
7754      {
7755 	NNTP_Artnum_Type min = r->min;
7756 	NNTP_Artnum_Type max = r->max;
7757 	while (h != NULL)
7758 	  {
7759 	     if (h->number < min)
7760 	       {
7761 		  h = h->real_next;
7762 		  continue;
7763 	       }
7764 
7765 	     if (h->number > max)
7766 	       {
7767 		  break;
7768 	       }
7769 
7770 	     if ((flag != HEADER_REQUEST_BODY) ||
7771 		 (h->flags & HEADER_WITHOUT_BODY))
7772 	       h->flags |= flag;
7773 	     if (flag == HEADER_READ)
7774 	       Number_Read++;
7775 	     h = h->real_next;
7776 	  }
7777 	r = r->next;
7778      }
7779 }
7780 
7781 /*}}}*/
7782 
slrn_set_header_flags(Slrn_Header_Type * h,unsigned int flags)7783 void slrn_set_header_flags (Slrn_Header_Type *h, unsigned int flags) /*{{{*/
7784 {
7785    if (h->flags & HEADER_READ) Number_Read--;
7786    if (flags & HEADER_READ) Number_Read++;
7787    h->flags &= ~HEADER_HARMLESS_FLAGS_MASK;
7788    h->flags |= (flags & HEADER_HARMLESS_FLAGS_MASK);
7789 }
7790 /*}}}*/
7791 
7792 /* If all > 0, get last 'all' headers from server independent of whether
7793  *             they have been read or not.
7794  * If all < 0, and this is not the first time this group has been accessed,
7795  *             either during this session or during previous sessions, get
7796  *             that last 'all' UNREAD articles.
7797  * Otherwise,  fetch ALL UNREAD headers from the server.
7798  */
slrn_select_article_mode(Slrn_Group_Type * g,NNTP_Artnum_Type all,int score)7799 int slrn_select_article_mode (Slrn_Group_Type *g, NNTP_Artnum_Type all, int score) /*{{{*/
7800 {
7801    NNTP_Artnum_Type min, max;
7802    NNTP_Artnum_Type smin, smax;
7803    Slrn_Range_Type *r;
7804    int status;
7805 
7806    slrn_init_graphic_chars ();
7807 
7808    Header_Window_HScroll = 0;
7809    User_Aborted_Group_Read = 0;
7810    _art_Headers = Slrn_First_Header = NULL;
7811    _art_Threads_Collapsed = 0;
7812    Same_Subject_Start_Header = NULL;
7813    Number_Read = 0;
7814    Number_Total = 0;
7815 
7816    init_scoring ();
7817    delete_hash_table ();
7818    Number_Killed = 0;
7819 
7820    Current_Group = g;
7821    r = &g->range;
7822    Slrn_Current_Group_Name = g->group_name;
7823 
7824    if (Slrn_Server_Obj->sv_reset_has_xover)
7825      Slrn_Server_Obj->sv_has_xover = 1;
7826 
7827    slrn_run_hooks (HOOK_PRE_ARTICLE_MODE, 0);
7828    if (SLang_get_error ())
7829      return -1;
7830 
7831    if (score && (1 == slrn_open_score (Slrn_Current_Group_Name)))
7832      Perform_Scoring = 1;
7833    else Perform_Scoring = 0;
7834 
7835    Slrn_Server_Min = r->min;
7836    Slrn_Server_Max = r->max;
7837    r = r->next;
7838    /* Now r points to ranges already read.  */
7839 
7840    status = 0;
7841 
7842    if (all > 0)
7843      {
7844 	min = Slrn_Server_Max - all + 1;
7845 	if (min < Slrn_Server_Min) min = Slrn_Server_Min;
7846 	status = get_headers (min, Slrn_Server_Max, &all);
7847 	if (status != -1)
7848 	  mark_ranges (r, HEADER_READ);
7849      }
7850    else
7851      {
7852 	if ((all < 0) && (r != NULL))
7853 	  {
7854 	     NNTP_Artnum_Type unread;
7855 
7856 	     /* This condition will occur when the user wants to read unread
7857 	      * articles that occur in a gap, i.e., RRRUUUUURRRUUUUUUU and
7858 	      * we need to dig back far enough below the last group of read
7859 	      * ones until we have retrieved abs(all) articles.
7860 	      *
7861 	      * The problem with this is that some articles may not be
7862 	      * available on the server which means that the number to
7863 	      * go back will be under estimated.
7864 	      */
7865 	     all = -all;
7866 
7867 	     while (r->next != NULL) r = r->next;
7868 	     /* Go back through previously read articles counting unread.
7869 	      * If number unread becomes greater than the number that we
7870 	      * intend to read, then we know where to start querying
7871 	      * the server.
7872 	      */
7873 	     unread = 0;
7874 	     max = Slrn_Server_Max;
7875 	     while (r->prev != NULL)
7876 	       {
7877 		  unread += max - r->max;
7878 		  if (unread >= all) break;
7879 		  max = r->min - 1;
7880 		  r = r->prev;
7881 	       }
7882 	     if (r->prev == NULL)
7883 	       unread += max;
7884 
7885 	     if (unread >= all)
7886 	       {
7887 		  /* This may be problematic if some articles are missing on
7888 		   * the server.  If that is the case, smin will be to high
7889 		   * and we will fall short of the goal.
7890 		   */
7891 		  if (r->prev != NULL)
7892 		    smin = r->max + (unread - all) + 1;
7893 		  else
7894 		    smin = unread - all + 1;
7895 	       }
7896 	     else smin = Slrn_Server_Min;
7897 	     smax = Slrn_Server_Max;
7898 	     r = r->next;
7899 	  }
7900 	else
7901 	  {
7902 	     /* all == 0, or no previously read articles. */
7903 	     smin = Slrn_Server_Min;
7904 	     smax = Slrn_Server_Max;
7905 	     if (r != NULL)
7906 	       {
7907 		  Slrn_Range_Type *r1;
7908 
7909 		  /* Estimate how many are available to read */
7910 		  all = smax - r->max;
7911 #if 0				       /* is this correct?? */
7912 		  all++;
7913 #endif
7914 
7915 		  /* Now subtract the ones that we have already read. */
7916 		  r1 = r->next;
7917 		  while (r1 != NULL)
7918 		    {
7919 		       all -= (r1->max - r1->min) + 1;
7920 		       r1 = r1->next;
7921 		    }
7922 		  /* This condition should never arise */
7923 		  if (all == 0) all = smax - smin + 1;
7924 	       }
7925 	     else all = smax - smin + 1;
7926 	  }
7927 
7928 	while (r != NULL)
7929 	  {
7930 	     if (r->min > smin)
7931 	       {
7932 		  min = smin;
7933 		  max = r->min - 1;
7934 
7935 		  status = get_headers (min, max, &all);
7936 
7937 		  if (status == -1)
7938 		    break;
7939 
7940 		  if (status == 0)
7941 		    {
7942 		       Slrn_Groups_Dirty = 1;
7943 		       r->min = min;
7944 		    }
7945 
7946 		  smin = r->max + 1;
7947 	       }
7948 	     else
7949 	       {
7950 		  smin = r->max + 1;
7951 	       }
7952 	     r = r->next;
7953 	  }
7954 
7955 	if (smin <= smax)
7956 	  {
7957 	     status = get_headers (smin, smax, &all);
7958 	  }
7959      }
7960 
7961    slrn_close_add_xover (1);
7962 
7963 #if SLRN_HAS_SPOOL_SUPPORT
7964    if (Slrn_Server_Id == SLRN_SERVER_ID_SPOOL)
7965      {
7966 	r = slrn_spool_get_no_body_ranges (Slrn_Current_Group_Name);
7967 	mark_ranges (r, HEADER_WITHOUT_BODY);
7968 	slrn_ranges_free (r);
7969 
7970 	slrn_ranges_free (g->requests);
7971 	r = slrn_spool_get_requested_ranges (Slrn_Current_Group_Name);
7972 	mark_ranges (r, HEADER_REQUEST_BODY);
7973 	g->requests = r;
7974 	g->requests_loaded = 1;
7975      }
7976 #endif
7977 
7978    if ((status == -1) || SLKeyBoard_Quit)
7979      {
7980 	if (SLang_get_error () == USER_BREAK)
7981 	  slrn_error_now (0, _("Group transfer aborted."));
7982 	else
7983 	  slrn_error_now (0, _("Server read failed."));
7984 
7985 	/* This means that we cannot update ranges for this group because
7986 	 * the user aborted and update_ranges assumes that all articles
7987 	 * upto server max are present.
7988 	 */
7989 	User_Aborted_Group_Read = 1;
7990 	art_quit ();
7991      }
7992    else if (Perform_Scoring)
7993      score_headers (1);
7994 
7995    if (_art_Headers == NULL)
7996      {
7997 	slrn_close_score ();
7998 	slrn_clear_requested_headers ();
7999 	if (Number_Killed)
8000 	  slrn_error (_("No unread articles found. (%d killed)"), Number_Killed);
8001 	else
8002 	  slrn_error (_("No unread articles found."));
8003 
8004 	free_kill_lists_and_update ();
8005 	Slrn_Current_Group_Name = NULL;
8006 	if ((SLang_get_error () == USER_BREAK) || (all != 0)) return -1;
8007 	else return -2;
8008      }
8009 
8010    make_hash_table ();
8011 
8012    /* This must go here to fix up the next/prev pointers */
8013    _art_sort_by_server_number ();
8014 
8015    slrn_push_mode (&Art_Mode_Cap);
8016 
8017    Slrn_Current_Header = _art_Headers;
8018    Last_Cursor_Row = 0;
8019    Mark_Header = NULL;
8020    init_header_window_struct ();
8021 
8022    At_End_Of_Article = NULL;
8023    Header_Showing = NULL;
8024    SLMEMSET ((char *) &Slrn_Article_Window, 0, sizeof (SLscroll_Window_Type));
8025 
8026    set_article_visibility (0);
8027 
8028 #if SLRN_HAS_GROUPLENS
8029    /* slrn_set_suspension (1); */
8030    Num_GroupLens_Rated = slrn_get_grouplens_scores ();
8031    /* slrn_set_suspension (0); */
8032 #endif
8033 
8034    (void) slrn_run_hooks (HOOK_ARTICLE_MODE, 0);
8035    slrn_sort_headers ();
8036    header_bob ();
8037 
8038    quick_help ();
8039 
8040    (void) slrn_run_hooks (HOOK_ARTICLE_MODE_STARTUP, 0);
8041 
8042    if (Slrn_Startup_With_Article) art_pagedn ();
8043    if (SLang_get_error () == 0)
8044      {
8045 	if (Perform_Scoring
8046 	    /* && (Number_Killed || Number_High_Scored) */
8047 	    )
8048 	  {
8049 #if SLRN_HAS_GROUPLENS
8050 	     if (Num_GroupLens_Rated != -1)
8051 	       {
8052 		  slrn_message (_("Num Killed: %u, Num High: %u, Num Low: %u, Num GroupLens Rated: %d"),
8053 				Number_Score_Killed, Number_High_Scored, Number_Low_Scored,
8054 				Num_GroupLens_Rated);
8055 	       }
8056 	     else
8057 #endif
8058 	       slrn_message (_("Num Killed: %u, Num High: %u, Num Low: %u"),
8059 			     Number_Score_Killed, Number_High_Scored, Number_Low_Scored);
8060 	  }
8061 
8062 	else slrn_clear_message ();
8063      }
8064 
8065 /*   Number_Low_Scored = Number_Score_Killed = Number_High_Scored = 0;*/
8066    return 0;
8067 }
8068 
8069 /*}}}*/
8070 
8071 /*}}}*/
8072 
8073 /*{{{ screen update functions */
8074 
8075 static char *Header_Display_Formats [SLRN_MAX_DISPLAY_FORMATS];
8076 static unsigned int Header_Format_Number;
8077 static char *Default_Header_Format = "%F%B%-5S%G%-5l:[%12r]%t%s";
8078 
slrn_set_header_format(unsigned int num,char * fmt)8079 int slrn_set_header_format (unsigned int num, char *fmt)
8080 {
8081    return slrn_set_display_format (Header_Display_Formats, num, fmt);
8082 }
8083 
slrn_get_header_format(int num)8084 char *slrn_get_header_format (int num)
8085 {
8086    char *fmt;
8087 
8088    if ((num < 0) || (num >= SLRN_MAX_DISPLAY_FORMATS))
8089      num = (int) Header_Format_Number;
8090 
8091    fmt = Header_Display_Formats[num];
8092    if ((fmt == NULL) || (*fmt == 0))
8093      fmt = Default_Header_Format;
8094 
8095    return fmt;
8096 }
8097 
toggle_header_formats(void)8098 static void toggle_header_formats (void)
8099 {
8100    Header_Format_Number = slrn_toggle_format (Header_Display_Formats,
8101 					      Header_Format_Number);
8102 }
8103 
smg_write_string(char * s)8104 static void smg_write_string (char *s)
8105 {
8106    slrn_write_nbytes (s, strlen (s));
8107 }
8108 
smg_write_byte(char c)8109 static void smg_write_byte (char c)
8110 {
8111    slrn_write_nbytes (&c, 1);
8112 }
8113 
8114 #if SLRN_HAS_EMPHASIZED_TEXT
smg_write_emphasized_text(char * str,int color0)8115 static void smg_write_emphasized_text (char *str, int color0)
8116 {
8117    char *s, *url = NULL;
8118    char ch;
8119    int url_len = Slrn_Highlight_Urls ? 0 : -1;
8120 
8121    if ((Slrn_Emphasized_Text_Mode == 0) && (Slrn_Highlight_Urls == 0))
8122      {
8123 	smg_write_string (str);
8124 	return;
8125      }
8126 
8127    s = str;
8128 
8129    while ((ch = *s) != 0)
8130      {
8131 	char *s0;
8132 	int color;
8133 	char ch0;
8134 
8135 	if ((url_len != -1) && (url < str) &&
8136 	    (NULL == (url = find_url (s, (unsigned int*) &url_len))))
8137 	  url_len = -1;
8138 
8139 	if ((url != NULL) && (s >= url))
8140 	  {
8141 	     slrn_write_nbytes (str, (unsigned int) (url - str));
8142 	     slrn_set_color (URL_COLOR);
8143 	     slrn_write_nbytes (url, url_len);
8144 	     slrn_set_color (color0);
8145 	     s = str = url + url_len;
8146 	     url = NULL;
8147 	     continue;
8148 	  }
8149 
8150 	if (Slrn_Emphasized_Text_Mode == 0)
8151 	  {
8152 	     if (url_len == -1)
8153 	       {
8154 		  s = str + strlen (str);
8155 		  break;
8156 	       }
8157 	     else
8158 	       {
8159 		  s++;
8160 		  continue;
8161 	       }
8162 	  }
8163 
8164 	if ((ch != '_') && (ch != '*') && (ch != '/'))
8165 	  {
8166 	     s++;
8167 	     continue;
8168 	  }
8169 	/*To handle these cases:
8170 	 *   bla bla _bla bla_ bla
8171 	 *   bla bla (_bla_) bla.
8172 	 * So, all these possibilities:
8173 	 *
8174 	 *      [^a-z0-9_]_[a-z0-9] ... [a-z0-9,]_[^a-z0-9]
8175 	 */
8176 
8177 	if (s > str)
8178 	  {
8179 	     ch0 = s[-1];
8180 	     if (isalnum (ch0)
8181 		 || (ch0 == ch))
8182 	       {
8183 		  s++;
8184 		  continue;
8185 	       }
8186 	  }
8187 	ch0 = s[1];
8188 	if (0 == isalnum (ch0))
8189 	  {
8190 	     s++;
8191 	     continue;
8192 	  }
8193 
8194 	/* Now look for the end */
8195 
8196 	s0 = s;
8197 	ch0 = ch;
8198 	s++;
8199 	while ((ch = *s) != 0)
8200 	  {
8201 	     if ((ch == '_') || (ch == '*') || (ch == '/'))
8202 	       break;
8203 
8204 	     s++;
8205 	  }
8206 
8207 	if (ch0 != ch)
8208 	  continue;
8209 
8210 	/* We have a possible match. */
8211 	ch0 = s[-1];
8212 	if ((0 == isalnum (ch0))
8213 	    && (0 == ispunct (ch0)))
8214 	  continue;
8215 
8216 	ch0 = s[1];
8217 	if (isalnum (ch0)
8218 	    || (ch0 == ch))
8219 	  continue;
8220 
8221 	/* Now, we have s0 at _bla bla and s at _ whatever */
8222 
8223 	slrn_write_nbytes (str, (unsigned int) (s0 - str));
8224 	str = s + 1;
8225 
8226 	if (ch == '_')
8227 	  color = UNDERLINE_COLOR;
8228 	else if (ch == '*')
8229 	  color = BOLD_COLOR;
8230 	else /* if (ch == '/') */
8231 	  color = ITALICS_COLOR;
8232 
8233 	switch (Slrn_Emphasized_Text_Mode)
8234 	  {
8235 	   case 1:  /* Strip _ characters */
8236 	     slrn_set_color (color);
8237 	     s0++;
8238 	     slrn_write_nbytes (s0, (unsigned int) (s-s0));
8239 	     slrn_set_color (color0);
8240 	     break;
8241 
8242 	   case 2:		       /* substitute space */
8243 	     ch = ' ';
8244 	     slrn_write_nbytes (&ch, 1);
8245 	     slrn_set_color (color);
8246 	     s0++;
8247 	     slrn_write_nbytes (s0, (unsigned int) (s-s0));
8248 	     slrn_set_color (color0);
8249 	     slrn_write_nbytes (&ch, 1);
8250 	     break;
8251 
8252 	   case 3:		       /* write as is */
8253 	   default:
8254 	     slrn_set_color (color);
8255 	     s++;
8256 	     slrn_write_nbytes (s0, (unsigned int)(s-s0));
8257 	     slrn_set_color (color0);
8258 	  }
8259 
8260 	s = str;
8261      }
8262    slrn_write_nbytes (str, (unsigned int)(s - str));
8263 }
8264 #endif				       /* SLRN_HAS_EMPHASIZED_TEXT */
8265 
quick_help(void)8266 static void quick_help (void) /*{{{*/
8267 {
8268    char *msg;
8269 
8270    if (Slrn_Batch) return;
8271 
8272    if (Article_Visible == 0)
8273      {
8274 	msg = _("SPC:Select  Ctrl-D:PgDn  Ctrl-U:PgUp  d:Mark-as-Read  n:Next  p:Prev  q:Quit");
8275 	if (Slrn_Header_Help_Line != NULL) msg = Slrn_Header_Help_Line;
8276      }
8277    else
8278      {
8279 	msg = _("SPC:Pgdn  B:PgUp  u:Un-Mark-as-Read  f:Followup  n:Next  p:Prev  q:Quit");
8280 	if (Slrn_Art_Help_Line != NULL) msg = Slrn_Art_Help_Line;
8281      }
8282 
8283    if (0 == slrn_message ("%s", msg))
8284      Slrn_Message_Present = 0;
8285 }
8286 
8287 /*}}}*/
8288 
8289 static char Rot13buf[256];
8290 
init_rot13(void)8291 static void init_rot13 (void) /*{{{*/
8292 {
8293    static int is_initialized;
8294    if (is_initialized == 0)
8295      {
8296 	int i;
8297 	is_initialized = 1;
8298 	for (i = 0; i < 256; i++)
8299 	  {
8300 	     Rot13buf[i] = i;
8301 	  }
8302 
8303 	for (i = 'A'; i <= 'M'; i++)
8304 	  {
8305 	     Rot13buf[i] = i + 13;
8306 	     /* Now take care of lower case ones */
8307 	     Rot13buf[i + 32] =  i + 32 + 13;
8308 	  }
8309 
8310 	for (i = 'N'; i <= 'Z'; i++)
8311 	  {
8312 	     Rot13buf[i] = i - 13;
8313 	     /* Now take care of lower case ones */
8314 	     Rot13buf[i + 32] =  i + 32 - 13;
8315 	  }
8316      }
8317 }
8318 /*}}}*/
8319 
write_rot13(unsigned char * buf,int color,int use_emph)8320 static void write_rot13 (unsigned char *buf, int color, int use_emph) /*{{{*/
8321 {
8322    char *buf_rot13;
8323 
8324    init_rot13();
8325 
8326    if (NULL == (buf_rot13 = slrn_strmalloc ((char *)buf, 1)))
8327      return;
8328 
8329    decode_rot13 ((unsigned char *)buf_rot13);
8330 #if SLRN_HAS_EMPHASIZED_TEXT
8331    if (use_emph)
8332      smg_write_emphasized_text (buf_rot13, color);
8333    else
8334 #endif
8335      smg_write_string (buf_rot13);
8336 
8337    slrn_free(buf_rot13);
8338 }
8339 /*}}}*/
8340 
8341 /* rot13-"decodes" buf (overwriting its content) */
decode_rot13(unsigned char * buf)8342 static void decode_rot13 (unsigned char *buf) /*{{{*/
8343 {
8344    init_rot13();
8345 
8346    while (*buf != 0)
8347      {
8348 	*buf = Rot13buf[*buf];
8349 	buf++;
8350      }
8351 }
8352 /*}}}*/
8353 
8354 /*{{{ utility routines */
8355 
8356 #if SLRN_HAS_SPOILERS
8357 /* write out the line, replacing all printable chars with '*' */
write_spoiler(char * buf,int first_line)8358 static void write_spoiler (char *buf, int first_line) /*{{{*/
8359 {
8360    char ch;
8361 
8362    Spoilers_Visible = Header_Showing;
8363 
8364    if (Slrn_Spoiler_Char == ' ')
8365      return;
8366 
8367    if (first_line)
8368      {
8369 	char *s, n;
8370 
8371 	n = Num_Spoilers_Visible;
8372 	s = buf;
8373 
8374 	while (n-- != 0)
8375 	  {
8376 	     if (NULL == (s = slrn_strbyte (s, 12)))
8377 	       break;
8378 	     else
8379 	       s++;
8380 	  }
8381 
8382 	if (NULL == s)
8383 	  {
8384 	     smg_write_string (buf);
8385 	     return;
8386 	  }
8387 
8388 	slrn_write_nbytes (buf, s - buf);
8389 	buf = s;
8390      }
8391 
8392    while ((ch = *buf++) != 0)
8393      {
8394 	if (!isspace(ch)) ch = Slrn_Spoiler_Char;
8395 	slrn_write_nbytes (&ch, 1);
8396      }
8397 }
8398 /*}}}*/
8399 #endif
8400 
draw_tree(Slrn_Header_Type * h)8401 static void draw_tree (Slrn_Header_Type *h) /*{{{*/
8402 {
8403    SLwchar_Type buf[2];
8404 
8405 #if !defined(IBMPC_SYSTEM)
8406    if (Graphic_Chars_Mode == 0)
8407      {
8408 	if (h->tree_ptr != NULL) smg_write_string (h->tree_ptr);
8409 	smg_write_string ("  ");
8410 	return;
8411      }
8412    if (Graphic_Chars_Mode == ALT_CHAR_SET_MODE)
8413      SLsmg_set_char_set (1);
8414 #endif
8415 
8416    slrn_set_color (TREE_COLOR);
8417 
8418    if (h->tree_ptr != NULL) smg_write_string (h->tree_ptr);
8419 
8420    if (h->flags & FAKE_CHILDREN)
8421      {
8422 	buf[0] = Graphic_UTee_Char;
8423 	buf[1] = Graphic_HLine_Char;
8424 	SLsmg_forward (-1);
8425 	SLsmg_write_char (Graphic_ULCorn_Char);
8426      }
8427    else if ((h->sister == NULL) ||
8428 	    ((h->sister->flags & FAKE_PARENT) && ((h->flags & FAKE_PARENT) == 0)))
8429      {
8430 	buf[0] = Graphic_LLCorn_Char;
8431 	buf[1] = Graphic_HLine_Char;
8432      }
8433    else
8434      {
8435 	buf[0] = Graphic_LTee_Char;
8436 	buf[1] = Graphic_HLine_Char;
8437      }
8438    SLsmg_write_char(buf[0]);
8439    SLsmg_write_char(buf[1]);
8440 
8441 #if !defined(IBMPC_SYSTEM)
8442    if (Graphic_Chars_Mode == ALT_CHAR_SET_MODE) SLsmg_set_char_set (0);
8443 #endif
8444 }
8445 
8446 /*}}}*/
8447 
8448 /*{{{ check_subject */
8449 
8450 /*
8451  * This checks if subjects should be printed (correctly I hope)
8452  * hacked: articles in a tree shouldn't display their subject if the
8453  *          subject is already displayed (i.e. at top)
8454  * To add: take more Re:'s into account (currently, one is allowed)
8455  */
8456 int Slrn_Show_Thread_Subject = 0;
check_subject(Slrn_Header_Type * h)8457 static int check_subject (Slrn_Header_Type *h) /*{{{*/
8458 {
8459    char *psubj, *subj;
8460 
8461    subj = h->subject;
8462    psubj = h->prev->subject;	       /* used to be: h->parent->subject */
8463    if ((subj == NULL) || (psubj == NULL)) return 1;
8464 
8465    return _art_subject_cmp (subj, psubj);
8466 }
8467 
8468 /*}}}*/
8469 
8470 /*}}}*/
8471 
8472 #if SLRN_HAS_END_OF_THREAD
display_end_of_thread(Slrn_Header_Type * h)8473 static int display_end_of_thread (Slrn_Header_Type *h) /*{{{*/
8474 {
8475    Slrn_Header_Type *parent, *next_parent;
8476 
8477    if ((h == NULL)
8478        || (h->parent == NULL)
8479        || (h->child != NULL)
8480        || (h->next == NULL))
8481      return -1;
8482 
8483    parent = h->parent;
8484    while (parent->parent != NULL) parent = parent->parent;
8485 
8486    next_parent = h->next;
8487    while (next_parent->parent != NULL)
8488      next_parent = next_parent->parent;
8489 
8490    if (next_parent != parent)
8491      {
8492 	if ((Header_Window_Nrows == 0)
8493 	    && (next_parent != NULL)
8494 	    && (next_parent->subject != NULL))
8495 	  slrn_message (_("End of Thread.  Next: %s"), next_parent->subject);
8496 	else
8497 	  slrn_message (_("End of Thread."));
8498 	return 0;
8499      }
8500 
8501    if ((h->sister == NULL)
8502        || (h->parent != h->next->parent)
8503        || (FAKE_PARENT & (h->next->flags ^ h->flags)))
8504      {
8505 	/* The last test involving ^ is necessary because the two can be
8506 	 * sisters except that one can have a fake parent.  If this is the
8507 	 * case, we are at the end of a subthread.
8508 	 */
8509 	slrn_message (_("End of Sub-Thread"));
8510 	return 0;
8511      }
8512 
8513    return -1;
8514 }
8515 /*}}}*/
8516 #endif
8517 
disp_write_flags(Slrn_Header_Type * h)8518 static void disp_write_flags (Slrn_Header_Type *h)
8519 {
8520    unsigned int flags = h->flags;
8521    int row = SLsmg_get_row ();
8522 
8523    /* Do not write header numbers if we are displaying at bottom of the
8524     * screen in the display area.  When this happens, the header window is
8525     * not visible.
8526     */
8527    if (row + 1 != SLtt_Screen_Rows)
8528      {
8529 	if (Slrn_Use_Header_Numbers)
8530 	  {
8531 	     slrn_set_color (HEADER_NUMBER_COLOR);
8532 	     SLsmg_printf ("%2d", row);
8533 	     if (row > Largest_Header_Number) Largest_Header_Number = row;
8534 	  }
8535 	else smg_write_string ("  ");
8536      }
8537 
8538    if (flags & HEADER_NTAGGED)
8539      {
8540 	slrn_set_color (HIGH_SCORE_COLOR);
8541 	SLsmg_printf ("%2d",
8542 		      h->tag_number);
8543      }
8544    else
8545      {
8546 	if ((flags & HEADER_HIGH_SCORE)
8547 	    || ((flags & FAKE_HEADER_HIGH_SCORE)
8548 		&& (h->child != NULL)
8549 		&& (h->child->flags & HEADER_HIDDEN)))
8550 	  {
8551 	     slrn_set_color (HIGH_SCORE_COLOR);
8552 	     SLsmg_printf ("!%c",
8553 			   ((flags & HEADER_TAGGED) ? '*': ' '));
8554 	  }
8555 	else
8556 	  {
8557 	     slrn_set_color (0);
8558 	     SLsmg_printf (" %c",
8559 			   ((flags & HEADER_TAGGED) ? '*': ' '));
8560 	  }
8561      }
8562    slrn_set_color (0);
8563 
8564     if ((h->parent == NULL)
8565 	&& (h->child != NULL)
8566 	&& (h->child->flags & HEADER_HIDDEN))
8567       {
8568 	 Slrn_Header_Type *next;
8569 	 unsigned int num, num_read;
8570 
8571 	 num = 0;
8572 	 num_read = 0;
8573 
8574 	 next = h->sister;
8575 	 while (h != next)
8576 	   {
8577 	      num++;
8578 	      if (h->flags & HEADER_READ)
8579 		num_read++;
8580 	      h = h->next;
8581 	   }
8582 	 if (num == num_read)
8583 	   SLsmg_write_char ('D');
8584 	 else if (num_read == 0)
8585 	   SLsmg_write_char ('-');
8586 	 else
8587 	   SLsmg_write_char ('%');
8588       }
8589     else SLsmg_write_char ((flags & HEADER_READ) ? 'D': '-');
8590 }
8591 
8592 #if SLRN_HAS_SPOOL_SUPPORT
get_body_status(Slrn_Header_Type * h)8593 static char get_body_status (Slrn_Header_Type *h) /*{{{*/
8594 {
8595    if ((h->parent == NULL) && (h->child != NULL)
8596        && (h->child->flags & HEADER_HIDDEN))
8597      {
8598 	Slrn_Header_Type *next;
8599 	unsigned int num=0, num_without=0, num_request=0;
8600 
8601 	next = h->sister;
8602 	while (h != next)
8603 	  {
8604 	     num++;
8605 	     if (h->flags & HEADER_WITHOUT_BODY)
8606 	       num_without++;
8607 	     if (h->flags & HEADER_REQUEST_BODY)
8608 	       num_request++;
8609 	     h = h->next;
8610 	  }
8611 
8612 	if (num == num_request) return 'M';
8613 	if (num_request) return 'm';
8614 	if (num == num_without) return 'H';
8615 	if (num_without) return 'h';
8616 	return ' ';
8617      }
8618    if (h->flags & HEADER_REQUEST_BODY) return 'M';
8619    if (h->flags & HEADER_WITHOUT_BODY) return 'H';
8620    return ' ';
8621 }
8622 /*}}}*/
8623 #endif
8624 
8625 #if SLRN_HAS_GROUPLENS
8626 # define SLRN_GROUPLENS_DISPLAY_WIDTH 5
disp_write_grplens(Slrn_Header_Type * h)8627 static void disp_write_grplens (Slrn_Header_Type *h)
8628 {
8629    char buf [SLRN_GROUPLENS_DISPLAY_WIDTH+1], *b, *bmax;
8630    int pred;
8631 
8632    if (Num_GroupLens_Rated == -1)
8633      return;
8634 
8635    b = buf;
8636    bmax = b + SLRN_GROUPLENS_DISPLAY_WIDTH;
8637    while (b < bmax) *b++ = ' ';
8638    *b = 0;
8639 
8640    pred = h->gl_pred;
8641    if (pred < 0)
8642      buf [SLRN_GROUPLENS_DISPLAY_WIDTH / 2] = '?';
8643    else
8644      {
8645 	b = buf;
8646 	while ((pred > 0) && (b < bmax))
8647 	  {
8648 	     pred--;
8649 	     *b++ = '*';
8650 	  }
8651      }
8652 
8653    slrn_set_color (GROUPLENS_DISPLAY_COLOR);
8654    SLsmg_write_nstring (buf, SLRN_GROUPLENS_DISPLAY_WIDTH);
8655    slrn_set_color (0);
8656 }
8657 #endif
8658 
8659 /*}}}*/
8660 
8661 /*}}}*/
8662 
disp_get_header_subject(Slrn_Header_Type * h)8663 static char *disp_get_header_subject (Slrn_Header_Type *h)
8664 {
8665    int row = SLsmg_get_row ();
8666 
8667    if ((Slrn_Show_Thread_Subject)
8668        /* || (0 == h->num_children) */
8669        || (h->parent == NULL)
8670        || (row == 1)
8671        || (row + 1 == SLtt_Screen_Rows)/* at bottom of screen */
8672        || check_subject (h))
8673      return h->subject;
8674 
8675    return ">";
8676 }
8677 
color_by_score(int score)8678 static int color_by_score (int score) /*{{{*/
8679 {
8680    if (score >= Slrn_High_Score_Min) return HIGH_SCORE_COLOR;
8681    else if (score)
8682      {
8683 	if (score > 0) return POS_SCORE_COLOR;
8684 	else return NEG_SCORE_COLOR;
8685      }
8686    return SUBJECT_COLOR;
8687 }
8688 /*}}}*/
8689 
display_header_cb(char ch,void * data,int * len,int * color)8690 static char *display_header_cb (char ch, void *data, int *len, int *color) /*{{{*/
8691 {
8692    Slrn_Header_Type *h = (Slrn_Header_Type *) data;
8693    static char buf[256];
8694    char *retval = NULL;
8695    int score;
8696 
8697    switch (ch)
8698      {
8699       case 'B':
8700 #if SLRN_HAS_SPOOL_SUPPORT
8701 	if (Slrn_Server_Id == SLRN_SERVER_ID_SPOOL)
8702 	  {
8703 	     retval = buf;
8704 	     retval[0] = get_body_status (h);
8705 	     retval[1] = 0;
8706 	     *len = 1;
8707 	  }
8708 	else
8709 #endif
8710 	  retval = "";
8711 	break;
8712 
8713       case 'C':
8714 	if ((h->next != NULL) && (h->next->flags & HEADER_HIDDEN))
8715 	  retval = "C";
8716 	else
8717 	  retval = " ";
8718 	*len = 1;
8719 	break;
8720 
8721       case 'F':
8722 	disp_write_flags (h);
8723 	break;
8724 
8725       case 'P':
8726 	if (h->parent != NULL) retval = "P";
8727 	else retval = " ";
8728 	*len = 1;
8729 	break;
8730 
8731       case 'S':
8732 	score = ((h->child != NULL) && (h->child->flags & HEADER_HIDDEN)
8733 		 ? h->thread_score : h->score);
8734 
8735 	if ((color != NULL) && (Slrn_Color_By_Score & 0x1))
8736 	  *color = color_by_score (score);
8737 	retval = buf;
8738 	if (score)
8739 #ifdef HAVE_ANSI_SPRINTF
8740 	  *len =
8741 #endif
8742 	  sprintf (retval, "%d", score); /* safe */
8743 	else
8744 	  *retval = 0;
8745 	break;
8746 
8747       case 'T':
8748 	if (((h->next == NULL) || (0 == (h->next->flags & HEADER_HIDDEN))) &&
8749 	    ((h->parent != NULL) || (h->flags & FAKE_CHILDREN)))
8750 	  draw_tree (h); /* field_len is ignored */
8751 	break;
8752 
8753       case 'b':
8754 	retval = buf;
8755 	if (h->bytes < 1000)
8756 #ifdef HAVE_ANSI_SPRINTF
8757 	  *len =
8758 #endif
8759 	  sprintf (retval, "%d ", h->bytes); /* safe */
8760 	else
8761 	  {
8762 	     float size = h->bytes / 1024.0;
8763 	     char factor = 'k';
8764 	     if (h->bytes >= 1024000)
8765 	       {
8766 		  size = size/1024.0;
8767 		  factor = 'M';
8768 	       }
8769 	     if (size < 10)
8770 #ifdef HAVE_ANSI_SPRINTF
8771 	       *len =
8772 #endif
8773 	       sprintf (retval, "%.1f%c", size, factor); /* safe */
8774 	     else
8775 #ifdef HAVE_ANSI_SPRINTF
8776 	       *len =
8777 #endif
8778 	       sprintf (retval, "%.0f%c", size, factor); /* safe */
8779 	  }
8780 	break;
8781 
8782       case 'c':
8783 	if (color != NULL) *color = THREAD_NUM_COLOR;
8784 	if (h->num_children)
8785 	  {
8786 	     retval = buf;
8787 #ifdef HAVE_ANSI_SPRINTF
8788 	     *len =
8789 #endif
8790 	     sprintf (buf, "%d", 1 + h->num_children); /* safe */
8791 	  }
8792 	else
8793 	  {
8794 	     retval = " ";
8795 	     *len = 1;
8796 	  }
8797 	break;
8798 
8799       case 'l':
8800 	retval = buf;
8801 #ifdef HAVE_ANSI_SPRINTF
8802 	*len =
8803 #endif
8804 	sprintf (retval, "%d", h->lines); /* safe */
8805 	break;
8806 
8807       case 't':
8808 	if (!_art_Headers_Threaded)
8809 	  {
8810 	     retval = " ";
8811 	     *len = 1;
8812 	  }
8813 	else if ((h->next != NULL) && (h->next->flags & HEADER_HIDDEN))
8814 	  {
8815 	     retval = buf;
8816 	     if (color != NULL) *color = THREAD_NUM_COLOR;
8817 #ifdef HAVE_ANSI_SPRINTF
8818 	     *len =
8819 #endif
8820 	     sprintf (buf, "%3d ", 1 + h->num_children); /* safe */
8821 	  }
8822 	else
8823 	  {
8824 	     smg_write_string ("    ");
8825 	     if ((h->parent != NULL) || (h->flags & FAKE_CHILDREN))
8826 	       draw_tree (h);
8827 	  }
8828 	break;
8829 
8830       case 'f':
8831 	retval = h->from;
8832 	if (retval == NULL) retval = "";
8833 	if (color != NULL)
8834 	  {
8835 	     if (strcmp (Slrn_User_Info.realname,
8836 			 (h->realname != NULL) ? h->realname : ""))
8837 	       *color = AUTHOR_COLOR;
8838 	     else
8839 	       *color = FROM_MYSELF_COLOR;
8840 	  }
8841 	break;
8842 
8843       case 'r':
8844 	retval = h->realname;
8845 	if (retval == NULL) retval = "";
8846 	if (color != NULL)
8847 	  {
8848 	     if (strcmp (Slrn_User_Info.realname, retval))
8849 	       *color = AUTHOR_COLOR;
8850 	     else
8851 	       *color = FROM_MYSELF_COLOR;
8852 	  }
8853 	break;
8854 
8855       case 's':
8856 	if (color == NULL) /* we're called for the status line */
8857 	  {
8858 	     retval = h->subject;
8859 	     break;
8860 	  }
8861 
8862 	if ((Slrn_Color_By_Score & 0x2) &&
8863 	    ((Slrn_Highlight_Unread != 2) || !(h->flags & HEADER_READ)))
8864 	  {
8865 	     score = ((h->child != NULL) && (h->child->flags & HEADER_HIDDEN)
8866 		      ? h->thread_score : h->score);
8867 	     *color = color_by_score (score);
8868 	  }
8869 	else *color = SUBJECT_COLOR;
8870 
8871 	if (Slrn_Highlight_Unread)
8872 	  {
8873 	     Slrn_Header_Type *t = h, *next;
8874 
8875 	     if ((h->parent == NULL) && (h->child != NULL)
8876 		 && (h->child->flags & HEADER_HIDDEN))
8877 	       next = h->sister;
8878 	     else
8879 	       next = h->next;
8880 
8881 	     while (t != next)
8882 	       {
8883 		  if (!(t->flags & HEADER_READ))
8884 		    {
8885 		       if (Slrn_Highlight_Unread != 2)
8886 			 *color += 5; /* cf. slrn.h */
8887 		       else if (*color == SUBJECT_COLOR)
8888 			 *color = UNREAD_SUBJECT_COLOR;
8889 		       break;
8890 		    }
8891 		  t = t->next;
8892 	       }
8893 	  }
8894 
8895 	retval = disp_get_header_subject (h);
8896 	break;
8897 #if SLRN_HAS_GROUPLENS
8898       case 'G':
8899 	disp_write_grplens (h);
8900 	break;
8901 #endif
8902       case 'd':
8903 	if (NULL == (retval = h->date))
8904 	  retval = "";
8905 	if (color != NULL)
8906 	  *color = DATE_COLOR;
8907 	break;
8908 
8909       case 'D':
8910 	if (NULL == (retval = h->date))
8911 	  retval = "";
8912 	else
8913 	  {
8914 	     char *fmtstr;
8915 
8916 	     fmtstr = Slrn_Overview_Date_Format;
8917 	     if (fmtstr == NULL)
8918 	       fmtstr = "%d %b %y %H:%M";
8919 
8920 	     slrn_strftime (buf, sizeof(buf), fmtstr, retval,
8921 			    Slrn_Use_Localtime & 0x01);
8922 	     retval = buf;
8923 	  }
8924 	if (color != NULL)
8925 	  *color = DATE_COLOR;
8926 	break;
8927 
8928       case 'n':
8929 	retval = buf;
8930 #ifdef HAVE_ANSI_SPRINTF
8931 	*len =
8932 #endif
8933 	sprintf (retval, NNTP_FMT_ARTNUM, h->number); /* safe */
8934 	break;
8935      }
8936    return retval;
8937 }
8938 /*}}}*/
8939 
display_header_line(Slrn_Header_Type * h,int row)8940 static void display_header_line (Slrn_Header_Type *h, int row)
8941 {
8942    char *fmt = Header_Display_Formats[Header_Format_Number];
8943 
8944    if ((fmt == NULL) || (*fmt == 0))
8945      fmt = Default_Header_Format;
8946 
8947    slrn_custom_printf (fmt, display_header_cb, (void *) h, row, 0);
8948 }
8949 
display_article_line(Slrn_Article_Line_Type * l)8950 static void display_article_line (Slrn_Article_Line_Type *l)
8951 {
8952    char *lbuf = l->buf;
8953    int color;
8954    int use_emph_mask = 0;
8955    int use_rot13 = 0;
8956 
8957    switch (l->flags & LINE_TYPE_MASK)
8958      {
8959       case HEADER_LINE:
8960 	if ((unsigned char)*lbuf > (unsigned char) ' ')
8961 	  {
8962 	     lbuf = slrn_strbyte (lbuf, ':');
8963 	     if (lbuf != NULL)
8964 	       {
8965 		  lbuf++;
8966 		  slrn_set_color (SLRN_HEADER_KEYWORD_COLOR);
8967 		  slrn_write_nbytes (l->buf, lbuf - l->buf);
8968 	       }
8969 	     else lbuf = l->buf;
8970 	  }
8971 	use_emph_mask = EMPHASIZE_HEADER;
8972 	color = HEADER_COLOR;
8973 	break;
8974 
8975       case QUOTE_LINE:
8976 	color = QUOTE_COLOR + ((l->v.quote_level-1) % MAX_QUOTE_COLORS);
8977 	use_emph_mask = EMPHASIZE_QUOTES;
8978 	use_rot13 = 1;
8979 	break;
8980 
8981       case SIGNATURE_LINE:
8982 	use_emph_mask = EMPHASIZE_SIGNATURE;
8983 	color = SIGNATURE_COLOR;
8984 	use_rot13 = 1;
8985 	break;
8986 
8987       case PGP_SIGNATURE_LINE:
8988 	color = PGP_SIGNATURE_COLOR;
8989 	break;
8990       case VERBATIM_MARK_LINE|VERBATIM_LINE:
8991 	if (Slrn_Verbatim_Marks_Hidden)
8992 	  lbuf = "";
8993 	/* drop */
8994       case VERBATIM_LINE:
8995 	color = VERBATIM_COLOR;
8996 	break;
8997       default:
8998 	color = ARTICLE_COLOR;
8999 	use_emph_mask = EMPHASIZE_ARTICLE;
9000 	use_rot13 = 1;
9001      }
9002 
9003    slrn_set_color (color);
9004 
9005 #if SLRN_HAS_SPOILERS
9006    if (l->flags & SPOILER_LINE)
9007      {
9008 	write_spoiler (lbuf, ((l->prev == NULL) ||
9009 			      !(l->prev->flags & SPOILER_LINE)));
9010 	return;
9011      }
9012 #endif
9013 
9014    use_emph_mask &= Slrn_Emphasized_Text_Mask;
9015 
9016    if (Do_Rot13 && use_rot13)
9017      {
9018 	write_rot13 ((unsigned char *) lbuf, color, use_emph_mask);
9019 	return;
9020      }
9021 
9022    if (use_emph_mask)
9023      {
9024 #if SLRN_HAS_EMPHASIZED_TEXT
9025 	smg_write_emphasized_text (lbuf, color);
9026 	return;
9027 #endif
9028      }
9029    smg_write_string (lbuf);
9030 }
9031 
header_status_line_cb(char ch,void * data,int * len,int * color)9032 static char *header_status_line_cb (char ch, void *data, int *len, int *color) /*{{{*/
9033 {
9034    static char buf[66];
9035    char *retval = buf;
9036 
9037    *buf = 0;
9038    (void) data; (void) color; /* currently unused */
9039 
9040    switch (ch)
9041      {
9042       case 'L':
9043 	retval = slrn_print_percent (buf, &Slrn_Header_Window, 1);
9044 	break;
9045       case 'P':
9046 	retval = slrn_print_percent (buf, &Slrn_Header_Window, 0);
9047 	break;
9048       case 'T':
9049 	if (Slrn_Current_Header != NULL)
9050 #ifdef HAVE_ANSI_SPRINTF
9051 	  *len =
9052 #endif
9053 	  sprintf (buf, "%u", 1 + Slrn_Current_Header->num_children); /* safe */
9054 	break;
9055       case 'h':
9056 #ifdef HAVE_ANSI_SPRINTF
9057 	*len =
9058 #endif
9059 	  sprintf (buf, "%u", Number_High_Scored); /* safe */
9060 	break;
9061       case 'k':
9062 #ifdef HAVE_ANSI_SPRINTF
9063 	*len =
9064 #endif
9065 	  sprintf (buf, "%u", Number_Score_Killed); /* safe */
9066 	break;
9067       case 'l':
9068 #ifdef HAVE_ANSI_SPRINTF
9069 	*len =
9070 #endif
9071 	  sprintf (buf, "%u", Number_Low_Scored); /* safe */
9072 	break;
9073       case 'n':
9074 	retval = Slrn_Current_Group_Name;
9075 	break;
9076       case 'p':
9077 	if (Header_Window_HScroll) strcpy (buf, "<"); /* safe */
9078 	else strcpy (buf, " "); /* safe */
9079 	*len = 1;
9080 	break;
9081       case 'r':
9082 #ifdef HAVE_ANSI_SPRINTF
9083 	*len =
9084 #endif
9085 	  sprintf (buf, "%u", Number_Read); /* safe */
9086 	break;
9087       case 't':
9088 #ifdef HAVE_ANSI_SPRINTF
9089 	*len =
9090 #endif
9091 	  sprintf (buf, "%u", Number_Total); /* safe */
9092 	break;
9093       case 'u':
9094 #ifdef HAVE_ANSI_SPRINTF
9095 	*len =
9096 #endif
9097 	  sprintf (buf, "%u", Number_Total - Number_Read); /* safe */
9098 	break;
9099      }
9100    return retval;
9101 }
9102 /*}}}*/
9103 
slrn_art_get_unread(void)9104 int slrn_art_get_unread (void)
9105 {
9106    return Number_Total - Number_Read;
9107 }
9108 
update_header_window(void)9109 static void update_header_window (void)
9110 {
9111    Slrn_Header_Type *h;
9112    char *fmt = Slrn_Header_Status_Line;
9113    int height;
9114    int row;
9115    int last_cursor_row;
9116    int c0;
9117 
9118    height = Slrn_Header_Window.nrows;
9119 
9120    h = (Slrn_Header_Type *) Slrn_Header_Window.top_window_line;
9121    SLscroll_find_top (&Slrn_Header_Window);
9122    if (h != (Slrn_Header_Type *) Slrn_Header_Window.top_window_line)
9123      {
9124 	Slrn_Full_Screen_Update = 1;
9125 	h = (Slrn_Header_Type *) Slrn_Header_Window.top_window_line;
9126      }
9127 
9128    last_cursor_row = Last_Cursor_Row;
9129 
9130    if ((fmt == NULL) || (*fmt == 0))
9131      fmt = _("%p[%u/%t unread] Group: %n%-20g -- %L (%P)");
9132    slrn_custom_printf (fmt, header_status_line_cb, NULL, height + 1,
9133 		       STATUS_COLOR);
9134 
9135    c0 = Header_Window_HScroll;
9136    SLsmg_set_screen_start (NULL, &c0);
9137 
9138    for (row = 1; row <= height; row++)
9139      {
9140 	while ((h != NULL) && (h->flags & HEADER_HIDDEN))
9141 	  h = h->next;
9142 
9143 	if (Slrn_Full_Screen_Update
9144 	    || (row == last_cursor_row))
9145 	  {
9146 	     if (h == NULL)
9147 	       {
9148 		  SLsmg_gotorc (row, 0);
9149 		  slrn_set_color (0);
9150 		  SLsmg_erase_eol ();
9151 		  continue;
9152 	       }
9153 
9154 	     display_header_line (h, row);
9155 	  }
9156 	h = h->next;
9157      }
9158    SLsmg_set_screen_start (NULL, NULL);
9159 }
9160 
article_status_line_cb(char ch,void * data,int * len,int * color)9161 static char *article_status_line_cb (char ch, void *data, int *len, int *color) /*{{{*/
9162 {
9163    static char buf[66];
9164    char *retval = buf;
9165 
9166    (void) data; (void) color; /* currently unused */
9167    *buf = ' '; *(buf + 1) = '\0';
9168 
9169    switch (ch)
9170      {
9171       case 'H':
9172 	if (!Slrn_Current_Article->headers_hidden) *buf = 'H';
9173 	*len = 1;
9174 	break;
9175       case 'I':
9176 	if (!Slrn_Current_Article->pgp_signature_hidden) *buf = 'P';
9177 	*len = 1;
9178 	break;
9179       case 'L':
9180 	retval = slrn_print_percent (buf, &Slrn_Article_Window, 1);
9181 	break;
9182       case 'P':
9183 	retval = slrn_print_percent (buf, &Slrn_Article_Window, 0);
9184 	break;
9185       case 'Q':
9186 	if (!Slrn_Current_Article->quotes_hidden) *buf = 'Q';
9187 	*len = 1;
9188 	break;
9189       case 'T':
9190 	if (!Slrn_Current_Article->signature_hidden) *buf = 'S';
9191 	*len = 1;
9192 	break;
9193       case 'V':
9194 	if (!Slrn_Current_Article->verbatim_hidden) *buf = 'V';
9195 	*len = 1;
9196 	break;
9197       case 'W':
9198 	if (Slrn_Current_Article->is_wrapped) *buf = 'W';
9199 	*len = 1;
9200 	break;
9201       case 'p':
9202 	if (Article_Window_HScroll) *buf = '<';
9203 	*len = 1;
9204 	break;
9205       case 'v':
9206 	if (!Slrn_Current_Article->verbatim_marks_hidden) *buf = 'v';
9207 	*len = 1;
9208 	break;
9209       default:
9210 	retval = display_header_cb (ch, (void *) Header_Showing, len, NULL);
9211 	break;
9212      }
9213 
9214    return retval;
9215 }
9216 /*}}}*/
9217 
update_article_window(void)9218 static void update_article_window (void)
9219 {
9220    Slrn_Article_Line_Type *l;
9221    Slrn_Article_Type *a;
9222 
9223    if (Article_Visible == 0)
9224      return;
9225 
9226    if (NULL == (a = Slrn_Current_Article))
9227      return;
9228 
9229    if (Slrn_Current_Article->needs_sync)
9230      slrn_art_sync_article (Slrn_Current_Article);
9231 
9232    l = (Slrn_Article_Line_Type *) Slrn_Article_Window.top_window_line;
9233 
9234    SLscroll_find_top (&Slrn_Article_Window);
9235 
9236    if (Slrn_Full_Screen_Update
9237        || (l != a->cline)
9238        || (l != (Slrn_Article_Line_Type *) Slrn_Article_Window.top_window_line))
9239      {
9240 	char *fmt = Slrn_Art_Status_Line;
9241 	int row;
9242 	int c0;
9243 	int height;
9244 
9245 	height = SLtt_Screen_Rows - 2;
9246 
9247 	if ((fmt == NULL) || (*fmt == 0))
9248 	  fmt = "%p%n : %s %-20g -- %L (%P)";
9249 
9250 	slrn_custom_printf (fmt, article_status_line_cb, NULL, height,
9251 			    STATUS_COLOR);
9252 
9253 	c0 = Article_Window_HScroll;
9254 	SLsmg_set_screen_start (NULL, &c0);
9255 	l = a->cline;
9256 
9257 	row = Slrn_Header_Window.nrows + 2;
9258 	if (row == 2) row--;	       /* header window not visible */
9259 
9260 	while (row < height)
9261 	  {
9262 	     SLsmg_gotorc (row, 0);
9263 
9264 	     if (l != NULL)
9265 	       {
9266 		  if (l->flags & HIDDEN_LINE)
9267 		    {
9268 		       l = l->next;
9269 		       continue;
9270 		    }
9271 
9272 		  display_article_line (l);
9273 		  l = l->next;
9274 	       }
9275 	     else if (Slrn_Use_Tildes)
9276 	       {
9277 		  slrn_set_color (SLRN_TILDE_COLOR);
9278 		  smg_write_byte ('~');
9279 	       }
9280 
9281 	     slrn_set_color (0);
9282 	     SLsmg_erase_eol ();
9283 
9284 	     row++;
9285 	  }
9286 #if 0
9287 	if (((l == NULL)
9288 	     || ((l->flags & SIGNATURE_LINE) && Slrn_Sig_Is_End_Of_Article))
9289 	    && (Slrn_Current_Header == Header_Showing))
9290 	  At_End_Of_Article = Slrn_Current_Header;
9291 #else
9292 	if ((l == NULL)
9293 	    || ((l->flags & SIGNATURE_LINE) && Slrn_Sig_Is_End_Of_Article))
9294 	  At_End_Of_Article = Header_Showing;
9295 #endif
9296 	SLsmg_set_screen_start (NULL, NULL);
9297      }
9298 }
9299 
art_update_screen(void)9300 static void art_update_screen (void) /*{{{*/
9301 {
9302    At_End_Of_Article = NULL;
9303 #if SLRN_HAS_SPOILERS
9304    Spoilers_Visible = NULL;
9305 #endif
9306    Largest_Header_Number = 0;
9307 
9308    update_header_window ();
9309    update_article_window ();
9310 
9311    if (Slrn_Use_Mouse) slrn_update_article_menu ();
9312    else
9313      slrn_update_top_status_line ();
9314 
9315    if (Slrn_Message_Present == 0)
9316      {
9317 #if SLRN_HAS_SPOILERS
9318 	if (Spoilers_Visible != NULL)
9319 	  slrn_message (_("Spoilers visible!"));
9320 	else
9321 #endif
9322 #if SLRN_HAS_END_OF_THREAD
9323 	  if (Article_Visible
9324 	      && (-1 != display_end_of_thread (Slrn_Current_Header)))
9325 	    /* do nothing */ ;
9326 	else
9327 #endif
9328 	  quick_help ();
9329      }
9330 
9331    Last_Cursor_Row = 1 + (int) Slrn_Header_Window.window_row;
9332 
9333    if ((Header_Window_Nrows == 0)
9334        && Article_Visible)
9335      {
9336 	if (Slrn_Message_Present == 0)
9337 	  display_header_line (Slrn_Current_Header, SLtt_Screen_Rows - 1);
9338 
9339 	SLsmg_gotorc (SLtt_Screen_Rows - 1, 0);
9340 	Slrn_Full_Screen_Update = 0;
9341 	return;
9342      }
9343 
9344    if (Header_Window_HScroll)
9345      {
9346 	int c0 = Header_Window_HScroll;
9347 	SLsmg_set_screen_start (NULL, &c0);
9348      }
9349    display_header_line (Slrn_Current_Header, Last_Cursor_Row);
9350    SLsmg_set_screen_start (NULL, NULL);
9351 
9352    SLsmg_gotorc (Last_Cursor_Row, 0);
9353 
9354    slrn_set_color (CURSOR_COLOR);
9355 #if SLANG_VERSION > 10003
9356    if (Slrn_Display_Cursor_Bar)
9357      SLsmg_set_color_in_region (CURSOR_COLOR, Last_Cursor_Row, 0, 1, SLtt_Screen_Cols);
9358    else
9359 #endif
9360    smg_write_string ("->");
9361 
9362    slrn_set_color (0);
9363 
9364    Slrn_Full_Screen_Update = 0;
9365 }
9366 
9367 /*}}}*/
9368 
9369 /*}}}*/
9370