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