1 /*
2  *  Project   : tin - a Usenet reader
3  *  Module    : page.c
4  *  Author    : I. Lea & R. Skrenta
5  *  Created   : 1991-04-01
6  *  Updated   : 2019-07-10
7  *  Notes     :
8  *
9  * Copyright (c) 1991-2021 Iain Lea <iain@bricbrac.de>, Rich Skrenta <skrenta@pbm.com>
10  * All rights reserved.
11  *
12  * Redistribution and use in source and binary forms, with or without
13  * modification, are permitted provided that the following conditions
14  * are met:
15  *
16  * 1. Redistributions of source code must retain the above copyright notice,
17  *    this list of conditions and the following disclaimer.
18  *
19  * 2. Redistributions in binary form must reproduce the above copyright
20  *    notice, this list of conditions and the following disclaimer in the
21  *    documentation and/or other materials provided with the distribution.
22  *
23  * 3. Neither the name of the copyright holder nor the names of its
24  *    contributors may be used to endorse or promote products derived from
25  *    this software without specific prior written permission.
26  *
27  * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
28  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
31  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
32  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
33  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
34  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
35  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
36  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
37  * POSSIBILITY OF SUCH DAMAGE.
38  */
39 
40 
41 #ifndef TIN_H
42 #	include "tin.h"
43 #endif /* !TIN_H */
44 #ifndef TCURSES_H
45 #	include "tcurses.h"
46 #endif /* !TCURSES_H */
47 
48 
49 /*
50  * PAGE_HEADER is the size in lines of the article page header
51  * ARTLINES is the number of lines available to display actual article text.
52  */
53 #define PAGE_HEADER	4
54 #define ARTLINES	(NOTESLINES - (PAGE_HEADER - INDEX_TOP))
55 
56 int curr_line;			/* current line in art (indexed from 0) */
57 static FILE *note_fp;			/* active stream (raw or cooked) */
58 static int artlines;			/* active # of lines in pager */
59 static t_lineinfo *artline;	/* active 'lineinfo' data */
60 
61 static t_url *url_list;
62 
63 t_openartinfo pgart =	/* Global context of article open in the pager */
64 	{
65 		{ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, FALSE, NULL},
66 		FALSE, 0,
67 		NULL, NULL, NULL, NULL,
68 	};
69 
70 int last_resp;			/* previous & current article # in arts[] for '-' command */
71 int this_resp;
72 
73 size_t tabwidth = 8;
74 
75 static struct t_header *note_h = &pgart.hdr;	/* Easy access to article headers */
76 
77 static FILE *info_file;
78 static const char *info_title;
79 static int curr_info_line;
80 static int hide_uue;			/* set when uuencoded sections are 'hidden' */
81 static int num_info_lines;
82 static int reveal_ctrl_l_lines;	/* number of lines (from top) with de-activated ^L */
83 static int rotate;				/* 0=normal, 13=rot13 decode */
84 static int scroll_region_top;	/* first screen line for displayed message */
85 static int search_line;			/* Line to commence next search from */
86 static t_lineinfo *infoline = (t_lineinfo *) 0;
87 
88 static t_bool show_all_headers;	/* all headers <-> headers in news_headers_to[_not]_display */
89 static t_bool show_raw_article;	/* CTRL-H raw <-> cooked article */
90 static t_bool reveal_ctrl_l;	/* set when ^L hiding is off */
91 
92 /*
93  * Local prototypes
94  */
95 static int build_url_list(void);
96 static int load_article(int new_respnum, struct t_group *group);
97 static int prompt_response(int ch, int curr_respnum);
98 static int scroll_page(int dir);
99 static t_bool deactivate_next_ctrl_l(void);
100 static t_bool activate_last_ctrl_l(void);
101 static t_bool process_url(int n);
102 static t_bool url_page(void);
103 static t_function page_left(void);
104 static t_function page_right(void);
105 static t_function page_mouse_action(t_function (*left_action) (void), t_function (*right_action) (void));
106 static t_function url_left(void);
107 static t_function url_right(void);
108 static void build_url_line(int i);
109 static void draw_page_header(const char *group);
110 static void draw_url_arrow(void);
111 static void free_url_list(void);
112 static void preprocess_info_message(FILE *info_fh);
113 static void print_message_page(FILE *file, t_lineinfo *messageline, size_t messagelines, size_t base_line, size_t begin, size_t end, int help_level);
114 static void process_search(int *lcurr_line, size_t message_lines, size_t screen_lines, int help_level);
115 static void show_url_page(void);
116 static void invoke_metamail(FILE *fp);
117 
118 static t_menu urlmenu = { 0, 0, 0, show_url_page, draw_url_arrow, build_url_line };
119 
120 #ifdef XFACE_ABLE
121 #	define XFACE_SHOW()	if (tinrc.use_slrnface) \
122 								slrnface_show_xface()
123 #	define XFACE_CLEAR()	if (tinrc.use_slrnface) \
124 								slrnface_clear_xface()
125 #	define XFACE_SUPPRESS()	if (tinrc.use_slrnface) \
126 								slrnface_suppress_xface()
127 #else
128 #	define XFACE_SHOW()	/*nothing*/
129 #	define XFACE_CLEAR()	/*nothing*/
130 #	define XFACE_SUPPRESS()	/*nothing*/
131 #endif /* XFACE_ABLE */
132 
133 /*
134  * Scroll visible article part of display down (+ve) or up (-ve)
135  * according to 'dir' (KEYMAP_UP or KEYMAP_DOWN) and tinrc.scroll_lines
136  * >= 1  line count
137  * 0     full page scroll
138  * -1    full page but retain last line of prev page when scrolling
139  *       down. Obviously only applies when scrolling down.
140  * -2    half page scroll
141  * Return the offset we scrolled by so that redrawing can be done
142  */
143 static int
scroll_page(int dir)144 scroll_page(
145 	int dir)
146 {
147 	int i;
148 
149 	if (tinrc.scroll_lines >= 1)
150 		i = tinrc.scroll_lines;
151 	else {
152 		i = (signal_context == cPage) ? ARTLINES : NOTESLINES;
153 		switch (tinrc.scroll_lines) {
154 			case 0:
155 				break;
156 
157 			case -1:
158 				i--;
159 				break;
160 
161 			case -2:
162 				i >>= 1;
163 				break;
164 		}
165 	}
166 
167 	if (dir == KEYMAP_UP)
168 		i = -i;
169 
170 #ifdef USE_CURSES
171 	scrollok(stdscr, TRUE);
172 #endif /* USE_CURSES */
173 	SetScrollRegion(scroll_region_top, NOTESLINES + 1);
174 	ScrollScreen(i);
175 	SetScrollRegion(0, cLINES);
176 #ifdef USE_CURSES
177 	scrollok(stdscr, FALSE);
178 #endif /* USE_CURSES */
179 
180 	return i;
181 }
182 
183 
184 /*
185  * Map keypad codes to standard keyboard characters
186  */
187 static t_function
page_left(void)188 page_left(
189 	void)
190 {
191 	return GLOBAL_QUIT;
192 }
193 
194 
195 static t_function
page_right(void)196 page_right(
197 	void)
198 {
199 	return PAGE_NEXT_UNREAD;
200 }
201 
202 
203 static t_function
page_mouse_action(t_function (* left_action)(void),t_function (* right_action)(void))204 page_mouse_action(
205 	t_function (*left_action) (void),
206 	t_function (*right_action) (void))
207 {
208 	t_function func = NOT_ASSIGNED;
209 
210 	switch (xmouse) {
211 		case MOUSE_BUTTON_1:
212 			if (xrow < PAGE_HEADER || xrow >= cLINES - 1)
213 				func = GLOBAL_PAGE_DOWN;
214 			else
215 				func = right_action();
216 			break;
217 
218 		case MOUSE_BUTTON_2:
219 			if (xrow < PAGE_HEADER || xrow >= cLINES - 1)
220 				func = GLOBAL_PAGE_UP;
221 			else
222 				func = left_action();
223 			break;
224 
225 		case MOUSE_BUTTON_3:
226 			func = SPECIAL_MOUSE_TOGGLE;
227 			break;
228 
229 		default:
230 			break;
231 	}
232 	return func;
233 }
234 
235 
236 /*
237  * Make hidden part of article after ^L visible.
238  * Returns:
239  *    FALSE no ^L found, no changes
240  *    TRUE  ^L found and displayed page must be updated
241  *          (draw_page must be called)
242  */
243 static t_bool
deactivate_next_ctrl_l(void)244 deactivate_next_ctrl_l(
245 	void)
246 {
247 	int i;
248 	int end = curr_line + ARTLINES;
249 
250 	if (reveal_ctrl_l)
251 		return FALSE;
252 	if (end > artlines)
253 		end = artlines;
254 	for (i = reveal_ctrl_l_lines + 1; i < end; i++)
255 		if (artline[i].flags & C_CTRLL) {
256 			reveal_ctrl_l_lines = i;
257 			return TRUE;
258 		}
259 	reveal_ctrl_l_lines = end - 1;
260 	return FALSE;
261 }
262 
263 
264 /*
265  * Re-hide revealed part of article after last ^L
266  * that is currently displayed.
267  * Returns:
268  *    FALSE no ^L found, no changes
269  *    TRUE  ^L found and displayed page must be updated
270  *          (draw_page must be called)
271  */
272 static t_bool
activate_last_ctrl_l(void)273 activate_last_ctrl_l(
274 	void)
275 {
276 	int i;
277 
278 	if (reveal_ctrl_l)
279 		return FALSE;
280 	for (i = reveal_ctrl_l_lines; i >= curr_line; i--)
281 		if (artline[i].flags & C_CTRLL) {
282 			reveal_ctrl_l_lines = i - 1;
283 			return TRUE;
284 		}
285 	reveal_ctrl_l_lines = curr_line - 1;
286 	return FALSE;
287 }
288 
289 
290 /*
291  * The main routine for viewing articles
292  * Returns:
293  *    >=0	normal exit - return a new base[] note
294  *    <0	indicates some unusual condition. See GRP_* in tin.h
295  *			GRP_QUIT		User is doing a 'Q'
296  *			GRP_RETSELECT	Back to selection level due to 'T' command
297  *			GRP_ARTUNAVAIL	We didn't make it into the art
298  *							don't bother fixing the screen up
299  *			GRP_ARTABORT	User 'q'uit load of article
300  *			GRP_GOTOTHREAD	To thread menu due to 'l' command
301  *			GRP_NEXT		Catchup with 'c'
302  *			GRP_NEXTUNREAD	   "      "  'C'
303  */
304 int
show_page(struct t_group * group,int start_respnum,int * threadnum)305 show_page(
306 	struct t_group *group,
307 	int start_respnum,		/* index into arts[] */
308 	int *threadnum)			/* to allow movement in thread mode */
309 {
310 	char buf[LEN];
311 	char key[MAXKEYLEN];
312 	int i, j, n = 0;
313 	int art_type = GROUP_TYPE_NEWS;
314 	int hide_uue_tmp;
315 	t_artnum old_artnum = T_ARTNUM_CONST(0);
316 	t_bool mouse_click_on = TRUE;
317 	t_bool repeat_search;
318 	t_function func;
319 
320 	if (group->attribute->mailing_list != NULL)
321 		art_type = GROUP_TYPE_MAIL;
322 
323 	/*
324 	 * Peek to see if the pager started due to a body search
325 	 * Stop load_article() changing context again
326 	 */
327 	if (srch_lineno != -1)
328 		this_resp = start_respnum;
329 
330 	if ((i = load_article(start_respnum, group)) < 0)
331 		return i;
332 
333 	if (srch_lineno != -1)
334 		process_search(&curr_line, artlines, ARTLINES, PAGE_LEVEL);
335 
336 	forever {
337 		if ((func = handle_keypad(page_left, page_right, page_mouse_action, page_keys)) == GLOBAL_SEARCH_REPEAT) {
338 			func = last_search;
339 			repeat_search = TRUE;
340 		} else
341 			repeat_search = FALSE;
342 
343 		switch (func) {
344 			case GLOBAL_ABORT:	/* Abort */
345 				break;
346 
347 			case DIGIT_1:
348 			case DIGIT_2:
349 			case DIGIT_3:
350 			case DIGIT_4:
351 			case DIGIT_5:
352 			case DIGIT_6:
353 			case DIGIT_7:
354 			case DIGIT_8:
355 			case DIGIT_9:
356 				if (!HAS_FOLLOWUPS(which_thread(this_resp)))
357 					info_message(_(txt_no_responses));
358 				else {
359 					if ((n = prompt_response(func_to_key(func, page_keys), this_resp)) != -1) {
360 						XFACE_CLEAR();
361 						if ((i = load_article(n, group)) < 0)
362 							return i;
363 					}
364 				}
365 				break;
366 
367 #ifndef NO_SHELL_ESCAPE
368 			case GLOBAL_SHELL_ESCAPE:
369 				XFACE_CLEAR();
370 				shell_escape();
371 				draw_page(group->name, 0);
372 				break;
373 #endif /* !NO_SHELL_ESCAPE */
374 
375 			case SPECIAL_MOUSE_TOGGLE:
376 				if (mouse_click_on)
377 					set_xclick_off();
378 				else
379 					set_xclick_on();
380 				mouse_click_on = bool_not(mouse_click_on);
381 				break;
382 
383 			case GLOBAL_PAGE_UP:
384 				if (activate_last_ctrl_l())
385 					draw_page(group->name, 0);
386 				else {
387 					if (curr_line == 0)
388 						info_message(_(txt_begin_of_art));
389 					else {
390 						curr_line -= ((tinrc.scroll_lines == -2) ? ARTLINES / 2 : ARTLINES);
391 						draw_page(group->name, 0);
392 					}
393 				}
394 				break;
395 
396 			case GLOBAL_PAGE_DOWN:		/* page down or next response */
397 			case PAGE_NEXT_UNREAD:
398 				if (!((func == PAGE_NEXT_UNREAD) && (tinrc.goto_next_unread & GOTO_NEXT_UNREAD_TAB)) && deactivate_next_ctrl_l())
399 					draw_page(group->name, 0);
400 				else {
401 					if (curr_line + ARTLINES >= artlines) {	/* End is already on screen */
402 						switch (func) {
403 							case PAGE_NEXT_UNREAD:	/* <TAB> */
404 								goto page_goto_next_unread;
405 
406 							case GLOBAL_PAGE_DOWN:
407 								if (tinrc.goto_next_unread & GOTO_NEXT_UNREAD_PGDN)
408 									goto page_goto_next_unread;
409 								break;
410 
411 							default:		/* to keep gcc quiet */
412 								break;
413 						}
414 						info_message(_(txt_end_of_art));
415 					} else {
416 						if ((func == PAGE_NEXT_UNREAD) && (tinrc.goto_next_unread & GOTO_NEXT_UNREAD_TAB))
417 							goto page_goto_next_unread;
418 
419 						curr_line += ((tinrc.scroll_lines == -2) ? ARTLINES / 2 : ARTLINES);
420 
421 						if (tinrc.scroll_lines == -1)		/* formerly show_last_line_prev_page */
422 							curr_line--;
423 						draw_page(group->name, 0);
424 					}
425 				}
426 				break;
427 
428 page_goto_next_unread:
429 				XFACE_CLEAR();
430 				if ((n = next_unread(next_response(this_resp))) == -1)
431 					return (which_thread(this_resp));
432 				if ((i = load_article(n, group)) < 0)
433 					return i;
434 				break;
435 
436 			case GLOBAL_FIRST_PAGE:		/* beginning of article */
437 				if (reveal_ctrl_l_lines > -1 || curr_line != 0) {
438 					reveal_ctrl_l_lines = -1;
439 					curr_line = 0;
440 					draw_page(group->name, 0);
441 				}
442 				break;
443 
444 			case GLOBAL_LAST_PAGE:		/* end of article */
445 				if (reveal_ctrl_l_lines < artlines - 1 || curr_line + ARTLINES != artlines) {
446 					reveal_ctrl_l_lines = artlines - 1;
447 					/* Display a full last page for neatness */
448 					curr_line = artlines - ARTLINES;
449 					draw_page(group->name, 0);
450 				}
451 				break;
452 
453 			case GLOBAL_LINE_UP:
454 				if (activate_last_ctrl_l())
455 					draw_page(group->name, 0);
456 				else {
457 					if (curr_line == 0) {
458 						info_message(_(txt_begin_of_art));
459 						break;
460 					}
461 
462 					i = scroll_page(KEYMAP_UP);
463 					curr_line += i;
464 					draw_page(group->name, i);
465 				}
466 				break;
467 
468 			case GLOBAL_LINE_DOWN:
469 				if (deactivate_next_ctrl_l())
470 					draw_page(group->name, 0);
471 				else {
472 					if (curr_line + ARTLINES >= artlines) {
473 						info_message(_(txt_end_of_art));
474 						break;
475 					}
476 
477 					i = scroll_page(KEYMAP_DOWN);
478 					curr_line += i;
479 					draw_page(group->name, i);
480 				}
481 				break;
482 
483 			case GLOBAL_LAST_VIEWED:	/* show last viewed article */
484 				if (last_resp < 0 || (which_thread(last_resp) == -1)) {
485 					info_message(_(txt_no_last_message));
486 					break;
487 				}
488 				if ((i = load_article(last_resp, group)) < 0) {
489 					XFACE_CLEAR();
490 					return i;
491 				}
492 				break;
493 
494 			case GLOBAL_LOOKUP_MESSAGEID:			/* Goto article by Message-ID */
495 				if ((n = prompt_msgid()) != ART_UNAVAILABLE) {
496 					if ((i = load_article(n, group)) < 0) {
497 						XFACE_CLEAR();
498 						return i;
499 					}
500 				}
501 				break;
502 
503 			case PAGE_GOTO_PARENT:		/* Goto parent of this article */
504 			{
505 				struct t_msgid *parent = arts[this_resp].refptr->parent;
506 
507 				if (parent == NULL) {
508 					info_message(_(txt_art_parent_none));
509 					break;
510 				}
511 
512 				if (parent->article == ART_UNAVAILABLE) {
513 					info_message(_(txt_art_parent_unavail));
514 					break;
515 				}
516 
517 				if (arts[parent->article].killed && tinrc.kill_level == KILL_NOTHREAD) {
518 					info_message(_(txt_art_parent_killed));
519 					break;
520 				}
521 
522 				if ((i = load_article(parent->article, group)) < 0) {
523 					XFACE_CLEAR();
524 					return i;
525 				}
526 
527 				break;
528 			}
529 
530 			case GLOBAL_PIPE:		/* pipe article/thread/tagged arts to command */
531 				XFACE_SUPPRESS();
532 				feed_articles(FEED_PIPE, PAGE_LEVEL, NOT_ASSIGNED, group, this_resp);
533 				XFACE_SHOW();
534 				break;
535 
536 			case PAGE_MAIL:	/* mail article/thread/tagged articles to somebody */
537 				XFACE_SUPPRESS();
538 				feed_articles(FEED_MAIL, PAGE_LEVEL, NOT_ASSIGNED, group, this_resp);
539 				XFACE_SHOW();
540 				break;
541 
542 #ifndef DISABLE_PRINTING
543 			case GLOBAL_PRINT:	/* output art/thread/tagged arts to printer */
544 				XFACE_SUPPRESS();
545 				feed_articles(FEED_PRINT, PAGE_LEVEL, NOT_ASSIGNED, group, this_resp);
546 				XFACE_SHOW();
547 				break;
548 #endif /* !DISABLE_PRINTING */
549 
550 			case PAGE_REPOST:	/* repost current article */
551 				if (can_post) {
552 					XFACE_SUPPRESS();
553 					feed_articles(FEED_REPOST, PAGE_LEVEL, NOT_ASSIGNED, group, this_resp);
554 					XFACE_SHOW();
555 				} else
556 					info_message(_(txt_cannot_post));
557 				break;
558 
559 			case PAGE_SAVE:	/* save article/thread/tagged articles */
560 				XFACE_SUPPRESS();
561 				feed_articles(FEED_SAVE, PAGE_LEVEL, NOT_ASSIGNED, group, this_resp);
562 				XFACE_SHOW();
563 				break;
564 
565 			case PAGE_AUTOSAVE:	/* Auto-save articles without prompting */
566 				if (grpmenu.curr >= 0) {
567 					XFACE_SUPPRESS();
568 					feed_articles(FEED_AUTOSAVE, PAGE_LEVEL, NOT_ASSIGNED, group, (int) base[grpmenu.curr]);
569 					XFACE_SHOW();
570 				}
571 				break;
572 
573 			case GLOBAL_SEARCH_REPEAT:
574 				info_message(_(txt_no_prev_search));
575 				break;
576 
577 			case GLOBAL_SEARCH_SUBJECT_FORWARD:	/* search in article */
578 			case GLOBAL_SEARCH_SUBJECT_BACKWARD:
579 				if (search_article((func == GLOBAL_SEARCH_SUBJECT_FORWARD), repeat_search, search_line, artlines, artline, reveal_ctrl_l_lines, note_fp) == -1)
580 					break;
581 
582 				if (func == GLOBAL_SEARCH_SUBJECT_BACKWARD && !reveal_ctrl_l) {
583 					reveal_ctrl_l_lines = curr_line + ARTLINES - 1;
584 					draw_page(group->name, 0);
585 				}
586 				process_search(&curr_line, artlines, ARTLINES, PAGE_LEVEL);
587 				break;
588 
589 			case GLOBAL_SEARCH_BODY:	/* article body search */
590 				if ((n = search_body(group, this_resp, repeat_search)) != -1) {
591 					this_resp = n;			/* Stop load_article() changing context again */
592 					if ((i = load_article(n, group)) < 0) {
593 						XFACE_CLEAR();
594 						return i;
595 					}
596 					process_search(&curr_line, artlines, ARTLINES, PAGE_LEVEL);
597 				}
598 				break;
599 
600 			case PAGE_TOP_THREAD:	/* first article in current thread */
601 				if (arts[this_resp].prev >= 0) {
602 					if ((n = which_thread(this_resp)) >= 0 && base[n] != this_resp) {
603 						assert(n < grpmenu.max);
604 						if ((i = load_article(base[n], group)) < 0) {
605 							XFACE_CLEAR();
606 							return i;
607 						}
608 					}
609 				}
610 				break;
611 
612 			case PAGE_BOTTOM_THREAD:	/* last article in current thread */
613 				for (i = this_resp; i >= 0; i = arts[i].thread)
614 					n = i;
615 
616 				if (n != this_resp) {
617 					if ((i = load_article(n, group)) < 0) {
618 						XFACE_CLEAR();
619 						return i;
620 					}
621 				}
622 				break;
623 
624 			case PAGE_NEXT_THREAD:	/* start of next thread */
625 				XFACE_CLEAR();
626 				if ((n = next_thread(this_resp)) == -1)
627 					return (which_thread(this_resp));
628 				if ((i = load_article(n, group)) < 0)
629 					return i;
630 				break;
631 
632 #ifdef HAVE_PGP_GPG
633 			case PAGE_PGP_CHECK_ARTICLE:
634 				XFACE_SUPPRESS();
635 				if (pgp_check_article(&pgart))
636 					draw_page(group->name, 0);
637 				XFACE_SHOW();
638 				break;
639 #endif /* HAVE_PGP_GPG */
640 
641 			case PAGE_TOGGLE_HEADERS:	/* toggle display of all headers */
642 				XFACE_CLEAR();
643 				show_all_headers = bool_not(show_all_headers);
644 				resize_article(TRUE, &pgart);	/* Also recooks it.. */
645 				curr_line = 0;
646 				draw_page(group->name, 0);
647 				break;
648 
649 			case PAGE_TOGGLE_RAW:	/* toggle display of whole 'raw' article */
650 				XFACE_CLEAR();
651 				toggle_raw(group);
652 				break;
653 
654 			case PAGE_TOGGLE_TEX2ISO:		/* toggle german TeX to ISO latin1 style conversion */
655 				if (((group->attribute->tex2iso_conv) = !(group->attribute->tex2iso_conv)))
656 					pgart.tex2iso = is_art_tex_encoded(pgart.raw);
657 				else
658 					pgart.tex2iso = FALSE;
659 
660 				resize_article(TRUE, &pgart);	/* Also recooks it.. */
661 				draw_page(group->name, 0);
662 				info_message(_(txt_toggled_tex2iso), txt_onoff[group->attribute->tex2iso_conv != FALSE ? 1 : 0]);
663 				break;
664 
665 			case PAGE_TOGGLE_TABS:		/* toggle tab stops 8 vs 4 */
666 				tabwidth = (tabwidth == 8) ? 4 : 8;
667 				resize_article(TRUE, &pgart);	/* Also recooks it.. */
668 				draw_page(group->name, 0);
669 				info_message(_(txt_toggled_tabwidth), tabwidth);
670 				break;
671 
672 			case PAGE_TOGGLE_UUE:			/* toggle display of uuencoded sections */
673 				hide_uue = (hide_uue + 1) % (UUE_ALL + 1);
674 				resize_article(TRUE, &pgart);	/* Also recooks it.. */
675 				/*
676 				 * If we hid uue and are off the end of the article, reposition to
677 				 * show last page for neatness
678 				 */
679 				if (hide_uue && curr_line + ARTLINES > artlines)
680 					curr_line = artlines - ARTLINES;
681 				draw_page(group->name, 0);
682 				/* TODO: info_message()? */
683 				break;
684 
685 			case PAGE_REVEAL:			/* toggle hiding after ^L */
686 				reveal_ctrl_l = bool_not(reveal_ctrl_l);
687 				if (!reveal_ctrl_l) {	/* switched back to active ^L's */
688 					reveal_ctrl_l_lines = -1;
689 					curr_line = 0;
690 				} else
691 					reveal_ctrl_l_lines = artlines - 1;
692 				draw_page(group->name, 0);
693 				/* TODO: info_message()? */
694 				break;
695 
696 			case GLOBAL_QUICK_FILTER_SELECT:	/* quickly auto-select article */
697 			case GLOBAL_QUICK_FILTER_KILL:		/* quickly kill article */
698 				if (quick_filter(func, group, &arts[this_resp])) {
699 					old_artnum = arts[this_resp].artnum;
700 					unfilter_articles(group);
701 					filter_articles(group);
702 					make_threads(group, FALSE);
703 					if ((n = find_artnum(old_artnum)) == -1 || which_thread(n) == -1) /* We have lost the thread */
704 						return GRP_KILLED;
705 					this_resp = n;
706 					draw_page(group->name, 0);
707 					info_message((func == GLOBAL_QUICK_FILTER_KILL) ? _(txt_info_add_kill) : _(txt_info_add_select));
708 				}
709 				break;
710 
711 			case GLOBAL_MENU_FILTER_SELECT:		/* auto-select article menu */
712 			case GLOBAL_MENU_FILTER_KILL:			/* kill article menu */
713 				XFACE_CLEAR();
714 				if (filter_menu(func, group, &arts[this_resp])) {
715 					old_artnum = arts[this_resp].artnum;
716 					unfilter_articles(group);
717 					filter_articles(group);
718 					make_threads(group, FALSE);
719 					if ((n = find_artnum(old_artnum)) == -1 || which_thread(n) == -1) /* We have lost the thread */
720 						return GRP_KILLED;
721 					this_resp = n;
722 				}
723 				draw_page(group->name, 0);
724 				break;
725 
726 			case GLOBAL_EDIT_FILTER:
727 				XFACE_CLEAR();
728 				if (invoke_editor(filter_file, filter_file_offset, NULL)) {
729 					old_artnum = arts[this_resp].artnum;
730 					unfilter_articles(group);
731 					(void) read_filter_file(filter_file);
732 					filter_articles(group);
733 					make_threads(group, FALSE);
734 					if ((n = find_artnum(old_artnum)) == -1 || which_thread(n) == -1) /* We have lost the thread */
735 						return GRP_KILLED;
736 					this_resp = n;
737 				}
738 				draw_page(group->name, 0);
739 				break;
740 
741 			case GLOBAL_REDRAW_SCREEN:		/* redraw current page of article */
742 				my_retouch();
743 				draw_page(group->name, 0);
744 				break;
745 
746 			case PAGE_TOGGLE_ROT13:	/* toggle rot-13 mode */
747 				rotate = rotate ? 0 : 13;
748 				draw_page(group->name, 0);
749 				info_message(_(txt_toggled_rot13));
750 				break;
751 
752 			case GLOBAL_SEARCH_AUTHOR_FORWARD:	/* author search forward */
753 			case GLOBAL_SEARCH_AUTHOR_BACKWARD:	/* author search backward */
754 				if ((n = search(func, this_resp, repeat_search)) < 0)
755 					break;
756 				if ((i = load_article(n, group)) < 0) {
757 					XFACE_CLEAR();
758 					return i;
759 				}
760 				break;
761 
762 			case CATCHUP:			/* catchup - mark read, goto next */
763 			case CATCHUP_NEXT_UNREAD:	/* goto next unread */
764 				if (group->attribute->thread_articles == THREAD_NONE)
765 					snprintf(buf, sizeof(buf), _(txt_mark_art_read), (func == CATCHUP_NEXT_UNREAD) ? _(txt_enter_next_unread_art) : "");
766 				else
767 					snprintf(buf, sizeof(buf), _(txt_mark_thread_read), (func == CATCHUP_NEXT_UNREAD) ? _(txt_enter_next_thread) : "");
768 				if ((!TINRC_CONFIRM_ACTION) || prompt_yn(buf, TRUE) == 1) {
769 					thd_mark_read(group, base[which_thread(this_resp)]);
770 					XFACE_CLEAR();
771 					return (func == CATCHUP_NEXT_UNREAD) ? GRP_NEXTUNREAD : GRP_NEXT;
772 				}
773 				break;
774 
775 			case MARK_THREAD_UNREAD:
776 				thd_mark_unread(group, base[which_thread(this_resp)]);
777 				if (group->attribute->thread_articles != THREAD_NONE)
778 					info_message(_(txt_marked_as_unread), _(txt_thread_upper));
779 				else
780 					info_message(_(txt_marked_as_unread), _(txt_article_upper));
781 				break;
782 
783 			case PAGE_CANCEL:			/* cancel an article */
784 				if (can_post || art_type != GROUP_TYPE_NEWS) {
785 					XFACE_SUPPRESS();
786 					if (cancel_article(group, &arts[this_resp], this_resp))
787 						draw_page(group->name, 0);
788 					XFACE_SHOW();
789 				} else
790 					info_message(_(txt_cannot_post));
791 				break;
792 
793 			case PAGE_EDIT_ARTICLE:		/* edit an article (mailgroup only) */
794 				XFACE_SUPPRESS();
795 				if (art_edit(group, &arts[this_resp]))
796 					draw_page(group->name, 0);
797 				XFACE_SHOW();
798 				break;
799 
800 			case PAGE_FOLLOWUP_QUOTE:		/* post a followup to this article */
801 			case PAGE_FOLLOWUP_QUOTE_HEADERS:
802 			case PAGE_FOLLOWUP:
803 				if (!can_post && art_type == GROUP_TYPE_NEWS) {
804 					info_message(_(txt_cannot_post));
805 					break;
806 				}
807 				XFACE_CLEAR();
808 				(void) post_response(group->name, this_resp,
809 				  (func == PAGE_FOLLOWUP_QUOTE || func == PAGE_FOLLOWUP_QUOTE_HEADERS) ? TRUE : FALSE,
810 				  func == PAGE_FOLLOWUP_QUOTE_HEADERS ? TRUE : FALSE, show_raw_article);
811 				draw_page(group->name, 0);
812 				break;
813 
814 			case GLOBAL_HELP:	/* help */
815 				XFACE_CLEAR();
816 				show_help_page(PAGE_LEVEL, _(txt_art_pager_com));
817 				draw_page(group->name, 0);
818 				break;
819 
820 			case GLOBAL_TOGGLE_HELP_DISPLAY:	/* toggle mini help menu */
821 				toggle_mini_help(PAGE_LEVEL);
822 				draw_page(group->name, 0);
823 				break;
824 
825 			case GLOBAL_QUIT:	/* return to index page */
826 return_to_index:
827 				XFACE_CLEAR();
828 				i = which_thread(this_resp);
829 				if (threadnum)
830 					*threadnum = which_response(this_resp);
831 
832 				return i;
833 
834 			case GLOBAL_TOGGLE_INVERSE_VIDEO:	/* toggle inverse video */
835 				toggle_inverse_video();
836 				draw_page(group->name, 0);
837 				show_inverse_video_status();
838 				break;
839 
840 #ifdef HAVE_COLOR
841 			case GLOBAL_TOGGLE_COLOR:		/* toggle color */
842 				if (toggle_color()) {
843 					draw_page(group->name, 0);
844 					show_color_status();
845 				}
846 				break;
847 #endif /* HAVE_COLOR */
848 
849 			case PAGE_LIST_THREAD:	/* -> thread page that this article is in */
850 				XFACE_CLEAR();
851 				fixup_thread(this_resp, FALSE);
852 				return GRP_GOTOTHREAD;
853 
854 			case GLOBAL_OPTION_MENU:	/* option menu */
855 				XFACE_CLEAR();
856 				old_artnum = arts[this_resp].artnum;
857 				config_page(group->name, signal_context);
858 				if ((this_resp = find_artnum(old_artnum)) == -1 || which_thread(this_resp) == -1) { /* We have lost the thread */
859 					pos_first_unread_thread();
860 					return GRP_EXIT;
861 				}
862 				fixup_thread(this_resp, FALSE);
863 				draw_page(group->name, 0);
864 				break;
865 
866 			case PAGE_NEXT_ARTICLE:	/* skip to next article */
867 				XFACE_CLEAR();
868 				if ((n = next_response(this_resp)) == -1)
869 					return (which_thread(this_resp));
870 
871 				if ((i = load_article(n, group)) < 0)
872 					return i;
873 				break;
874 
875 			case PAGE_MARK_THREAD_READ:	/* mark rest of thread as read */
876 				thd_mark_read(group, this_resp);
877 				if ((n = next_unread(next_response(this_resp))) == -1)
878 					goto return_to_index;
879 				if ((i = load_article(n, group)) < 0) {
880 					XFACE_CLEAR();
881 					return i;
882 				}
883 				break;
884 
885 			case PAGE_NEXT_UNREAD_ARTICLE:	/* next unread article */
886 				goto page_goto_next_unread;
887 
888 			case PAGE_PREVIOUS_ARTICLE:	/* previous article */
889 				XFACE_CLEAR();
890 				if ((n = prev_response(this_resp)) == -1)
891 					return this_resp;
892 
893 				if ((i = load_article(n, group)) < 0)
894 					return i;
895 				break;
896 
897 			case PAGE_PREVIOUS_UNREAD_ARTICLE:	/* previous unread article */
898 				if ((n = prev_unread(prev_response(this_resp))) == -1)
899 					info_message(_(txt_no_prev_unread_art));
900 				else {
901 					if ((i = load_article(n, group)) < 0) {
902 						XFACE_CLEAR();
903 						return i;
904 					}
905 				}
906 				break;
907 
908 			case GLOBAL_QUIT_TIN:	/* quit */
909 				XFACE_CLEAR();
910 				return GRP_QUIT;
911 
912 			case PAGE_REPLY_QUOTE:	/* reply to author through mail */
913 			case PAGE_REPLY_QUOTE_HEADERS:
914 			case PAGE_REPLY:
915 				XFACE_CLEAR();
916 				mail_to_author(group->name, this_resp, (func == PAGE_REPLY_QUOTE || func == PAGE_REPLY_QUOTE_HEADERS) ? TRUE : FALSE, func == PAGE_REPLY_QUOTE_HEADERS ? TRUE : FALSE, show_raw_article);
917 				draw_page(group->name, 0);
918 				break;
919 
920 			case PAGE_TAG:	/* tag/untag article for saving */
921 				tag_article(this_resp);
922 				break;
923 
924 			case PAGE_GROUP_SELECT:	/* return to group selection page */
925 #if 0
926 				/* Hasn't been used since tin 1.1 PL4 */
927 				if (filter_state == FILTERING) {
928 					filter_articles(group);
929 					make_threads(group, FALSE);
930 				}
931 #endif /* 0 */
932 				XFACE_CLEAR();
933 				return GRP_RETSELECT;
934 
935 			case GLOBAL_VERSION:
936 				info_message(cvers);
937 				break;
938 
939 			case GLOBAL_POST:	/* post a basenote */
940 				XFACE_SUPPRESS();
941 				if (post_article(group->name))
942 					draw_page(group->name, 0);
943 				XFACE_SHOW();
944 				break;
945 
946 			case GLOBAL_POSTPONED:	/* post postponed article */
947 				if (can_post || art_type != GROUP_TYPE_NEWS) {
948 					XFACE_SUPPRESS();
949 					if (pickup_postponed_articles(FALSE, FALSE))
950 						draw_page(group->name, 0);
951 					XFACE_SHOW();
952 				} else
953 					info_message(_(txt_cannot_post));
954 				break;
955 
956 			case GLOBAL_DISPLAY_POST_HISTORY:	/* display messages posted by user */
957 				XFACE_SUPPRESS();
958 				if (user_posted_messages())
959 					draw_page(group->name, 0);
960 				XFACE_SHOW();
961 				break;
962 
963 			case MARK_ARTICLE_UNREAD:	/* mark article as unread(to return) */
964 				art_mark(group, &arts[this_resp], ART_WILL_RETURN);
965 				info_message(_(txt_marked_as_unread), _(txt_article_upper));
966 				break;
967 
968 			case PAGE_SKIP_INCLUDED_TEXT:	/* skip included text */
969 				for (i = j = curr_line; i < artlines; i++) {
970 					if (artline[i].flags & (C_QUOTE1 | C_QUOTE2 | C_QUOTE3)) {
971 						j = i;
972 						break;
973 					}
974 				}
975 
976 				for (; j < artlines; j++) {
977 					if (!(artline[j].flags & (C_QUOTE1 | C_QUOTE2 | C_QUOTE3)))
978 						break;
979 				}
980 
981 				if (j != curr_line) {
982 					curr_line = j;
983 					draw_page(group->name, 0);
984 				}
985 				break;
986 
987 			case GLOBAL_TOGGLE_INFO_LAST_LINE: /* this is _not_ correct, we do not toggle status here */
988 				info_message("%s", arts[this_resp].subject);
989 				break;
990 
991 			case PAGE_TOGGLE_HIGHLIGHTING:
992 				word_highlight = bool_not(word_highlight);
993 				draw_page(group->name, 0);
994 				info_message(_(txt_toggled_high), txt_onoff[word_highlight != FALSE ? 1 : 0]);
995 				break;
996 
997 			case PAGE_VIEW_ATTACHMENTS:
998 				XFACE_SUPPRESS();
999 				hide_uue_tmp = hide_uue;
1000 				hide_uue = UUE_NO;
1001 				resize_article(TRUE, &pgart);
1002 				attachment_page(&pgart);
1003 				hide_uue = hide_uue_tmp;
1004 				resize_article(TRUE, &pgart);
1005 				draw_page(group->name, 0);
1006 				XFACE_SHOW();
1007 				break;
1008 
1009 			case PAGE_VIEW_URL:
1010 				if (!show_raw_article) { /* cooked mode? */
1011 					t_bool success;
1012 
1013 					XFACE_SUPPRESS();
1014 					resize_article(FALSE, &pgart); /* unbreak long lines */
1015 					success = url_page();
1016 					resize_article(TRUE, &pgart); /* rebreak long lines */
1017 					draw_page(group->name, 0);
1018 					if (!success)
1019 						info_message(_(txt_url_done));
1020 					XFACE_SHOW();
1021 				}
1022 				break;
1023 
1024 			default:
1025 				info_message(_(txt_bad_command), printascii(key, func_to_key(GLOBAL_HELP, page_keys)));
1026 		}
1027 	}
1028 	/* NOTREACHED */
1029 	return GRP_ARTUNAVAIL;
1030 }
1031 
1032 
1033 static void
print_message_page(FILE * file,t_lineinfo * messageline,size_t messagelines,size_t base_line,size_t begin,size_t end,int help_level)1034 print_message_page(
1035 	FILE *file,
1036 	t_lineinfo *messageline,
1037 	size_t messagelines,
1038 	size_t base_line,
1039 	size_t begin,
1040 	size_t end,
1041 	int help_level)
1042 {
1043 	char *line;
1044 	char *p;
1045 	int bytes;
1046 	size_t i = begin;
1047 	t_lineinfo *curr;
1048 
1049 	for (; i < end; i++) {
1050 		if (base_line + i >= messagelines)		/* ran out of message */
1051 			break;
1052 
1053 		curr = &messageline[base_line + i];
1054 
1055 		if (fseek(file, curr->offset, SEEK_SET) != 0)
1056 			break;
1057 
1058 		if ((line = tin_fgets(file, FALSE)) == NULL)
1059 			break;	/* ran out of message */
1060 
1061 		if ((help_level == INFO_PAGER) && (strwidth(line) >= cCOLS - 1))
1062 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
1063 			{
1064 				char *tmp, *f, *t;
1065 
1066 				f = tmp = strunc(line, cCOLS - 1);
1067 				t = line;
1068 				while (*f)
1069 					*t++ = *f++;
1070 				*t = '\0';
1071 				free(tmp);
1072 			}
1073 #else
1074 			line[cCOLS - 1] = '\0';
1075 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
1076 
1077 		/*
1078 		 * use the offsets gained while doing line wrapping to
1079 		 * determine the correct position to truncate the line
1080 		 */
1081 		if ((help_level != INFO_PAGER) && (base_line + i < messagelines - 1)) {	/* not last line of message */
1082 			bytes = (curr + 1)->offset - curr->offset;
1083 			line[bytes] = '\0';
1084 		}
1085 
1086 		/*
1087 		 * rotN encoding on body and sig data only
1088 		 */
1089 		if ((rotate != 0) && ((curr->flags & (C_BODY | C_SIG)) || show_raw_article)) {
1090 			for (p = line; *p; p++) {
1091 				if (*p >= 'A' && *p <= 'Z')
1092 					*p = (*p - 'A' + rotate) % 26 + 'A';
1093 				else if (*p >= 'a' && *p <= 'z')
1094 					*p = (*p - 'a' + rotate) % 26 + 'a';
1095 			}
1096 		}
1097 
1098 		strip_line(line);
1099 
1100 #ifndef USE_CURSES
1101 		snprintf(screen[i + scroll_region_top].col, cCOLS, "%s" cCRLF, line);
1102 #endif /* !USE_CURSES */
1103 
1104 		MoveCursor(i + scroll_region_top, 0);
1105 		draw_pager_line(line, curr->flags, show_raw_article);
1106 
1107 		/*
1108 		 * Highlight URL's and mail addresses
1109 		 */
1110 		if (tinrc.url_highlight) {
1111 			if (curr->flags & C_URL)
1112 #ifdef HAVE_COLOR
1113 				highlight_regexes(i + scroll_region_top, &url_regex, use_color ? tinrc.col_urls : -1);
1114 #else
1115 				highlight_regexes(i + scroll_region_top, &url_regex, -1);
1116 #endif /* HAVE_COLOR */
1117 
1118 			if (curr->flags & C_MAIL)
1119 #ifdef HAVE_COLOR
1120 				highlight_regexes(i + scroll_region_top, &mail_regex, use_color ? tinrc.col_urls : -1);
1121 #else
1122 				highlight_regexes(i + scroll_region_top, &mail_regex, -1);
1123 #endif /* HAVE_COLOR */
1124 
1125 			if (curr->flags & C_NEWS)
1126 #ifdef HAVE_COLOR
1127 				highlight_regexes(i + scroll_region_top, &news_regex, use_color ? tinrc.col_urls : -1);
1128 #else
1129 				highlight_regexes(i + scroll_region_top, &news_regex, -1);
1130 #endif /* HAVE_COLOR */
1131 		}
1132 
1133 		/*
1134 		 * Highlight /slashes/, *stars*, _underscores_ and -strokes-
1135 		 */
1136 		if (word_highlight && (curr->flags & C_BODY) && !(curr->flags & C_CTRLL)) {
1137 #ifdef HAVE_COLOR
1138 			highlight_regexes(i + scroll_region_top, &slashes_regex, use_color ? tinrc.col_markslash : tinrc.mono_markslash);
1139 			highlight_regexes(i + scroll_region_top, &stars_regex, use_color ? tinrc.col_markstar : tinrc.mono_markstar);
1140 			highlight_regexes(i + scroll_region_top, &underscores_regex, use_color ? tinrc.col_markdash : tinrc.mono_markdash);
1141 			highlight_regexes(i + scroll_region_top, &strokes_regex, use_color ? tinrc.col_markstroke : tinrc.mono_markstroke);
1142 #else
1143 			highlight_regexes(i + scroll_region_top, &slashes_regex, tinrc.mono_markslash);
1144 			highlight_regexes(i + scroll_region_top, &stars_regex, tinrc.mono_markstar);
1145 			highlight_regexes(i + scroll_region_top, &underscores_regex, tinrc.mono_markdash);
1146 			highlight_regexes(i + scroll_region_top, &strokes_regex, tinrc.mono_markstroke);
1147 #endif /* HAVE_COLOR */
1148 		}
1149 
1150 		/* Blank the screen after a ^L (only occurs when showing cooked) */
1151 		if (!reveal_ctrl_l && (curr->flags & C_CTRLL) && (int) (base_line + i) > reveal_ctrl_l_lines) {
1152 			CleartoEOS();
1153 			break;
1154 		}
1155 	}
1156 
1157 #ifdef HAVE_COLOR
1158 	fcol(tinrc.col_text);
1159 #endif /* HAVE_COLOR */
1160 
1161 	show_mini_help(help_level);
1162 }
1163 
1164 
1165 /*
1166  * Redraw the current page, curr_line will be the first line displayed
1167  * Everything that calls draw_page() just sets curr_line, this function must
1168  * ensure it is set to something sane
1169  * If part is !=0, then only draw the first (-ve) or last (+ve) few lines
1170  */
1171 void
draw_page(const char * group,int part)1172 draw_page(
1173 	const char *group,
1174 	int part)
1175 {
1176 	int start, end;	/* 1st, last line to draw */
1177 
1178 	signal_context = cPage;
1179 
1180 	/*
1181 	 * Can't do partial draw if term can't scroll properly
1182 	 */
1183 	if (part && !have_linescroll)
1184 		part = 0;
1185 
1186 	/*
1187 	 * Ensure curr_line is in bounds
1188 	 */
1189 	if (curr_line < 0)
1190 		curr_line = 0;			/* Oops - off the top */
1191 	else {
1192 		if (curr_line > artlines)
1193 			curr_line = artlines;	/* Oops - off the end */
1194 	}
1195 
1196 	search_line = curr_line;	/* Reset search to start from top of display */
1197 
1198 	scroll_region_top = PAGE_HEADER;
1199 
1200 	/* Down-scroll, only redraw bottom 'part' lines of screen */
1201 	if ((start = (part > 0) ? ARTLINES - part : 0) < 0)
1202 		start = 0;
1203 
1204 	/* Up-scroll, only redraw the top 'part' lines of screen */
1205 	if ((end = (part < 0) ? -part : ARTLINES) > ARTLINES)
1206 		end = ARTLINES;
1207 
1208 	/*
1209 	 * ncurses doesn't clear the scroll area when you scroll by more than the
1210 	 * window size - force full redraw
1211 	 */
1212 	if ((end - start >= ARTLINES) || (part == 0)) {
1213 		ClearScreen();
1214 		draw_page_header(group);
1215 	} else
1216 		MoveCursor(0, 0);
1217 
1218 	print_message_page(note_fp, artline, artlines, curr_line, start, end, PAGE_LEVEL);
1219 
1220 	/*
1221 	 * Print an appropriate footer
1222 	 */
1223 	if (curr_line + ARTLINES >= artlines) {
1224 		char buf[LEN], *buf2;
1225 		int len;
1226 
1227 		STRCPY(buf, (arts[this_resp].thread != -1) ? _(txt_next_resp) : _(txt_last_resp));
1228 		buf2 = strunc(buf, cCOLS - 1);
1229 		len = strwidth(buf2);
1230 		clear_message();
1231 		MoveCursor(cLINES, cCOLS - len - (1 + BLANK_PAGE_COLS));
1232 #ifdef HAVE_COLOR
1233 		fcol(tinrc.col_normal);
1234 #endif /* HAVE_COLOR */
1235 		StartInverse();
1236 		my_fputs(buf2, stdout);
1237 		EndInverse();
1238 		my_flush();
1239 		free(buf2);
1240 	} else
1241 		draw_percent_mark(curr_line + ARTLINES, artlines);
1242 
1243 #ifdef XFACE_ABLE
1244 	if (tinrc.use_slrnface && !show_raw_article)
1245 		slrnface_display_xface(note_h->xface);
1246 #endif /* XFACE_ABLE */
1247 
1248 	stow_cursor();
1249 }
1250 
1251 
1252 /*
1253  * Start external metamail program
1254  */
1255 static void
invoke_metamail(FILE * fp)1256 invoke_metamail(
1257 	FILE *fp)
1258 {
1259 	char *ptr = tinrc.metamail_prog;
1260 	char buf[LEN];
1261 	long offset;
1262 	FILE *mime_fp;
1263 #ifdef DONT_HAVE_PIPING
1264 	char mimefile[PATH_LEN];
1265 	int fd_mime;
1266 #endif /* DONT_HAVE_PIPING */
1267 
1268 	if ((*ptr == '\0') || (!strcmp(ptr, INTERNAL_CMD)) || (getenv("NOMETAMAIL") != NULL))
1269 		return;
1270 
1271 	if ((offset = ftell(fp)) == -1) {
1272 		perror_message(_(txt_command_failed), ptr);
1273 		return;
1274 	}
1275 
1276 	EndWin();
1277 	Raw(FALSE);
1278 
1279 #ifdef DONT_HAVE_PIPING
1280 	if ((fd_mime = my_tmpfile(mimefile, sizeof(mimefile) - 1, homedir)) == -1) {
1281 		perror_message(_(txt_command_failed), ptr);
1282 		return;
1283 	}
1284 	if ((mime_fp = fdopen(fd_mime, "w")))
1285 #else
1286 	if ((mime_fp = popen(ptr, "w")))
1287 #endif /* DONT_HAVE_PIPING */
1288 	{
1289 		rewind(fp);
1290 		while (fgets(buf, (int) sizeof(buf), fp) != NULL)
1291 			fputs(buf, mime_fp);
1292 
1293 		fflush(mime_fp);
1294 		/* This is needed if we are viewing the raw art */
1295 		fseek(fp, offset, SEEK_SET);	/* goto old position */
1296 
1297 #ifdef DONT_HAVE_PIPING
1298 		snprintf(buf, sizeof(buf) - 1, "%s %s", tinrc.metamail_prog, mimefile);
1299 		invoke_cmd(buf);
1300 		fclose(mime_fp);
1301 		unlink(mimefile);
1302 #else
1303 		pclose(mime_fp);
1304 #endif /* DONT_HAVE_PIPING */
1305 	} else
1306 		perror_message(_(txt_command_failed), ptr);
1307 
1308 #ifdef USE_CURSES
1309 	Raw(TRUE);
1310 	InitWin();
1311 #endif /* USE_CURSES */
1312 	prompt_continue();
1313 #ifndef USE_CURSES
1314 	Raw(TRUE);
1315 	InitWin();
1316 #endif /* !USE_CURSES */
1317 }
1318 
1319 
1320 /*
1321  * PAGE_HEADER defines the size in lines of this header
1322  */
1323 static void
draw_page_header(const char * group)1324 draw_page_header(
1325 	const char *group)
1326 {
1327 	char *buf, *tmp;
1328 	int i;
1329 	int whichresp, x_resp;
1330 	int len, right_len, center_pos, cur_pos;
1331 	size_t line_len;
1332 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
1333 	wchar_t *fmt_resp, *fmt_thread, *wtmp, *wtmp2, *wbuf;
1334 #else
1335 	char *tmp2;
1336 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
1337 
1338 	whichresp = which_response(this_resp);
1339 	x_resp = num_of_responses(which_thread(this_resp));
1340 
1341 	line_len = LEN + 1;
1342 	buf = my_malloc(line_len);
1343 
1344 	if (!my_strftime(buf, line_len, curr_group->attribute->date_format, localtime(&arts[this_resp].date))) {
1345 		strncpy(buf, BlankIfNull(note_h->date), line_len);
1346 		buf[line_len - 1] = '\0';
1347 	}
1348 
1349 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
1350 	/* convert to wide-char format strings */
1351 	fmt_thread = char2wchar_t(_(txt_thread_x_of_n));
1352 	fmt_resp = char2wchar_t(_(txt_art_x_of_n));
1353 
1354 	/*
1355 	 * determine the needed space for the text at the right hand margin
1356 	 * the formatting info (%4s) needs 3 positions but we need 4 positions
1357 	 * on the screen for each counter.
1358 	 */
1359 	if (fmt_thread && fmt_resp)
1360 		right_len = MAX((wcswidth(fmt_thread, wcslen(fmt_thread)) - 6 + 8), (wcswidth(fmt_resp, wcslen(fmt_resp)) - 6 + 8));
1361 	else if (fmt_thread)
1362 		right_len = wcswidth(fmt_thread, wcslen(fmt_thread)) - 6 + 8;
1363 	else if (fmt_resp)
1364 		right_len = wcswidth(fmt_resp, wcslen(fmt_resp)) - 6 + 8;
1365 	else
1366 		right_len = 0;
1367 	FreeIfNeeded(fmt_thread);
1368 	FreeIfNeeded(fmt_resp);
1369 
1370 	/*
1371 	 * limit right_len to cCOLS / 3
1372 	 */
1373 	if (right_len > cCOLS / 3 + 1)
1374 		right_len = cCOLS / 3 + 1;
1375 
1376 #	ifdef HAVE_COLOR
1377 	fcol(tinrc.col_head);
1378 #	endif /* HAVE_COLOR */
1379 
1380 	/*
1381 	 * first line
1382 	 */
1383 	cur_pos = 0;
1384 
1385 	/* date */
1386 	if ((wtmp = char2wchar_t(buf)) != NULL) {
1387 		my_fputws(wtmp, stdout);
1388 		cur_pos += wcswidth(wtmp, wcslen(wtmp));
1389 		free(wtmp);
1390 	}
1391 
1392 	/*
1393 	 * determine max len for centered group name
1394 	 * allow one space before and after group name
1395 	 */
1396 	len = cCOLS - 2 * MAX(cur_pos, right_len) - 3;
1397 
1398 	/* group name */
1399 	if ((wtmp = char2wchar_t(group)) != NULL) {
1400 		/* wconvert_to_printable(wtmp, FALSE); */
1401 		if (tinrc.abbreviate_groupname)
1402 			wtmp2 = abbr_wcsgroupname(wtmp, len);
1403 		else
1404 			wtmp2 = wstrunc(wtmp, len);
1405 
1406 		if ((i = wcswidth(wtmp2, wcslen(wtmp2))) < len)
1407 			len = i;
1408 
1409 		center_pos = (cCOLS - len) / 2;
1410 
1411 		/* pad out to left */
1412 		for (; cur_pos < center_pos; cur_pos++)
1413 			my_fputc(' ', stdout);
1414 
1415 		my_fputws(wtmp2, stdout);
1416 		cur_pos += wcswidth(wtmp2, wcslen(wtmp2));
1417 		free(wtmp2);
1418 		free(wtmp);
1419 	}
1420 
1421 	/* pad out to right */
1422 	for (; cur_pos < cCOLS - right_len - 1; cur_pos++)
1423 		my_fputc(' ', stdout);
1424 
1425 	/* thread info */
1426 	/* can't eval tin_ltoa() more than once in a statement due to statics */
1427 	strcpy(buf, tin_ltoa(which_thread(this_resp) + 1, 4));
1428 	tmp = strunc(_(txt_thread_x_of_n), cCOLS / 3 - 1);
1429 	my_printf(tmp, buf, tin_ltoa(grpmenu.max, 4));
1430 	free(tmp);
1431 
1432 	my_fputs(cCRLF, stdout);
1433 
1434 #	if 0
1435 	/* display a ruler for layout checking purposes */
1436 	my_fputs("....|....3....|....2....|....1....|....0....|....1....|....2....|....3....|....\n", stdout);
1437 #	endif /* 0 */
1438 
1439 	/*
1440 	 * second line
1441 	 */
1442 	cur_pos = 0;
1443 
1444 	/* line count */
1445 	if (arts[this_resp].line_count < 0)
1446 		strcpy(buf, "?");
1447 	else
1448 		snprintf(buf, line_len, "%-4d", arts[this_resp].line_count);
1449 
1450 	{
1451 		wchar_t *fmt;
1452 
1453 		if ((wtmp = char2wchar_t(_(txt_lines))) != NULL) {
1454 			int tex_space = pgart.tex2iso ? 5 : 0;
1455 
1456 			fmt = wstrunc(wtmp, cCOLS / 3 - 1 - tex_space);
1457 			wtmp = my_realloc(wtmp, sizeof(wchar_t) * line_len);
1458 			swprintf(wtmp, line_len, fmt, buf);
1459 			my_fputws(wtmp, stdout);
1460 			cur_pos += wcswidth(wtmp, wcslen(wtmp));
1461 			free(fmt);
1462 			free(wtmp);
1463 		}
1464 	}
1465 
1466 #	ifdef HAVE_COLOR
1467 	fcol(tinrc.col_subject);
1468 #	endif /* HAVE_COLOR */
1469 
1470 	/* tex2iso */
1471 	if (pgart.tex2iso) {
1472 		if ((wtmp = char2wchar_t(_(txt_tex))) != NULL) {
1473 			wtmp2 = wstrunc(wtmp, 5);
1474 			my_fputws(wtmp2, stdout);
1475 			cur_pos += wcswidth(wtmp2, wcslen(wtmp2));
1476 			free(wtmp);
1477 			free(wtmp2);
1478 		}
1479 	}
1480 
1481 	/* subject */
1482 	/*
1483 	 * TODO: why do we fall back to arts[this_resp].subject if !note_h->subj?
1484 	 *       if !note_h->subj then the article just has no subject, no matter
1485 	 *       what the overview says.
1486 	 *
1487 	 *       add BiDi handling
1488 	 */
1489 	strncpy(buf, (note_h->subj ? note_h->subj : arts[this_resp].subject), line_len);
1490 	buf[line_len - 1] = '\0';
1491 	if ((wtmp = char2wchar_t(buf)) != NULL) {
1492 		wbuf = wexpand_tab(wtmp, tabwidth);
1493 		wtmp2 = wstrunc(wbuf, cCOLS - 2 * right_len - 3);
1494 		center_pos = (cCOLS - wcswidth(wtmp2, wcslen(wtmp2))) / 2;
1495 
1496 		/* pad out to left */
1497 		for (; cur_pos < center_pos; cur_pos++)
1498 			my_fputc(' ', stdout);
1499 
1500 		StartInverse();
1501 		my_fputws(wtmp2, stdout);
1502 		EndInverse();
1503 		cur_pos += wcswidth(wtmp2, wcslen(wtmp2));
1504 		free(wtmp2);
1505 		free(wtmp);
1506 		free(wbuf);
1507 	}
1508 
1509 #	ifdef HAVE_COLOR
1510 	fcol(tinrc.col_response);
1511 #	endif /* HAVE_COLOR */
1512 
1513 	/* pad out to right */
1514 	for (; cur_pos < cCOLS - right_len - 1; cur_pos++)
1515 		my_fputc(' ', stdout);
1516 
1517 	if (whichresp) {
1518 		tmp = strunc(_(txt_art_x_of_n), cCOLS / 3 - 1);
1519 		my_printf(tmp, whichresp + 1, x_resp + 1);
1520 		free(tmp);
1521 	} else {
1522 		/* TODO: ngettext */
1523 		if (!x_resp) {
1524 			tmp = strunc(_(txt_no_responses), cCOLS / 3 - 1);
1525 			my_printf("%s", tmp);
1526 		} else if (x_resp == 1) {
1527 			tmp = strunc(_(txt_1_resp), cCOLS / 3 - 1);
1528 			my_printf("%s", tmp);
1529 		} else {
1530 			tmp = strunc(_(txt_x_resp), cCOLS / 3 - 1);
1531 			my_printf(tmp, x_resp);
1532 		}
1533 		free(tmp);
1534 	}
1535 	my_fputs(cCRLF, stdout);
1536 
1537 	/*
1538 	 * third line
1539 	 */
1540 	cur_pos = 0;
1541 
1542 #	ifdef HAVE_COLOR
1543 	fcol(tinrc.col_from);
1544 #	endif /* HAVE_COLOR */
1545 	/* from */
1546 	/*
1547 	 * TODO: don't use arts[this_resp].name/arts[this_resp].from
1548 	 *       split up note_h->from and use that instead as it might
1549 	 *       be different _if_ the overviews are broken
1550 	 *
1551 	 *       add BiDi handling
1552 	 */
1553 	{
1554 		char *p = idna_decode(arts[this_resp].from);
1555 
1556 		if (arts[this_resp].name)
1557 			snprintf(buf, line_len, "%s <%s>", arts[this_resp].name, p);
1558 		else {
1559 			strncpy(buf, p, line_len);
1560 			buf[line_len - 1] = '\0';
1561 		}
1562 		free(p);
1563 	}
1564 
1565 	if ((wtmp = char2wchar_t(buf)) != NULL) {
1566 		wtmp2 = wstrunc(wtmp, cCOLS - 1);
1567 		my_fputws(wtmp2, stdout);
1568 		cur_pos += wcswidth(wtmp2, wcslen(wtmp2));
1569 		free(wtmp2);
1570 		free(wtmp);
1571 	}
1572 
1573 	/*
1574 	 * Organization
1575 	 *
1576 	 * TODO: IDNA decoding, see also comment in
1577 	 *       cook.c:cook_article()
1578 	 *       add BiDi handling
1579 	 */
1580 	if (note_h->org) {
1581 		snprintf(buf, line_len, _(txt_at_s), note_h->org);
1582 
1583 		if ((wtmp = char2wchar_t(buf)) != NULL) {
1584 			wbuf = wexpand_tab(wtmp, tabwidth);
1585 			wtmp2 = wstrunc(wbuf, cCOLS - cur_pos - 1);
1586 
1587 			i = cCOLS - wcswidth(wtmp2, wcslen(wtmp2)) - 1;
1588 			for (; cur_pos < i; cur_pos++)
1589 				my_fputc(' ', stdout);
1590 
1591 			my_fputws(wtmp2, stdout);
1592 			free(wtmp2);
1593 			free(wtmp);
1594 			free(wbuf);
1595 		}
1596 	}
1597 
1598 	my_fputs(cCRLF, stdout);
1599 	my_fputs(cCRLF, stdout);
1600 
1601 #else /* !MULTIBYTE_ABLE || NO_LOCALE */
1602 	/*
1603 	 * determine the needed space for the text at the right hand margin
1604 	 * the formatting info (%4s) needs 3 positions but we need 4 positions
1605 	 * on the screen for each counter
1606 	 */
1607 	right_len = MAX((strlen(_(txt_thread_x_of_n)) - 6 + 8), (strlen(_(txt_art_x_of_n)) - 6 + 8));
1608 
1609 #	ifdef HAVE_COLOR
1610 	fcol(tinrc.col_head);
1611 #	endif /* HAVE_COLOR */
1612 
1613 	/*
1614 	 * first line
1615 	 */
1616 	cur_pos = 0;
1617 
1618 	/* date */
1619 	my_fputs(buf, stdout);
1620 	cur_pos += strlen(buf);
1621 
1622 	/*
1623 	 * determine max len for centered group name
1624 	 * allow one space before and after group name
1625 	 */
1626 	len = cCOLS - 2 * MAX(cur_pos, right_len) - 3;
1627 
1628 	/* group name */
1629 	if (tinrc.abbreviate_groupname)
1630 		tmp = abbr_groupname(group, len);
1631 	else
1632 		tmp = strunc(group, len);
1633 
1634 	if ((i = strlen(tmp)) < len)
1635 		len = i;
1636 
1637 	center_pos = (cCOLS - len) / 2;
1638 
1639 	/* pad out to left */
1640 	for (; cur_pos < center_pos; cur_pos++)
1641 		my_fputc(' ', stdout);
1642 
1643 	my_fputs(tmp, stdout);
1644 	cur_pos += strlen(tmp);
1645 	free(tmp);
1646 
1647 	/* pad out to right */
1648 	for (; cur_pos < cCOLS - right_len - 1; cur_pos++)
1649 		my_fputc(' ', stdout);
1650 
1651 	/* thread info */
1652 	/* can't eval tin_ltoa() more than once in a statement due to statics */
1653 	strcpy(buf, tin_ltoa(which_thread(this_resp) + 1, 4));
1654 	my_printf(_(txt_thread_x_of_n), buf, tin_ltoa(grpmenu.max, 4));
1655 
1656 	my_fputs(cCRLF, stdout);
1657 
1658 #	if 0
1659 	/* display a ruler for layout checking purposes */
1660 	my_fputs("....|....3....|....2....|....1....|....0....|....1....|....2....|....3....|....\n", stdout);
1661 #	endif /* 0 */
1662 
1663 	/*
1664 	 * second line
1665 	 */
1666 	cur_pos = 0;
1667 
1668 	/* line count */
1669 	/* an accurate line count will appear in the footer anymay */
1670 	if (arts[this_resp].line_count < 0)
1671 		strcpy(buf, "?");
1672 	else
1673 		snprintf(buf, line_len, "%-4d", arts[this_resp].line_count);
1674 
1675 	tmp = my_malloc(line_len);
1676 	snprintf(tmp, line_len, _(txt_lines), buf);
1677 	my_fputs(tmp, stdout);
1678 	cur_pos += strlen(tmp);
1679 	free(tmp);
1680 
1681 #	ifdef HAVE_COLOR
1682 	fcol(tinrc.col_subject);
1683 #	endif /* HAVE_COLOR */
1684 
1685 	/* tex2iso */
1686 	if (pgart.tex2iso) {
1687 		my_fputs(_(txt_tex), stdout);
1688 		cur_pos += strlen(_(txt_tex));
1689 	}
1690 
1691 	/* subject */
1692 	/*
1693 	 * TODO: why do we fall back to arts[this_resp].subject if !note_h->subj?
1694 	 *       if !note_h->subj then the article just has no subject, no matter
1695 	 *       what the overview says.
1696 	 */
1697 	strncpy(buf, (note_h->subj ? note_h->subj : arts[this_resp].subject), line_len);
1698 	buf[line_len - 1] = '\0';
1699 
1700 	tmp2 = expand_tab(buf, tabwidth);
1701 	tmp = strunc(tmp2, cCOLS - 2 * right_len - 3);
1702 
1703 	center_pos = (cCOLS - strlen(tmp)) / 2;
1704 
1705 	/* pad out to left */
1706 	for (; cur_pos < center_pos; cur_pos++)
1707 		my_fputc(' ', stdout);
1708 
1709 	StartInverse();
1710 	my_fputs(tmp, stdout);
1711 	EndInverse();
1712 	cur_pos += strlen(tmp);
1713 	free(tmp);
1714 	free(tmp2);
1715 
1716 #	ifdef HAVE_COLOR
1717 	fcol(tinrc.col_response);
1718 #	endif /* HAVE_COLOR */
1719 
1720 	/* pad out to right */
1721 	for (; cur_pos < cCOLS - right_len - 1; cur_pos++)
1722 		my_fputc(' ', stdout);
1723 
1724 	if (whichresp)
1725 		my_printf(_(txt_art_x_of_n), whichresp + 1, x_resp + 1);
1726 	else {
1727 		/* TODO: ngettext */
1728 		if (!x_resp)
1729 			my_printf("%s", _(txt_no_responses));
1730 		else if (x_resp == 1)
1731 			my_printf("%s", _(txt_1_resp));
1732 		else
1733 			my_printf(_(txt_x_resp), x_resp);
1734 	}
1735 	my_fputs(cCRLF, stdout);
1736 
1737 	/*
1738 	 * third line
1739 	 */
1740 	cur_pos = 0;
1741 
1742 #	ifdef HAVE_COLOR
1743 	fcol(tinrc.col_from);
1744 #	endif /* HAVE_COLOR */
1745 	/* from */
1746 	/*
1747 	 * TODO: don't use arts[this_resp].name/arts[this_resp].from
1748 	 *       split up note_h->from and use that instead as it might
1749 	 *       be different _if_ the overviews are broken
1750 	 */
1751 	if (arts[this_resp].name)
1752 		snprintf(buf, line_len, "%s <%s>", arts[this_resp].name, arts[this_resp].from);
1753 	else {
1754 		strncpy(buf, arts[this_resp].from, line_len);
1755 		buf[line_len - 1] = '\0';
1756 	}
1757 
1758 	tmp = strunc(buf, cCOLS - 1);
1759 	my_fputs(tmp, stdout);
1760 	cur_pos += strlen(tmp);
1761 	free(tmp);
1762 
1763 	if (note_h->org && cCOLS - cur_pos - 1 >= (int) strlen(_(txt_at_s)) - 2 + 3) {
1764 		/* we have enough space to print at least " at ..." */
1765 		snprintf(buf, line_len, _(txt_at_s), note_h->org);
1766 
1767 		tmp2 = expand_tab(buf, tabwidth);
1768 		tmp = strunc(tmp2, cCOLS - cur_pos - 1);
1769 		len = cCOLS - (int) strlen(tmp) - 1;
1770 		for (; cur_pos < len; cur_pos++)
1771 			my_fputc(' ', stdout);
1772 		my_fputs(tmp, stdout);
1773 		free(tmp);
1774 		free(tmp2);
1775 	}
1776 
1777 	my_fputs(cCRLF, stdout);
1778 	my_fputs(cCRLF, stdout);
1779 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
1780 	free(buf);
1781 
1782 #ifdef HAVE_COLOR
1783 	fcol(tinrc.col_normal);
1784 #endif /* HAVE_COLOR */
1785 }
1786 
1787 
1788 /*
1789  * Change the pager article context to arts[new_respnum]
1790  * Return GRP_ARTUNAVAIL if article could not be opened
1791  * or GRP_ARTABORT if load of article was interrupted
1792  * or 0 on success
1793  */
1794 static int
load_article(int new_respnum,struct t_group * group)1795 load_article(
1796 	int new_respnum,
1797 	struct t_group *group)
1798 {
1799 	static t_bool art_closed = FALSE;
1800 
1801 #ifdef DEBUG
1802 	if (debug & DEBUG_MISC)
1803 		fprintf(stderr, "load_art %s(new=%d, curr=%d)\n", (new_respnum == this_resp && !art_closed) ? "ALREADY OPEN!" : "", new_respnum, this_resp);
1804 #endif /* DEBUG */
1805 
1806 	if (new_respnum != this_resp || art_closed) {
1807 		int ret;
1808 
1809 		art_close(&pgart);			/* close previously opened art in pager */
1810 		ret = art_open(TRUE, &arts[new_respnum], group, &pgart, TRUE, _(txt_reading_article));
1811 
1812 		switch (ret) {
1813 			case ART_UNAVAILABLE:
1814 				art_mark(group, &arts[new_respnum], ART_READ);
1815 				/* prevent retagging as unread in unfilter_articles() */
1816 				if (arts[new_respnum].killed == ART_KILLED_UNREAD)
1817 					arts[new_respnum].killed = ART_KILLED;
1818 				art_closed = TRUE;
1819 				wait_message(1, _(txt_art_unavailable));
1820 				return GRP_ARTUNAVAIL;
1821 
1822 			case ART_ABORT:
1823 				art_close(&pgart);
1824 				art_closed = TRUE;
1825 				return GRP_ARTABORT;	/* special retcode to stop redrawing screen */
1826 
1827 			default:					/* Normal case */
1828 #if 0			/* Very useful debugging tool */
1829 				if (prompt_yn("Fake art unavailable? ", FALSE) == 1) {
1830 					art_close(&pgart);
1831 					art_mark(group, &arts[new_respnum], ART_READ);
1832 					art_closed = TRUE;
1833 					return GRP_ARTUNAVAIL;
1834 				}
1835 #endif /* 0 */
1836 				if (art_closed)
1837 					art_closed = FALSE;
1838 				if (new_respnum != this_resp) {
1839 					/*
1840 					 * Remember current & previous articles for '-' command
1841 					 */
1842 					last_resp = this_resp;
1843 					this_resp = new_respnum;		/* Set new art globally */
1844 				}
1845 				break;
1846 		}
1847 	} else if (show_all_headers) {
1848 		/*
1849 		 * article is already opened with show_all_headers ON
1850 		 * -> re-cook it
1851 		 */
1852 		show_all_headers = FALSE;
1853 		resize_article(TRUE, &pgart);
1854 	}
1855 
1856 	art_mark(group, &arts[this_resp], ART_READ);
1857 
1858 	/*
1859 	 * Change status if art was unread before killing to
1860 	 * prevent retagging as unread in unfilter_articles()
1861 	 */
1862 	if (arts[this_resp].killed == ART_KILLED_UNREAD)
1863 		arts[this_resp].killed = ART_KILLED;
1864 
1865 	if (pgart.cooked == NULL) { /* harmony corruption */
1866 		wait_message(1, _(txt_art_unavailable));
1867 		return GRP_ARTUNAVAIL;
1868 	}
1869 
1870 	/*
1871 	 * Setup to start viewing cooked version
1872 	 */
1873 	show_raw_article = FALSE;
1874 	show_all_headers = FALSE;
1875 	curr_line = 0;
1876 	note_fp = pgart.cooked;
1877 	artline = pgart.cookl;
1878 	artlines = pgart.cooked_lines;
1879 	search_line = 0;
1880 	/*
1881 	 * Reset offsets only if not invoked during 'body search' (srch_lineno != -1)
1882 	 * otherwise the found string will not be highlighted
1883 	 */
1884 	if (srch_lineno == -1)
1885 		reset_srch_offsets();
1886 	rotate = 0;			/* normal mode, not rot13 */
1887 	reveal_ctrl_l = FALSE;
1888 	reveal_ctrl_l_lines = -1;	/* all ^L's active */
1889 	hide_uue = tinrc.hide_uue;
1890 
1891 	draw_page(group->name, 0);
1892 
1893 	/*
1894 	 * Automatically invoke attachment viewing if requested
1895 	 */
1896 	if (!note_h->mime || IS_PLAINTEXT(note_h->ext))		/* Text only article */
1897 		return 0;
1898 
1899 	if (*tinrc.metamail_prog == '\0' || getenv("NOMETAMAIL") != NULL)	/* Viewer turned off */
1900 		return 0;
1901 
1902 	if (group->attribute->ask_for_metamail) {
1903 		if (prompt_yn(_(txt_use_mime), TRUE) != 1)
1904 			return 0;
1905 	}
1906 
1907 	XFACE_SUPPRESS();
1908 	if (strcmp(tinrc.metamail_prog, INTERNAL_CMD) == 0)	/* Use internal viewer */
1909 		decode_save_mime(&pgart, FALSE);
1910 	else
1911 		invoke_metamail(pgart.raw);
1912 	XFACE_SHOW();
1913 	return 0;
1914 }
1915 
1916 
1917 static int
prompt_response(int ch,int curr_respnum)1918 prompt_response(
1919 	int ch,
1920 	int curr_respnum)
1921 {
1922 	int i, num;
1923 
1924 	clear_message();
1925 
1926 	if ((num = (prompt_num(ch, _(txt_select_art)) - 1)) == -1) {
1927 		clear_message();
1928 		return -1;
1929 	}
1930 
1931 	if ((i = which_thread(curr_respnum)) >= 0)
1932 		return find_response(i, num);
1933 	else
1934 		return -1;
1935 }
1936 
1937 
1938 /*
1939  * Reposition within message as needed, highlight searched string
1940  * This is tied quite closely to the information stored by
1941  * get_search_vectors()
1942  */
1943 static void
process_search(int * lcurr_line,size_t message_lines,size_t screen_lines,int help_level)1944 process_search(
1945 	int *lcurr_line,
1946 	size_t message_lines,
1947 	size_t screen_lines,
1948 	int help_level)
1949 {
1950 	int i, start, end;
1951 
1952 	if ((i = get_search_vectors(&start, &end)) == -1)
1953 		return;
1954 
1955 	/*
1956 	 * Is matching line off the current view?
1957 	 * Reposition within article if needed, try to get matched line
1958 	 * in the middle of the screen
1959 	 */
1960 	if (i < *lcurr_line || i >= (int) (*lcurr_line + screen_lines)) {
1961 		*lcurr_line = i - (screen_lines / 2);
1962 		if (*lcurr_line + screen_lines > message_lines)	/* off the end */
1963 			*lcurr_line = message_lines - screen_lines;
1964 		/* else pos. is just fine */
1965 	}
1966 
1967 	switch (help_level) {
1968 		case PAGE_LEVEL:
1969 			draw_page(curr_group->name, 0);
1970 			break;
1971 
1972 		case INFO_PAGER:
1973 			display_info_page(0);
1974 			break;
1975 
1976 		default:
1977 			break;
1978 	}
1979 	search_line = i;								/* draw_page() resets this to 0 */
1980 
1981 	/*
1982 	 * Highlight found string
1983 	 */
1984 	highlight_string(i - *lcurr_line + scroll_region_top, start, end - start);
1985 }
1986 
1987 
1988 /*
1989  * Implement ^H toggle between cooked and raw views of article
1990  */
1991 void
toggle_raw(struct t_group * group)1992 toggle_raw(
1993 	struct t_group *group)
1994 {
1995 	if (show_raw_article) {
1996 		artline = pgart.cookl;
1997 		artlines = pgart.cooked_lines;
1998 		note_fp = pgart.cooked;
1999 	} else {
2000 		static int j;				/* Needed on successive invocations */
2001 		int chunk = note_h->ext->line_count;
2002 
2003 		/*
2004 		 * We do this on the fly, since most of the time it won't be used
2005 		 */
2006 		if (!pgart.rawl) {			/* Already done this for this article? */
2007 			char *line;
2008 			char *p;
2009 			long offset;
2010 
2011 			j = 0;
2012 			rewind(pgart.raw);
2013 			pgart.rawl = my_malloc(sizeof(t_lineinfo) * chunk);
2014 			offset = ftell(pgart.raw);
2015 
2016 			while ((line = tin_fgets(pgart.raw, FALSE)) != NULL) {
2017 				int space;
2018 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
2019 				int num_bytes;
2020 				wchar_t wc;
2021 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
2022 
2023 				pgart.rawl[j].offset = offset;
2024 				pgart.rawl[j].flags = 0;
2025 				j++;
2026 				if (j >= chunk) {
2027 					chunk += 50;
2028 					pgart.rawl = my_realloc(pgart.rawl, sizeof(t_lineinfo) * chunk);
2029 				}
2030 
2031 				p = line;
2032 				while (*p) {
2033 					space = cCOLS - 1;
2034 
2035 					while ((space > 0) && *p) {
2036 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
2037 						num_bytes = mbtowc(&wc, p, MB_CUR_MAX);
2038 						if (num_bytes != -1 && iswprint(wc)) {
2039 							if ((space -= wcwidth(wc)) < 0)
2040 								break;
2041 							p += num_bytes;
2042 							offset += num_bytes;
2043 						}
2044 #else
2045 						if (my_isprint((unsigned char) *p)) {
2046 							space--;
2047 							p++;
2048 							offset++;
2049 						}
2050 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
2051 						else if (IS_LOCAL_CHARSET("Big5") && (unsigned char) *p >= 0xa1 && (unsigned char) *p <= 0xfe && *(p + 1)) {
2052 							/*
2053 							 * Big5: ASCII chars are handled by the normal code
2054 							 * check only for 2-byte chars
2055 							 * TODO: should we also check if the second byte is
2056 							 * also valid?
2057 							 */
2058 							p += 2;
2059 							offset += 2;
2060 							space--;
2061 						} else {
2062 							/*
2063 							 * the current character can't be displayed print it as
2064 							 * an octal value (needs 4 columns) see also
2065 							 * color.c:draw_pager_line()
2066 							 */
2067 							if ((space -= 4) < 0)
2068 								break;
2069 							offset++;
2070 							p++;
2071 						}
2072 					}
2073 					/*
2074 					 * if we reached the end of the line we don't need to
2075 					 * remember anything
2076 					 */
2077 					if (*p) {
2078 						pgart.rawl[j].offset = offset;
2079 						pgart.rawl[j].flags = 0;
2080 						if (++j >= chunk) {
2081 							chunk += 50;
2082 							pgart.rawl = my_realloc(pgart.rawl, sizeof(t_lineinfo) * chunk);
2083 						}
2084 					}
2085 				}
2086 
2087 				/*
2088 				 * only use ftell's return value here because we didn't
2089 				 * take the \n into account above.
2090 				 */
2091 				offset = ftell(pgart.raw);
2092 			}
2093 
2094 			pgart.rawl = my_realloc(pgart.rawl, sizeof(t_lineinfo) * j);
2095 		}
2096 		artline = pgart.rawl;
2097 		artlines = j;
2098 		note_fp = pgart.raw;
2099 	}
2100 	curr_line = 0;
2101 	show_raw_article = bool_not(show_raw_article);
2102 	draw_page(group ? group->name : "", 0);
2103 }
2104 
2105 
2106 /*
2107  * Re-cook an article
2108  */
2109 void
resize_article(t_bool wrap_lines,t_openartinfo * artinfo)2110 resize_article(
2111 	t_bool wrap_lines,
2112 	t_openartinfo *artinfo)
2113 {
2114 	free(artinfo->cookl);
2115 	if (artinfo->cooked)
2116 		fclose(artinfo->cooked);
2117 
2118 	if (!cook_article(wrap_lines, artinfo, hide_uue, show_all_headers))
2119 		tin_done(EXIT_FAILURE, _(txt_cook_article_failed_exiting), tin_progname);
2120 
2121 	show_raw_article = FALSE;
2122 	artline = pgart.cookl;
2123 	artlines = pgart.cooked_lines;
2124 	note_fp = pgart.cooked;
2125 }
2126 
2127 
2128 /*
2129  * Infopager: simply page files
2130  */
2131 void
info_pager(FILE * info_fh,const char * title,t_bool wrap_at_ends)2132 info_pager(
2133 	FILE *info_fh,
2134 	const char *title,
2135 	t_bool wrap_at_ends)
2136 {
2137 	int offset;
2138 	t_function func;
2139 
2140 	search_line = 0;
2141 	reset_srch_offsets();
2142 	info_file = info_fh;
2143 	info_title = title;
2144 	curr_info_line = 0;
2145 	preprocess_info_message(info_fh);
2146 	if (!info_fh)
2147 		return;
2148 	set_xclick_off();
2149 	display_info_page(0);
2150 
2151 	forever {
2152 		switch (func = handle_keypad(page_left, page_right, page_mouse_action, info_keys)) {
2153 			case GLOBAL_ABORT:	/* common arrow keys */
2154 				break;
2155 
2156 			case GLOBAL_LINE_UP:
2157 				if (num_info_lines <= NOTESLINES) {
2158 					info_message(_(txt_begin_of_page));
2159 					break;
2160 				}
2161 				if (curr_info_line == 0) {
2162 					if (!wrap_at_ends) {
2163 						info_message(_(txt_begin_of_page));
2164 						break;
2165 					}
2166 					curr_info_line = num_info_lines - NOTESLINES;
2167 					display_info_page(0);
2168 					break;
2169 				}
2170 				offset = scroll_page(KEYMAP_UP);
2171 				curr_info_line += offset;
2172 				display_info_page(offset);
2173 				break;
2174 
2175 			case GLOBAL_LINE_DOWN:
2176 				if (num_info_lines <= NOTESLINES) {
2177 					info_message(_(txt_end_of_page));
2178 					break;
2179 				}
2180 				if (curr_info_line + NOTESLINES >= num_info_lines) {
2181 					if (!wrap_at_ends) {
2182 						info_message(_(txt_end_of_page));
2183 						break;
2184 					}
2185 					curr_info_line = 0;
2186 					display_info_page(0);
2187 					break;
2188 				}
2189 				offset = scroll_page(KEYMAP_DOWN);
2190 				curr_info_line += offset;
2191 				display_info_page(offset);
2192 				break;
2193 
2194 			case GLOBAL_PAGE_DOWN:
2195 				if (num_info_lines <= NOTESLINES) {
2196 					info_message(_(txt_end_of_page));
2197 					break;
2198 				}
2199 				if (curr_info_line + NOTESLINES >= num_info_lines) {	/* End is already on screen */
2200 					if (!wrap_at_ends) {
2201 						info_message(_(txt_end_of_page));
2202 						break;
2203 					}
2204 					curr_info_line = 0;
2205 					display_info_page(0);
2206 					break;
2207 				}
2208 				curr_info_line += ((tinrc.scroll_lines == -2) ? NOTESLINES / 2 : NOTESLINES);
2209 				display_info_page(0);
2210 				break;
2211 
2212 			case GLOBAL_PAGE_UP:
2213 				if (num_info_lines <= NOTESLINES) {
2214 					info_message(_(txt_begin_of_page));
2215 					break;
2216 				}
2217 				if (curr_info_line == 0) {
2218 					if (!wrap_at_ends) {
2219 						info_message(_(txt_begin_of_page));
2220 						break;
2221 					}
2222 					curr_info_line = num_info_lines - NOTESLINES;
2223 					display_info_page(0);
2224 					break;
2225 				}
2226 				curr_info_line -= ((tinrc.scroll_lines == -2) ? NOTESLINES / 2 : NOTESLINES);
2227 				display_info_page(0);
2228 				break;
2229 
2230 			case GLOBAL_FIRST_PAGE:
2231 				if (curr_info_line) {
2232 					curr_info_line = 0;
2233 					display_info_page(0);
2234 				}
2235 				break;
2236 
2237 			case GLOBAL_LAST_PAGE:
2238 				if (curr_info_line + NOTESLINES != num_info_lines) {
2239 					/* Display a full last page for neatness */
2240 					curr_info_line = num_info_lines - NOTESLINES;
2241 					display_info_page(0);
2242 				}
2243 				break;
2244 
2245 			case GLOBAL_TOGGLE_HELP_DISPLAY:
2246 				toggle_mini_help(INFO_PAGER);
2247 				display_info_page(0);
2248 				break;
2249 
2250 			case GLOBAL_SEARCH_SUBJECT_FORWARD:
2251 			case GLOBAL_SEARCH_SUBJECT_BACKWARD:
2252 			case GLOBAL_SEARCH_REPEAT:
2253 				if (func == GLOBAL_SEARCH_REPEAT && last_search != GLOBAL_SEARCH_SUBJECT_FORWARD && last_search != GLOBAL_SEARCH_SUBJECT_BACKWARD)
2254 					break;
2255 
2256 				if ((search_article((func == GLOBAL_SEARCH_SUBJECT_FORWARD), (func == GLOBAL_SEARCH_REPEAT), search_line, num_info_lines, infoline, num_info_lines - 1, info_file)) == -1)
2257 					break;
2258 
2259 				process_search(&curr_info_line, num_info_lines, NOTESLINES, INFO_PAGER);
2260 				break;
2261 
2262 			case GLOBAL_QUIT:	/* quit */
2263 				ClearScreen();
2264 				return;
2265 
2266 			default:
2267 				break;
2268 		}
2269 	}
2270 }
2271 
2272 
2273 /*
2274  * Redraw the current page, curr_info_line will be the first line displayed
2275  * If part is !=0, then only draw the first (-ve) or last (+ve) few lines
2276  */
2277 void
display_info_page(int part)2278 display_info_page(
2279 	int part)
2280 {
2281 	int start, end;	/* 1st, last line to draw */
2282 
2283 	signal_context = cInfopager;
2284 
2285 	/*
2286 	 * Can't do partial draw if term can't scroll properly
2287 	 */
2288 	if (part && !have_linescroll)
2289 		part = 0;
2290 
2291 	if (curr_info_line < 0)
2292 		curr_info_line = 0;
2293 	if (curr_info_line >= num_info_lines)
2294 		curr_info_line = num_info_lines - 1;
2295 
2296 	scroll_region_top = INDEX_TOP;
2297 
2298 	/* Down-scroll, only redraw bottom 'part' lines of screen */
2299 	if ((start = (part > 0) ? NOTESLINES - part : 0) < 0)
2300 		start = 0;
2301 
2302 	/* Up-scroll, only redraw the top 'part' lines of screen */
2303 	if ((end = (part < 0) ? -part : NOTESLINES) > NOTESLINES)
2304 		end = NOTESLINES;
2305 
2306 	/* Print title */
2307 	if ((end - start >= NOTESLINES) || (part == 0)) {
2308 		ClearScreen();
2309 		center_line(0, TRUE, info_title);
2310 	}
2311 
2312 	print_message_page(info_file, infoline, num_info_lines, curr_info_line, start, end, INFO_PAGER);
2313 
2314 	/* print footer */
2315 	draw_percent_mark(curr_info_line + (curr_info_line + NOTESLINES < num_info_lines ? NOTESLINES : num_info_lines - curr_info_line), num_info_lines);
2316 	stow_cursor();
2317 }
2318 
2319 
2320 static void
preprocess_info_message(FILE * info_fh)2321 preprocess_info_message(
2322 	FILE *info_fh)
2323 {
2324 	int chunk = 50;
2325 
2326 	FreeAndNull(infoline);
2327 	if (!info_fh)
2328 		return;
2329 
2330 	rewind(info_fh);
2331 	infoline = my_malloc(sizeof(t_lineinfo) * chunk);
2332 	num_info_lines = 0;
2333 
2334 	do {
2335 		infoline[num_info_lines].offset = ftell(info_fh);
2336 		infoline[num_info_lines].flags = 0;
2337 		num_info_lines++;
2338 		if (num_info_lines >= chunk) {
2339 			chunk += 50;
2340 			infoline = my_realloc(infoline, sizeof(t_lineinfo) * chunk);
2341 		}
2342 	} while (tin_fgets(info_fh, FALSE) != NULL);
2343 
2344 	num_info_lines--;
2345 	infoline = my_realloc(infoline, sizeof(t_lineinfo) * num_info_lines);
2346 }
2347 
2348 
2349 /*
2350  * URL menu
2351  */
2352 static t_function
url_left(void)2353 url_left(
2354 	void)
2355 {
2356 	return GLOBAL_QUIT;
2357 }
2358 
2359 
2360 static t_function
url_right(void)2361 url_right(
2362 	void)
2363 {
2364 	return URL_SELECT;
2365 }
2366 
2367 
2368 static void
show_url_page(void)2369 show_url_page(
2370 	void)
2371 {
2372 	int i;
2373 
2374 	signal_context = cURL;
2375 	currmenu = &urlmenu;
2376 	mark_offset = 0;
2377 
2378 	if (urlmenu.curr < 0)
2379 		urlmenu.curr = 0;
2380 
2381 	ClearScreen();
2382 	set_first_screen_item();
2383 	center_line(0, TRUE, _(txt_url_menu));
2384 
2385 	for (i = urlmenu.first; i < urlmenu.first + NOTESLINES && i < urlmenu.max; ++i)
2386 		build_url_line(i);
2387 
2388 	show_mini_help(URL_LEVEL);
2389 
2390 	draw_url_arrow();
2391 }
2392 
2393 
2394 static t_bool
url_page(void)2395 url_page(
2396 	void)
2397 {
2398 	char key[MAXKEYLEN];
2399 	t_function func;
2400 	t_menu *oldmenu = NULL;
2401 
2402 	if (currmenu)
2403 		oldmenu = currmenu;
2404 	urlmenu.curr = 0;
2405 	urlmenu.max = build_url_list();
2406 	if (urlmenu.max == 0)
2407 		return FALSE;
2408 
2409 	clear_note_area();
2410 	show_url_page();
2411 	set_xclick_off();
2412 
2413 	forever {
2414 		switch ((func = handle_keypad(url_left, url_right, NULL, url_keys))) {
2415 			case GLOBAL_QUIT:
2416 				free_url_list();
2417 				if (oldmenu)
2418 					currmenu = oldmenu;
2419 				return TRUE;
2420 
2421 			case DIGIT_1:
2422 			case DIGIT_2:
2423 			case DIGIT_3:
2424 			case DIGIT_4:
2425 			case DIGIT_5:
2426 			case DIGIT_6:
2427 			case DIGIT_7:
2428 			case DIGIT_8:
2429 			case DIGIT_9:
2430 				if (urlmenu.max)
2431 					prompt_item_num(func_to_key(func, url_keys), _(txt_url_select));
2432 				break;
2433 
2434 #ifndef NO_SHELL_ESCAPE
2435 			case GLOBAL_SHELL_ESCAPE:
2436 				do_shell_escape();
2437 				break;
2438 #endif /* !NO_SHELL_ESCAPE */
2439 
2440 			case GLOBAL_HELP:
2441 				show_help_page(URL_LEVEL, _(txt_url_menu_com));
2442 				show_url_page();
2443 				break;
2444 
2445 			case GLOBAL_FIRST_PAGE:
2446 				top_of_list();
2447 				break;
2448 
2449 			case GLOBAL_LAST_PAGE:
2450 				end_of_list();
2451 				break;
2452 
2453 			case GLOBAL_REDRAW_SCREEN:
2454 				my_retouch();
2455 				show_url_page();
2456 				break;
2457 
2458 			case GLOBAL_LINE_DOWN:
2459 				move_down();
2460 				break;
2461 
2462 			case GLOBAL_LINE_UP:
2463 				move_up();
2464 				break;
2465 
2466 			case GLOBAL_PAGE_DOWN:
2467 				page_down();
2468 				break;
2469 
2470 			case GLOBAL_PAGE_UP:
2471 				page_up();
2472 				break;
2473 
2474 			case GLOBAL_SCROLL_DOWN:
2475 				scroll_down();
2476 				break;
2477 
2478 			case GLOBAL_SCROLL_UP:
2479 				scroll_up();
2480 				break;
2481 
2482 			case GLOBAL_TOGGLE_HELP_DISPLAY:
2483 				toggle_mini_help(URL_LEVEL);
2484 				show_url_page();
2485 				break;
2486 
2487 			case GLOBAL_TOGGLE_INFO_LAST_LINE:
2488 				tinrc.info_in_last_line = bool_not(tinrc.info_in_last_line);
2489 				show_url_page();
2490 				break;
2491 
2492 			case URL_SELECT:
2493 				if (urlmenu.max) {
2494 					if (process_url(urlmenu.curr))
2495 						show_url_page();
2496 					else
2497 						draw_url_arrow();
2498 				}
2499 				break;
2500 
2501 			case GLOBAL_SEARCH_SUBJECT_FORWARD:
2502 			case GLOBAL_SEARCH_SUBJECT_BACKWARD:
2503 			case GLOBAL_SEARCH_REPEAT:
2504 				if (func == GLOBAL_SEARCH_REPEAT && last_search != GLOBAL_SEARCH_SUBJECT_FORWARD && last_search != GLOBAL_SEARCH_SUBJECT_BACKWARD)
2505 					info_message(_(txt_no_prev_search));
2506 				else if (urlmenu.max) {
2507 					int new_pos, old_pos = urlmenu.curr;
2508 
2509 					new_pos = generic_search((func == GLOBAL_SEARCH_SUBJECT_FORWARD), (func == GLOBAL_SEARCH_REPEAT), urlmenu.curr, urlmenu.max - 1, URL_LEVEL);
2510 					if (new_pos != old_pos)
2511 						move_to_item(new_pos);
2512 				}
2513 				break;
2514 
2515 			default:
2516 				info_message(_(txt_bad_command), printascii(key, func_to_key(GLOBAL_HELP, url_keys)));
2517 				break;
2518 		}
2519 	}
2520 }
2521 
2522 
2523 static void
draw_url_arrow(void)2524 draw_url_arrow(
2525 	void)
2526 {
2527 	draw_arrow_mark(INDEX_TOP + urlmenu.curr - urlmenu.first);
2528 	if (tinrc.info_in_last_line) {
2529 		t_url *lptr;
2530 
2531 		lptr = find_url(urlmenu.curr);
2532 		info_message("%s", lptr->url);
2533 	} else if (urlmenu.curr == urlmenu.max - 1)
2534 		info_message(_(txt_end_of_urls));
2535 }
2536 
2537 
2538 t_url *
find_url(int n)2539 find_url(
2540 	int n)
2541 {
2542 	t_url *lptr;
2543 
2544 	lptr = url_list;
2545 	while (n-- > 0 && lptr->next)
2546 		lptr = lptr->next;
2547 
2548 	return lptr;
2549 }
2550 
2551 
2552 static void
build_url_line(int i)2553 build_url_line(
2554 	int i)
2555 {
2556 	char *sptr;
2557 	int len = cCOLS - 9;
2558 	t_url *lptr;
2559 
2560 #ifdef USE_CURSES
2561 	/*
2562 	 * Allocate line buffer
2563 	 * make it the same size like in !USE_CURSES case to simplify some code
2564 	 */
2565 	sptr = my_malloc(cCOLS + 2);
2566 #else
2567 	sptr = screen[INDEX2SNUM(i)].col;
2568 #endif /* USE_CURSES */
2569 
2570 	lptr = find_url(i);
2571 	snprintf(sptr, cCOLS, "  %s  %-*.*s%s", tin_ltoa(i + 1, 4), len, len, lptr->url, cCRLF);
2572 	WriteLine(INDEX2LNUM(i), sptr);
2573 
2574 #ifdef USE_CURSES
2575 	free(sptr);
2576 #endif /* USE_CURSES */
2577 }
2578 
2579 
2580 static t_bool
process_url(int n)2581 process_url(
2582 	int n)
2583 {
2584 	char *url, *url_esc;
2585 	size_t len;
2586 	t_url *lptr;
2587 
2588 	lptr = find_url(n);
2589 	len = strlen(lptr->url) << 1; /* double size; room for editing URL */
2590 	url = my_malloc(len + 1);
2591 	if (prompt_default_string("URL:", url, len, lptr->url, HIST_URL)) {
2592 		if (!*url) {			/* Don't try and open nothing */
2593 			free(url);
2594 			return FALSE;
2595 		}
2596 		wait_message(2, _(txt_url_open), url);
2597 		url_esc = escape_shell_meta(url, no_quote);
2598 		len = strlen(url_esc) + strlen(tinrc.url_handler) + 2;
2599 		url = my_realloc(url, len);
2600 		snprintf(url, len, "%s %s", tinrc.url_handler, url_esc);
2601 		invoke_cmd(url);
2602 		free(url);
2603 		cursoroff();
2604 		return TRUE;
2605 	}
2606 	free(url);
2607 	return FALSE;
2608 }
2609 
2610 
2611 static int
build_url_list(void)2612 build_url_list(
2613 	void)
2614 {
2615 	char *ptr;
2616 	int i, count = 0;
2617 	int offsets[6];
2618 	int offsets_size = ARRAY_SIZE(offsets);
2619 	t_url *lptr = NULL;
2620 
2621 	for (i = 0; i < artlines; ++i) {
2622 		if (!(artline[i].flags & (C_URL | C_NEWS | C_MAIL)))
2623 			continue;
2624 
2625 		/*
2626 		 * Line contains a URL, so read it in
2627 		 */
2628 		if (fseek(pgart.cooked, artline[i].offset, SEEK_SET) == -1) /* skip on error */
2629 			continue;
2630 		if ((ptr = tin_fgets(pgart.cooked, FALSE)) == NULL)
2631 			continue;
2632 
2633 		/*
2634 		 * Step through, finding URL's
2635 		 */
2636 		forever {
2637 			/* any matches left? */
2638 			if (pcre_exec(url_regex.re, url_regex.extra, ptr, strlen(ptr), 0, 0, offsets, offsets_size) == PCRE_ERROR_NOMATCH)
2639 				if (pcre_exec(mail_regex.re, mail_regex.extra, ptr, strlen(ptr), 0, 0, offsets, offsets_size) == PCRE_ERROR_NOMATCH)
2640 					if (pcre_exec(news_regex.re, news_regex.extra, ptr, strlen(ptr), 0, 0, offsets, offsets_size) == PCRE_ERROR_NOMATCH)
2641 						break;
2642 
2643 			*(ptr + offsets[1]) = '\0';
2644 
2645 			if (!lptr)
2646 				lptr = url_list = my_malloc(sizeof(t_url));
2647 			else {
2648 				lptr->next = my_malloc(sizeof(t_url));
2649 				lptr = lptr->next;
2650 			}
2651 			lptr->url = my_strdup(ptr + offsets[0]);
2652 			lptr->next = NULL;
2653 			++count;
2654 
2655 			ptr += offsets[1] + 1;
2656 		}
2657 	}
2658 	return count;
2659 }
2660 
2661 
2662 static void
free_url_list(void)2663 free_url_list(
2664 	void)
2665 {
2666 	t_url *p, *q;
2667 
2668 	for (p = url_list; p != NULL; p = q) {
2669 		q = p->next;
2670 		free(p->url);
2671 		free(p);
2672 	}
2673 	url_list = NULL;
2674 }
2675