1 /*
2  *	(c) Copyright 1990, Kim Fabricius Storm.  All rights reserved.
3  *      Copyright (c) 1996-2005 Michael T Pins.  All rights reserved.
4  *
5  *	Folder handling
6  */
7 
8 #include <stdlib.h>
9 #include <unistd.h>
10 #include <pwd.h>
11 #include <string.h>
12 #include <ctype.h>
13 #include "config.h"
14 #include "global.h"
15 #include "articles.h"
16 #include "db.h"
17 #include "digest.h"
18 #include "dir.h"
19 #include "match.h"
20 #include "menu.h"
21 #include "pack_date.h"
22 #include "pack_name.h"
23 #include "pack_subject.h"
24 #include "save.h"
25 #include "sort.h"
26 #include "nn_term.h"
27 
28 /* folder.c */
29 
30 static char    *tilde_expansion(char **srcp, int compl);
31 static int      folder_header(void);
32 static void     rewrite_folder(void);
33 
34 
35 extern char    *home_directory;
36 extern int      current_folder_type;
37 extern int      use_mail_folders;
38 extern int      use_mmdf_folders;
39 
40 extern int      bypass_consolidation;
41 
42 int             dont_sort_folders = 0;
43 char           *folder_directory = NULL;
44 int             folder_rewrite_trace = 1;
45 char           *backup_folder_path = "BackupFolder~";
46 int             keep_backup_folder = 1;
47 int             convert_folder_mode = 0;	/* ignore folder's format on
48 						 * rewrite */
49 int             consolidated_manual = 0;
50 int             ignore_fancy_select = 0;	/* turn off select features
51 						 * for folders */
52 
53 extern int      fmt_linenum;
54 extern char    *header_lines;
55 
56 extern struct passwd *getpwnam();
57 
58 /*
59  *	expand ~[user][/...] form
60  *	src ptr is advanced to last char of user name.
61  */
62 
63 static char    *
tilde_expansion(char ** srcp,int compl)64 tilde_expansion(char **srcp, int compl)
65 {
66     struct passwd  *pwd;
67     register char  *name = *srcp;
68     register char  *tail, x;
69 
70     tail = ++name;		/* skip ~ */
71     while (*tail && isascii(*tail) && !isspace(*tail) && *tail != '/')
72 	tail++;
73 
74     if (compl && *tail != '/')
75 	return NULL;
76     if (tail == name)
77 	return home_directory;
78 
79     *srcp = tail - 1;
80     x = *tail;
81     *tail = NUL;
82     pwd = getpwnam(name);
83     if (pwd == NULL && !compl)
84 	msg("User %s not found", name);
85     *tail = x;
86 
87     return (pwd == NULL) ? NULL : pwd->pw_dir;
88 }
89 
90 /*
91  * 	file name completion and expansion
92  *
93  *	expand_mode bits:
94  *		1:	expand path names
95  *		2:	don't expand $N
96  *		4:	don't expand any $?  (but $(...) is expanded)
97  *		8:	don't complain about ~... (shell will do that)
98  *		10:	doing filename completion
99  */
100 
101 
102 int
expand_file_name(char * dest,char * src,int expand_mode)103 expand_file_name(char *dest, char *src, int expand_mode)
104 {
105     register char  *cp, *dp, c;
106     int             parse, remap;
107     char           *cur_grp, *cur_art;
108 
109     cur_grp = current_group ? current_group->group_name : NULL;
110     cur_art = (group_file_name && *group_file_name) ? group_path_name : NULL;
111 
112     for (dp = dest, parse = 1; ((c = *src)); src++) {
113 
114 	if (parse) {
115 
116 	    if ((expand_mode & 1) && c == '+') {
117 		if (folder_directory == NULL) {
118 		    if (!(cp = getenv("FOLDER")))
119 			cp = FOLDER_DIRECTORY;
120 		    folder_directory = home_relative(cp);
121 		}
122 		cp = folder_directory;
123 		goto cp_str;
124 	    }
125 	    if ((expand_mode & 1) && c == '~') {
126 		if ((cp = tilde_expansion(&src, (expand_mode & 0x10))) == NULL) {
127 		    return 0;
128 		}
129 	cp_str:
130 		while (*cp)
131 		    *dp++ = *cp++;
132 		if (dp[-1] != '/')
133 		    *dp++ = '/';
134 		goto no_parse;
135 	    }
136 	    if ((expand_mode & 4) == 0 &&
137 		cur_art && c == '%' && (src[1] == ' ' || src[1] == NUL)) {
138 		cp = cur_art;
139 		while (*cp)
140 		    *dp++ = *cp++;
141 		goto no_parse;
142 	    }
143 	}
144 	if (c == '$' && src[1] == '(') {
145 	    char            envar[64];
146 	    for (src += 2, cp = envar; (c = *src) != NUL && c != ')'; src++)
147 		*cp++ = c;
148 	    *cp = NUL;
149 	    if (cp != envar) {
150 		if ((cp = getenv(envar)) != NULL)
151 		    while (*cp)
152 			*dp++ = *cp++;
153 		else {
154 		    msg("Environment variable $(%s) not set", envar);
155 		    return 0;
156 		}
157 	    }
158 	    goto no_parse;
159 	}
160 	if ((expand_mode & 4) == 0 && c == '$' && !isalnum(src[2])) {
161 	    remap = 0;
162 	    cp = NULL;
163 
164 	    switch (src[1]) {
165 		case 'A':
166 		    cp = cur_art;
167 		    break;
168 		case 'F':
169 		    cp = cur_grp;
170 		    remap = 1;
171 		    break;
172 		case 'G':
173 		    cp = cur_grp;
174 		    break;
175 		case 'L':
176 		    if ((cp = strrchr(cur_grp, '.')))
177 			cp++;
178 		    else
179 			cp = cur_grp;
180 		    break;
181 		case 'N':
182 		    if (expand_mode & 2)
183 			goto copy;
184 		    if (cur_art)
185 			cp = group_file_name;
186 		    if (cp == NULL)
187 			goto copy;
188 		    break;
189 		default:
190 		    goto copy;
191 	    }
192 	    src++;
193 
194 	    if (!cp) {
195 		msg("$%c not defined on this level", c);
196 		return 0;
197 	    }
198 	    while (*cp)
199 		if (remap && *cp == '.')
200 		    cp++, *dp++ = '/';
201 		else
202 		    *dp++ = *cp++;
203 	    goto no_parse;
204 	}
205 	if (c == '/')
206 
207 #ifdef ALLOW_LEADING_DOUBLE_SLASH
208 	    if (dp != &dest[1])
209 #endif
210 
211 		if (dp != dest && dp[-1] == '/')
212 		    goto no_parse;
213 
214 copy:
215 	*dp++ = c;
216 	parse = isspace(c);
217 	continue;
218 
219 no_parse:
220 	parse = 0;
221     }
222 
223     *dp = NUL;
224 
225     return 1;
226 }
227 
228 
229 int
file_completion(char * path,int index)230 file_completion(char *path, int index)
231 {
232     static int      dir_in_use = 0;
233     static char    *head, *tail = NULL;
234     static int      tail_offset;
235 
236     char            nbuf[FILENAME], buffer[FILENAME];
237     char           *dir, *base;
238 
239     if (path) {
240 	if (dir_in_use) {
241 	    close_directory();
242 	    dir_in_use = 0;
243 	}
244 	if (index < 0)
245 	    return 0;
246 
247 	head = path;
248 	tail = path + index;
249     }
250     if (!dir_in_use) {
251 	path = head;
252 	*tail = NUL;
253 
254 	if (*path == '|')
255 	    return -1;		/* no completion for pipes */
256 
257 	if (*path == '+' || *path == '~') {
258 	    if (!expand_file_name(nbuf, path, 0x11))
259 		return 0;	/* no completions */
260 	} else
261 	    strcpy(nbuf, path);
262 
263 	if ((base = strrchr(nbuf, '/'))) {
264 	    if (base == nbuf) {
265 		dir = "/";
266 		base++;
267 	    } else {
268 		*base++ = NUL;
269 		dir = nbuf;
270 	    }
271 	} else {
272 	    base = nbuf;
273 	    dir = ".";
274 	}
275 
276 	tail_offset = strlen(base);
277 
278 	dir_in_use = list_directory(dir, base);
279 
280 	return dir_in_use;
281     }
282     if (index)
283 	return compl_help_directory();
284 
285     if (!next_directory(buffer, 1))
286 	return 0;
287 
288     strcpy(tail, buffer + tail_offset);
289 
290     return 1;
291 }
292 
293 
294 static int      cancel_count;
295 
296 void
fcancel(article_header * ah)297 fcancel(article_header * ah)
298 {
299     if (ah->attr == A_CANCEL) {
300 	cancel_count--;
301 	ah->attr = 0;
302     } else {
303 	cancel_count++;
304 	ah->attr = A_CANCEL;
305     }
306 }
307 
308 static int
folder_header(void)309 folder_header(void)
310 {
311     so_printxy(0, 0, "Folder: %s", current_group->group_name);
312 
313     return 1;			/* number of header lines */
314 }
315 
316 /*
317  *	mode values:
318  *		0: normal folder or digest
319  *		1: online manual
320  *		2: mailbox
321  */
322 
323 int
folder_menu(char * path,int mode)324 folder_menu(char *path, int mode)
325 {
326     FILE           *folder;
327     register article_header *ah;
328     news_header_buffer dgbuf;
329     char            buffer[256];
330     int             more, length, re, menu_cmd, was_raw;
331     memory_marker   mem_marker;
332     group_header    fake_group;
333     int             cc_save;
334     char            folder_name[FILENAME], folder_file[FILENAME];
335     int             orig_layout;
336     char           *orig_hdr_lines;
337 
338     orig_layout = fmt_linenum;
339     orig_hdr_lines = header_lines;
340 
341     strcpy(folder_name, path);
342     fake_group.group_name = folder_name;
343     fake_group.kill_list = NULL;
344     if (!expand_file_name(folder_file, folder_name, 1))
345 	return ME_NO_REDRAW;
346     fake_group.archive_file = path = folder_file;
347     fake_group.next_group = fake_group.prev_group = NULL;
348     fake_group.group_flag = G_FOLDER | G_FAKED;
349     fake_group.master_flag = 0;
350     fake_group.save_file = NULL;
351     current_group = NULL;
352     init_group(&fake_group);
353 
354     folder = open_file(path, OPEN_READ);
355     if (folder == NULL) {
356 	msg("%s not found", path);
357 	return ME_NO_REDRAW;
358     }
359     switch (get_folder_type(folder)) {
360 	case 0:
361 	    msg("Reading: %-.65s", path);
362 	    break;
363 	case 1:
364 	case 2:
365 	    msg("Reading %s folder: %-.50s",
366 		current_folder_type == 1 ? "mail" : "mmdf", path);
367 	    break;
368 	default:
369 	    msg("Folder is empty");
370 	    fclose(folder);
371 	    return ME_NO_REDRAW;
372     }
373     rewind(folder);
374 
375     was_raw = no_raw();
376     s_keyboard = 0;
377 
378     current_group = &fake_group;
379 
380     mark_memory(&mem_marker);
381 
382     ah = alloc_art();
383 
384     more = 1;
385     while (more && (more = get_digest_article(folder, dgbuf)) >= 0) {
386 	if (s_keyboard)
387 	    break;
388 
389 	ah->a_number = 0;
390 	ah->flag = A_FOLDER;
391 	ah->attr = 0;
392 
393 	ah->lines = digest.dg_lines;
394 
395 	ah->hpos = digest.dg_hpos;
396 	ah->fpos = digest.dg_fpos;
397 	ah->lpos = digest.dg_lpos;
398 
399 	if (digest.dg_from) {
400 	    length = pack_name(buffer, digest.dg_from, Name_Length);
401 	    ah->sender = alloc_str(length);
402 	    strcpy(ah->sender, buffer);
403 	    ah->name_length = length;
404 	    if (mode == 1)
405 		fold_string(ah->sender);
406 	} else {
407 	    ah->sender = "";
408 	    ah->name_length = 0;
409 	}
410 
411 	if (digest.dg_subj) {
412 	    length = pack_subject(buffer, digest.dg_subj, &re, 255);
413 	    ah->replies = re;
414 	    ah->subject = alloc_str(length);
415 	    strcpy(ah->subject, buffer);
416 	    ah->subj_length = length;
417 	    if (mode == 1 && length > 1)
418 		fold_string(ah->subject + 1);
419 	} else {
420 	    ah->replies = 0;
421 	    ah->subject = "";
422 	    ah->subj_length = 0;
423 	}
424 
425 	ah->t_stamp = digest.dg_date ? pack_date(digest.dg_date) : 0;
426 
427 	add_article(ah);
428 	ah = alloc_art();
429     }
430 
431     fclose(folder);
432 
433     if (s_keyboard) {
434 	menu_cmd = ME_NO_REDRAW;
435     } else if (n_articles == 0) {
436 	msg("Not a folder (no article header)");
437 	menu_cmd = ME_NO_REDRAW;
438     } else {
439 	if (n_articles > 1) {
440 	    clrdisp();
441 	    prompt_line = 2;
442 	    if (mode == 0 && !dont_sort_folders)
443 		sort_articles(-1);
444 	    else if (mode == 1) {
445 		article_number  n;
446 		for (n = 0; n < n_articles; n++) {
447 		    ah = articles[n];
448 		    if (n == 0)
449 			ah->flag |= A_ROOT_ART;
450 		    else if (strcmp(ah->sender, articles[n - 1]->sender) == 0)
451 			articles[n - 1]->flag |= A_NEXT_SAME;
452 		    else
453 			ah->flag |= A_ROOT_ART;
454 		}
455 		bypass_consolidation = consolidated_manual ? 2 : 1;
456 	    } else
457 		no_sort_articles();
458 	} else
459 	    no_sort_articles();
460 
461 	cc_save = cancel_count;
462 	cancel_count = 0;
463 	if (mode == 1) {
464 	    fmt_linenum = -1;
465 	    header_lines = "*";
466 	}
467 reenter_menu:
468 	ignore_fancy_select = 1;
469 	menu_cmd = menu(folder_header);
470 	ignore_fancy_select = 0;
471 
472 	if (mode == 0 && cancel_count) {
473 	    register article_number cur;
474 
475 	    cancel_count = 0;
476 	    for (cur = 0; cur < n_articles; cur++) {
477 		if (articles[cur]->attr == A_CANCEL)
478 		    cancel_count++;
479 	    }
480 	}
481 	if (mode == 0 && cancel_count) {
482 	    clrdisp();
483 	    tprintf("\rFolder: %s\n\rFile:   %s\n\n\r", folder_name, folder_file);
484 	    if (cancel_count == n_articles)
485 		tprintf("Cancel all articles and remove folder? ");
486 	    else
487 		tprintf("Remove %d article%s from folder? ",
488 			cancel_count, plural((long) cancel_count));
489 	    fl;
490 
491 	    switch (yes(1)) {
492 		case 1:
493 		    tprintf("\n\n");
494 		    if (cancel_count == n_articles) {
495 			if (unlink(group_path_name) < 0 &&
496 			    nn_truncate(group_path_name, (off_t) 0) < 0) {
497 			    tprintf("\rCould not unlink %s\n\r", group_path_name);
498 			    any_key(0);
499 			}
500 		    } else
501 			rewrite_folder();
502 		    break;
503 		case 0:
504 		    break;
505 		default:
506 		    goto reenter_menu;
507 	    }
508 	}
509 	cancel_count = cc_save;
510     }
511 
512     release_memory(&mem_marker);
513     if (fmt_linenum == -1) {
514 	fmt_linenum = orig_layout;
515 	header_lines = orig_hdr_lines;
516     }
517     if (was_raw)
518 	nn_raw();
519 
520     return menu_cmd;
521 }
522 
523 static void
rewrite_folder(void)524 rewrite_folder(void)
525 {
526     register FILE  *src, *dst;
527     char           *oldfile;
528     register int    c;
529     register long   cnt;
530     register article_header *ah, **ahp;
531     register article_number n;
532 
533     if (strchr(backup_folder_path, '/'))
534 	oldfile = backup_folder_path;
535     else
536 	oldfile = relative(nn_directory, backup_folder_path);
537 
538     if (move_file(group_path_name, oldfile, 1) < 0) {
539 	tprintf("\r\n\nCannot backup folder in %s\n", oldfile);
540 	goto confirm;
541     }
542     if ((src = open_file(oldfile, OPEN_READ)) == NULL) {
543 	tprintf("\rCannot open %s\n\r", oldfile);
544 	goto move_back;
545     }
546     if ((dst = open_file(group_path_name, OPEN_CREATE)) == NULL) {
547 	fclose(src);
548 	tprintf("\rCannot create %s\n\r", group_path_name);
549 	goto move_back;
550     }
551     sort_articles(-2);
552 
553     tprintf("\rCompressing folder...\n\r");
554     fl;
555 
556     get_folder_type(src);
557 
558     for (ahp = articles, n = n_articles; --n >= 0; ahp++) {
559 	ah = *ahp;
560 	cnt = ah->lpos - ah->hpos;
561 	if (folder_rewrite_trace)
562 	    tprintf("%s\t%s (%ld-%ld=%ld)\n\r",
563 		    ah->attr == A_CANCEL ? "CANCEL" : "KEEP",
564 		    ah->subject, ah->hpos, (long) (ah->lpos), cnt);
565 	if (ah->attr == A_CANCEL)
566 	    continue;
567 	fseek(src, ah->hpos, 0);
568 	mailbox_format(dst, -1);
569 	while (--cnt >= 0) {
570 	    if ((c = getc(src)) == EOF)
571 		break;
572 	    putc(c, dst);
573 	}
574 	mailbox_format(dst, 0);
575     }
576 
577     fclose(src);
578     if (fclose(dst) == EOF)
579 	goto move_back;
580     if (!keep_backup_folder)
581 	unlink(oldfile);
582     if (folder_rewrite_trace)
583 	goto confirm;
584     return;
585 
586 move_back:
587     if (move_file(oldfile, group_path_name, 2) == 0)
588 	tprintf("Cannot create new file -- Folder restored\n\r");
589     else
590 	tprintf("Cannot create new file\n\n\rFolder saved in %s\n\r", oldfile);
591 
592 confirm:
593     any_key(0);
594 }
595