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