1 /*
2  *	(c) Copyright 1990, Kim Fabricius Storm.  All rights reserved.
3  *      Copyright (c) 1996-2005 Michael T Pins.  All rights reserved.
4  *
5  *	Basic article access and management
6  */
7 
8 #include <string.h>
9 #include <stdlib.h>
10 #include "config.h"
11 #include "global.h"
12 #include "articles.h"
13 #include "db.h"
14 #include "kill.h"
15 #include "match.h"
16 #include "news.h"
17 #include "newsrc.h"
18 #include "regexp.h"
19 #include "sort.h"
20 #include "nn_term.h"
21 
22 /* articles.c */
23 
24 static void     new_thunk(thunk * t, char *ptr, long size);
25 
26 #ifdef ART_GREP
27 static int      grep_article(group_header * gh, article_header * ah, char *pattern, int (*fcn) ());
28 #endif				/* ART_GREP */
29 
30 int             seq_cross_filtering = 1;
31 int             select_leave_next = 0;	/* ask to select left over art. */
32 int             ignore_re = 0;
33 int             body_search_header = 0;
34 int             cross_post_limit = 0;
35 
36 extern int      ignore_fancy_select;
37 extern int      killed_articles;
38 
39 extern attr_type test_article();
40 
41 /*
42  * memory management
43  */
44 
45 static          thunk
46                 dummy_str_t = {
47     NULL,
48     NULL,
49     0L
50 },
51 
52                 dummy_art_t = {
53     NULL,
54     NULL,
55     0L
56 };
57 
58 
59 static thunk   *first_str_t = &dummy_str_t;
60 static thunk   *current_str_t = &dummy_str_t;
61 static thunk   *first_art_t = &dummy_art_t;
62 static thunk   *current_art_t = &dummy_art_t;
63 static long     cur_str_size = 0, cur_art_size = 0;
64 static char    *next_str;
65 static article_header *next_art;
66 static article_header **art_array = NULL;
67 
68 static article_number max_articles = 0, mem_offset = 0;
69 
70 /*
71  * allocate one article header
72  */
73 
74 #ifndef ART_THUNK_SIZE
75 #define ART_THUNK_SIZE	127
76 #endif
77 
78 static void
new_thunk(thunk * t,char * ptr,long size)79 new_thunk(thunk * t, char *ptr, long size)
80 {
81     thunk          *new;
82 
83     new = newobj(thunk, 1);
84 
85     new->next_thunk = t->next_thunk;
86     t->next_thunk = new;
87 
88     new->this_thunk = ptr;
89     new->thunk_size = size;
90 }
91 
92 
93 article_header *
alloc_art(void)94 alloc_art(void)
95 {
96     if (cur_art_size == 0) {
97 	if (current_art_t->next_thunk == NULL)
98 	    new_thunk(current_art_t,
99 		      (char *) newobj(article_header, ART_THUNK_SIZE),
100 		      (long) ART_THUNK_SIZE);
101 
102 	current_art_t = current_art_t->next_thunk;
103 	next_art = (article_header *) current_art_t->this_thunk;
104 	cur_art_size = current_art_t->thunk_size;
105     }
106     cur_art_size--;
107     return next_art++;
108 }
109 
110 /*
111  * allocate a string of length 'len'
112  */
113 
114 #ifndef STR_THUNK_SIZE
115 #define STR_THUNK_SIZE	((1<<14) - 32)	/* leave room for malloc header */
116 #endif
117 
118 char           *
alloc_str(int len)119 alloc_str(int len)
120 {
121     /* allow space for '\0', and align on word boundary */
122     int             size = (len + sizeof(int)) & ~(sizeof(int) - 1);
123     char           *ret;
124 
125     if (cur_str_size < size) {
126 	if (current_str_t->next_thunk == NULL)
127 	    new_thunk(current_str_t,
128 		      newstr(STR_THUNK_SIZE), (long) STR_THUNK_SIZE);
129 
130 	current_str_t = current_str_t->next_thunk;
131 	next_str = current_str_t->this_thunk;
132 	cur_str_size = current_str_t->thunk_size;
133     }
134     /* XXX else should do something reasonable */
135 
136     ret = next_str;
137     cur_str_size -= size;
138     next_str += size;
139 
140     ret[len] = NUL;		/* ensure string is null terminated */
141     return ret;
142 }
143 
144 /*
145  * "free" the allocated memory
146  */
147 
148 void
free_memory(void)149 free_memory(void)
150 {
151     current_str_t = first_str_t;
152     current_art_t = first_art_t;
153     cur_str_size = 0;
154     cur_art_size = 0;
155     n_articles = 0;
156 }
157 
158 
159 /*
160  * mark/release memory
161  */
162 
163 
164 void
mark_str(string_marker * str_marker)165 mark_str(string_marker * str_marker)
166 {
167     str_marker->sm_cur_t = current_str_t;
168     str_marker->sm_size = cur_str_size;
169     str_marker->sm_next = next_str;
170 }
171 
172 void
release_str(string_marker * str_marker)173 release_str(string_marker * str_marker)
174 {
175     current_str_t = str_marker->sm_cur_t;
176     cur_str_size = str_marker->sm_size;
177     next_str = str_marker->sm_next;
178 }
179 
180 
181 void
mark_memory(memory_marker * mem_marker)182 mark_memory(memory_marker * mem_marker)
183 {
184     mark_str(&(mem_marker->mm_string));
185 
186     mem_marker->mm_cur_t = current_art_t;
187     mem_marker->mm_size = cur_art_size;
188     mem_marker->mm_next = next_art;
189 
190     mem_marker->mm_nart = n_articles;
191     mem_offset += n_articles;
192 
193     n_articles = 0;
194     articles = art_array + mem_offset;
195 }
196 
197 void
release_memory(memory_marker * mem_marker)198 release_memory(memory_marker * mem_marker)
199 {
200     release_str(&(mem_marker->mm_string));
201 
202     current_art_t = mem_marker->mm_cur_t;
203     cur_art_size = mem_marker->mm_size;
204     next_art = mem_marker->mm_next;
205 
206     n_articles = mem_marker->mm_nart;
207 
208     mem_offset -= n_articles;
209     articles = art_array + mem_offset;
210 }
211 
212 /*
213  * merge all memory chunks into one.
214  */
215 
216 void
merge_memory(void)217 merge_memory(void)
218 {
219     n_articles += mem_offset;
220     mem_offset = 0;
221     articles = art_array;
222 }
223 
224 
225 /*
226  * save article header in 'articles' array
227  * 'articles' is enlarged if too small
228  */
229 
230 #define	FIRST_ART_ARRAY_SIZE	500	/* malloc header */
231 #define	NEXT_ART_ARRAY_SIZE	512
232 
233 void
add_article(article_header * art)234 add_article(article_header * art)
235 {
236     if ((n_articles + mem_offset) == max_articles) {
237 	/* must increase size of 'articles' */
238 
239 	if (max_articles == 0) {
240 	    /* allocate initial 'articles' array */
241 	    max_articles = FIRST_ART_ARRAY_SIZE;
242 	} else {
243 	    max_articles += NEXT_ART_ARRAY_SIZE;
244 	}
245 	art_array = resizeobj(art_array, article_header *, max_articles);
246 	articles = art_array + mem_offset;
247     }
248     articles[n_articles] = art;
249     n_articles++;
250 }
251 
252 
253 int
access_group(register group_header * gh,article_number first_article,article_number last_article,register flag_type flags,char * mask)254 access_group(register group_header * gh, article_number first_article, article_number last_article, register flag_type flags, char *mask)
255 {
256     register article_header *ah;
257     int             skip_digest, n;
258     group_header   *cpgh;
259 
260 #ifdef NOV
261     int             dbstatus;
262 #else				/* NOV */
263     FILE           *data;
264     long            data_offset, data_size;
265 #endif				/* NOV */
266 
267     cross_post_number cross_post;
268     attr_type       leave_attr;
269     memory_marker   mem_marker;
270     static regexp  *rexp = NULL;
271     static char     subptext[80];
272 
273     if (first_article < gh->first_db_article)
274 	first_article = gh->first_db_article;
275 
276     if (last_article > gh->last_db_article)
277 	last_article = gh->last_db_article;
278 
279     if (last_article == 0 || first_article > last_article)
280 	return 0;
281 
282 #ifdef NOV
283     db_read_group(gh, first_article, last_article);
284 #else
285     data = open_data_file(gh, 'd', OPEN_READ);
286     if (data == NULL)
287 	return -10;
288 
289     if ((data_offset = get_data_offset(gh, first_article)) == -1)
290 	return -11;
291 #endif				/* NOV */
292 
293     if (mask == NULL) {
294 	if (rexp != NULL) {
295 	    freeobj(rexp);
296 	    rexp = NULL;
297 	}
298     } else {
299 	if (*mask == '/') {
300 	    mask++;
301 	    if (rexp != NULL) {
302 		if (strncmp(mask, subptext, 80) != 0) {
303 		    freeobj(rexp);
304 		    rexp = NULL;
305 		}
306 	    }
307 	    if (rexp == NULL) {
308 		strncpy(subptext, mask, 80);
309 		rexp = regcomp(mask);
310 		if (rexp == NULL)
311 		    return -1;
312 	    }
313 	    /* notice mask is still non-NULL */
314 	}
315     }
316 
317     if ((flags & (ACC_ALSO_READ_ARTICLES | ACC_ONLY_READ_ARTICLES)))
318 	leave_attr = 0;
319     else if (select_leave_next)
320 	leave_attr = A_READ;	/* will prompt */
321     else
322 	leave_attr = A_LEAVE_NEXT;
323 
324     if (!(flags & ACC_SPEW_MODE))
325 	use_newsrc(gh, (flags & ACC_MERGED_NEWSRC) ? 2 :
326 		   (flags & ACC_ORIG_NEWSRC) ? 1 : 0);
327 
328     if ((flags & ACC_EXTRA_ARTICLES) == 0)
329 	mark_memory(&mem_marker);
330 
331     ah = alloc_art();
332 
333     skip_digest = 0;
334 
335 #ifdef NOV
336     /* XXX: db_read_art takes a FILE * in nnmaster version */
337     while ((dbstatus = db_read_art((FILE *) - 1)) > 0) {
338 
339 #ifdef ART_GREP
340 	if (s_keyboard || s_hangup)
341 	    break;		/* add to allow interrupt of GREP */
342 #endif
343 
344 	if (db_hdr.dh_number < first_article)
345 	    continue;
346 #else
347     fseek(data, data_offset, 0);
348 
349     while (data_offset < gh->data_write_offset) {
350 
351 #ifdef ART_GREP
352 	if (s_keyboard || s_hangup)
353 	    break;		/* add to allow interrupt of GREP */
354 #endif
355 
356 	data_size = db_read_art(data);
357 	if (data_size <= 0) {
358 	    fclose(data);
359 	    if ((flags & ACC_EXTRA_ARTICLES) == 0)
360 		release_memory(&mem_marker);
361 	    return -2;
362 	}
363 	data_offset += data_size;
364 #endif				/* NOV */
365 
366 	if (db_hdr.dh_lpos == (off_t) 0)
367 	    continue;		/* article not accessible */
368 
369 	if (db_hdr.dh_number > gh->last_db_article
370 	    || db_hdr.dh_number < gh->first_db_article)
371 	    goto data_error;
372 
373 	if (skip_digest && db_data.dh_type == DH_SUB_DIGEST)
374 	    continue;
375 
376 	skip_digest = 0;
377 
378 	if (cross_post_limit && db_hdr.dh_cross_postings >= cross_post_limit)
379 	    continue;
380 
381 	if (db_hdr.dh_cross_postings && !(flags & ACC_ALSO_CROSS_POSTINGS)) {
382 	    for (n = 0; n < (int) db_hdr.dh_cross_postings; n++) {
383 		cross_post = NETW_CROSS_INT(db_data.dh_cross[n]);
384 		if (cross_post < 0 || cross_post >= master.number_of_groups)
385 		    continue;
386 		cpgh = ACTIVE_GROUP(cross_post);
387 		if (cpgh == gh) {
388 		    if (seq_cross_filtering)
389 			continue;
390 		    n = db_hdr.dh_cross_postings;
391 		    break;
392 		}
393 		if (!(flags & ACC_ALSO_UNSUB_GROUPS))
394 		    if (cpgh->group_flag & G_UNSUBSCRIBED)
395 			continue;
396 
397 		if (!seq_cross_filtering)
398 		    break;
399 		if (cpgh->preseq_index > 0 &&
400 		    cpgh->preseq_index < gh->preseq_index)
401 		    break;
402 	    }
403 
404 	    if (n < (int) db_hdr.dh_cross_postings) {
405 		if (db_data.dh_type == DH_DIGEST_HEADER)
406 		    skip_digest++;
407 		continue;
408 	    }
409 	}
410 	ah->flag = 0;
411 
412 	switch (db_data.dh_type) {
413 	    case DH_DIGEST_HEADER:
414 		if (flags & ACC_DONT_SPLIT_DIGESTS)
415 		    skip_digest++;
416 		else if ((flags & ACC_ALSO_FULL_DIGEST) == 0)
417 		    continue;	/* don't want the full digest when split */
418 		ah->flag |= A_FULL_DIGEST;
419 		break;
420 	    case DH_SUB_DIGEST:
421 		ah->flag |= A_DIGEST;
422 		break;
423 	}
424 
425 	ah->a_number = db_hdr.dh_number;
426 	if (ah->a_number > last_article)
427 	    break;
428 
429 	if (flags & ACC_SPEW_MODE) {
430 	    fold_string(db_data.dh_subject);
431 	    tprintf("%x:%s\n", (int) (gh->group_num), db_data.dh_subject);
432 	    continue;
433 	}
434 	ah->hpos = db_hdr.dh_hpos;
435 	ah->fpos = ah->hpos + (off_t) (db_hdr.dh_fpos);
436 	ah->lpos = db_hdr.dh_lpos;
437 
438 	ah->attr = test_article(ah);
439 
440 	if (flags & ACC_MERGED_NEWSRC) {
441 	    if (ah->attr == A_KILL)
442 		continue;
443 	} else if (ah->attr != A_READ && (flags & ACC_ONLY_READ_ARTICLES))
444 	    continue;
445 
446 #ifdef ART_GREP
447 	if ((flags & ACC_ON_GREP_UNREAD) && (ah->attr == A_READ))
448 	    continue;
449 #endif				/* ART_GREP */
450 
451 	if (rexp != NULL) {
452 	    if (flags & ACC_ON_SUBJECT)
453 		if (regexec_cf(rexp, db_data.dh_subject))
454 		    goto match_ok;
455 	    if (flags & ACC_ON_SENDER)
456 		if (regexec_cf(rexp, db_data.dh_sender))
457 		    goto match_ok;
458 
459 #ifdef ART_GREP
460 	    if (flags & (ACC_ON_GREP_UNREAD | ACC_ON_GREP_ALL))
461 		if (grep_article(gh, ah, (char *) rexp, regexec_cf))
462 		    goto match_ok;
463 #endif				/* ART_GREP */
464 
465 	    continue;
466 	} else if (mask != NULL) {
467 	    if (flags & ACC_ON_SUBJECT)
468 		if (strmatch_cf(mask, db_data.dh_subject))
469 		    goto match_ok;
470 	    if (flags & ACC_ON_SENDER)
471 		if (strmatch_cf(mask, db_data.dh_sender))
472 		    goto match_ok;
473 
474 #ifdef ART_GREP
475 	    if (flags & (ACC_ON_GREP_UNREAD | ACC_ON_GREP_ALL))
476 		if (grep_article(gh, ah, mask, strmatch_cf))
477 		    goto match_ok;
478 #endif				/* ART_GREP */
479 
480 	    continue;
481 	}
482 match_ok:
483 
484 attr_again:
485 
486 	switch (ah->attr) {
487 	    case A_LEAVE:
488 		if (mask) {
489 		    ah->attr = 0;
490 		    break;
491 		}
492 		if (leave_attr == A_READ) {
493 		    clrdisp();
494 		    prompt("Select left over articles in group %s? ", gh->group_name);
495 		    leave_attr = yes(0) > 0 ? A_SELECT : A_LEAVE_NEXT;
496 		}
497 		ah->attr = leave_attr;
498 		goto attr_again;
499 
500 	    case A_SELECT:
501 		if (mask)
502 		    ah->attr = 0;
503 		break;
504 
505 	    case A_READ:
506 		if (flags & ACC_MERGED_NEWSRC)
507 		    break;
508 
509 		if (!(flags & (ACC_ALSO_READ_ARTICLES | ACC_ONLY_READ_ARTICLES)))
510 		    if (ah->a_number > gh->last_article)
511 			continue;
512 
513 		/* FALLTHROUGH */
514 	    case A_SEEN:
515 	    case 0:
516 		if (flags & ACC_DO_KILL) {
517 		    ah->replies = db_hdr.dh_replies;
518 		    ah->sender = db_data.dh_sender;
519 		    ah->subject = db_data.dh_subject;
520 		    if (kill_article(ah))
521 			continue;
522 		}
523 		/* The 'P' command to a read group must show articles as read */
524 		if (gh->unread_count <= 0)
525 		    ah->attr = A_READ;
526 		break;
527 
528 	    default:
529 		break;
530 	}
531 
532 	if (db_hdr.dh_sender_length) {
533 	    ah->name_length = db_hdr.dh_sender_length;
534 	    ah->sender = alloc_str((int) db_hdr.dh_sender_length);
535 	    strcpy(ah->sender, db_data.dh_sender);
536 	} else
537 	    ah->sender = "";
538 
539 	if (db_hdr.dh_subject_length) {
540 	    ah->subj_length = db_hdr.dh_subject_length;
541 	    ah->subject = alloc_str((int) db_hdr.dh_subject_length);
542 	    strcpy(ah->subject, db_data.dh_subject);
543 	} else
544 	    ah->subject = "";
545 
546 	ah->replies = db_hdr.dh_replies;
547 	ah->lines = db_hdr.dh_lines;
548 	ah->t_stamp = db_hdr.dh_date;
549 
550 	ah->a_group = (flags & ACC_MERGED_MENU) ? current_group : NULL;
551 
552 	add_article(ah);
553 	ah = alloc_art();
554     }
555 
556 #ifdef NOV
557     if (dbstatus < 0) {		/* Error reading DB? */
558 	if ((flags & ACC_EXTRA_ARTICLES) == 0)
559 	    release_memory(&mem_marker);
560 	return -2;
561     }
562     /* else EOF reading DB */
563 #else
564     fclose(data);
565 #endif				/* NOV */
566 
567     if ((flags & ACC_DONT_SORT_ARTICLES) == 0)
568 	sort_articles(-1);
569     else
570 	no_sort_articles();
571 
572     if (ignore_re && !ignore_fancy_select && !(flags & ACC_ALSO_READ_ARTICLES)) {
573 	int             nexta, roota, killa, newa;
574 	for (nexta = killa = newa = roota = 0; nexta < n_articles; nexta++) {
575 	    ah = articles[nexta];
576 	    if (ah->flag & A_ROOT_ART)
577 		roota = ah->replies & 127;
578 	    if (roota && ah->replies && !auto_select_article(ah, 1))
579 		killa++;
580 	    else
581 		articles[newa++] = ah;
582 	}
583 	n_articles -= killa;
584 	killed_articles += killa;
585     }
586     return n_articles > 0 ? 1 : 0;
587 
588 data_error:
589     log_entry('E', "%s: data inconsistency", gh->group_name);
590 
591 #ifndef NOV
592     fclose(data);
593 #endif				/* NOV */
594 
595     if ((flags & ACC_EXTRA_ARTICLES) == 0)
596 	release_memory(&mem_marker);
597     return -12;
598 }
599 
600 #ifdef ART_GREP
601 /* GREP ARTICLE -- search the article for the pattern using the specified function
602    Return 1 if pattern occurs in the article else 0
603    Also return 0 if any malloc or file open or read error occurs
604    Global data_header db_hdr is set by caller
605 todo:
606    doesn't work for folders (hangs)
607 */
608 
609 static int
610 grep_article(group_header * gh, article_header * ah, char *pattern, int (*fcn) ())
611 {
612     char           *line, *end;
613     FILE           *art;
614     news_header_buffer buffer1, buffer2;
615     static char    *buf;
616     static int      bufsize, count;
617     size_t          size;
618 
619     count++;
620     art = open_news_article(ah, FILL_OFFSETS |
621 		  (body_search_header ? 0 : SKIP_HEADER), buffer1, buffer2);
622     if (!art) {
623 	/* msg("Cannot open article"); */
624 	return 0;
625     }
626     size = ah->lpos - ftell(art) + 1;
627     if (bufsize < size) {
628 	if (buf)
629 	    free(buf);
630 	buf = (char *) malloc(size + 10);
631 	if (!buf) {
632 	    msg("Cannot malloc %d bytes", size);
633 	    bufsize = 0;
634 	    return 0;
635 	}
636 	bufsize = size;
637     }
638     if (fread(buf, size - 1, 1, art) != 1) {
639 	fclose(art);
640 	/* msg("Cannot read article"); */
641 	return 0;
642     }
643     fclose(art);
644     /* print status message every so often */
645     if (!(count % 27))
646 	msg("Searching %d of %d",
647 	    ah->a_number - gh->first_db_article,
648 	    gh->last_db_article - gh->first_db_article);
649 
650     buf[size] = 0;		/* make buf a giant string so strchr below
651 				 * terminates */
652     line = buf;
653     while ((end = strchr(line, '\n'))) {
654 	*end++ = 0;		/* make the line a nul terminated string */
655 	if ((*fcn) (pattern, line))
656 	    return 1;
657 	line = end;
658     }
659     return 0;
660 }
661 
662 #endif				/* ART_GREP */
663