1 /* artio.c
2  */
3 /* This software is copyrighted as detailed in the LICENSE file. */
4 
5 
6 #include "EXTERN.h"
7 #include "common.h"
8 #include "list.h"
9 #include "hash.h"
10 #include "ngdata.h"
11 #include "nntpclient.h"
12 #include "datasrc.h"
13 #include "nntp.h"
14 #include "cache.h"
15 #include "rthread.h"
16 #include "head.h"
17 #include "mime.h"
18 #include "term.h"
19 #include "ng.h"
20 #include "art.h"
21 #include "search.h"
22 #include "artstate.h"
23 #include "bits.h"
24 #include "final.h"
25 #include "util.h"
26 #include "util2.h"
27 #include "color.h"
28 #include "decode.h"
29 #include "INTERN.h"
30 #include "artio.h"
31 
32 void
artio_init()33 artio_init()
34 {
35     artbuf_size = 8 * 1024;
36     artbuf = safemalloc(artbuf_size);
37     clear_artbuf();
38 }
39 
40 /* open an article, unless it's already open */
41 
42 FILE*
artopen(artnum,pos)43 artopen(artnum, pos)
44 ART_NUM artnum;
45 ART_POS pos;
46 {
47     char artname[MAXFILENAME];		/* filename of current article */
48     ARTICLE* ap = article_find(artnum);
49 
50     if (!ap || !artnum || (ap->flags & (AF_EXISTS|AF_FAKE)) != AF_EXISTS) {
51 	errno = ENOENT;
52 	return NULL;
53     }
54     if (openart == artnum) {		/* this article is already open? */
55 	seekart(pos);			/* yes: just seek the file */
56 	return artfp;			/* and say we succeeded */
57     }
58     artclose();
59 retry_open:
60 #ifdef SUPPORT_NNTP
61     if (datasrc->flags & DF_REMOTE)
62 	nntp_body(artnum);
63     else
64 #endif
65     {
66 	sprintf(artname,"%ld",(long)artnum);
67 	artfp = fopen(artname,"r");
68 	/*artio_setbuf(artfp);$$*/
69     }
70     if (!artfp) {
71 #ifdef ETIMEDOUT
72 	if (errno == ETIMEDOUT)
73 	    goto retry_open;
74 #endif
75 	if (errno == EINTR)
76 	    goto retry_open;
77 	uncache_article(ap,FALSE);
78     } else {
79 #ifdef LINKART
80 #ifdef SUPPORT_NNTP
81 	if (!(datasrc->flags & DF_REMOTE))
82 #endif
83 	{
84 	    char tmpbuf[256];
85 	    char* s;
86 
87 	    if (!fstat(fileno(artfp),&filestat)
88 	     && filestat.st_size < sizeof tmpbuf) {
89 		fgets(tmpbuf,sizeof tmpbuf,artfp);
90 		if (FILE_REF(tmpbuf)) {	/* is a "link" to another article */
91 		    fclose(artfp);
92 		    if ((s = index(tmpbuf,'\n')) != NULL)
93 			*s = '\0';
94 		    if (!(artfp = fopen(tmpbuf,"r")))
95 			uncache_article(ap,FALSE);
96 		    else {
97 			if (*linkartname)
98 			    free(linkartname);
99 			linkartname = savestr(tmpbuf);
100 		    }
101 		}
102 	    }
103 	}
104 #endif
105 	openart = artnum;		/* remember what we did here */
106 	seekart(pos);
107     }
108     return artfp;			/* and return either fp or NULL */
109 }
110 
111 void
artclose()112 artclose()
113 {
114     if (artfp != NULL) {		/* article still open? */
115 #ifdef SUPPORT_NNTP
116 	if (datasrc->flags & DF_REMOTE)
117 	    nntp_finishbody(FB_DISCARD);
118 #endif
119 	fclose(artfp);			/* close it */
120 	artfp = NULL;			/* and tell the world */
121 	openart = 0;
122 	clear_artbuf();
123     }
124 }
125 
126 int
seekart(pos)127 seekart(pos)
128 ART_POS pos;
129 {
130 #ifdef SUPPORT_NNTP
131     if (datasrc->flags & DF_REMOTE)
132 	return nntp_seekart(pos);
133 #endif
134     return fseek(artfp,(long)pos,0);
135 }
136 
137 ART_POS
tellart()138 tellart()
139 {
140 #ifdef SUPPORT_NNTP
141     if (datasrc->flags & DF_REMOTE)
142 	return nntp_tellart();
143 #endif
144     return (ART_POS)ftell(artfp);
145 }
146 
147 char*
readart(s,limit)148 readart(s, limit)
149 char* s;
150 int limit;
151 {
152 #ifdef SUPPORT_NNTP
153     if (datasrc->flags & DF_REMOTE)
154 	return nntp_readart(s,limit);
155 #endif
156     return fgets(s,limit,artfp);
157 }
158 
159 void
clear_artbuf()160 clear_artbuf()
161 {
162     *artbuf = '\0';
163     artbuf_pos = artbuf_seek = artbuf_len = 0;
164 }
165 
166 int
seekartbuf(pos)167 seekartbuf(pos)
168 ART_POS pos;
169 {
170     if (!do_hiding)
171 	return seekart(pos);
172 
173     pos -= htype[PAST_HEADER].minpos;
174     artbuf_pos = artbuf_len;
175 
176     while (artbuf_pos < pos) {
177 	if (!readartbuf(FALSE))
178 	    return -1;
179     }
180 
181     artbuf_pos = pos;
182 
183     return 0;
184 }
185 
186 char*
readartbuf(view_inline)187 readartbuf(view_inline)
188 bool_int view_inline;
189 {
190     char* bp;
191     char* s;
192     int read_offset, line_offset, filter_offset, extra_offset, len, o;
193     int word_wrap, extra_chars = 0;
194     int read_something = 0;
195 
196     if (!do_hiding) {
197 	bp = readart(art_line,(sizeof art_line)-1);
198 	artbuf_pos = artbuf_seek = tellart() - htype[PAST_HEADER].minpos;
199 	return bp;
200     }
201     if (artbuf_pos == artsize - htype[PAST_HEADER].minpos)
202 	return NULL;
203     bp = artbuf + artbuf_pos;
204     if (*bp == '\001' || *bp == '\002') {
205 	bp++;
206 	artbuf_pos++;
207     }
208     if (*bp) {
209 	for (s = bp; *s && !AT_NL(*s); s++) ;
210 	if (*s) {
211 	    len = s - bp + 1;
212 	    goto done;
213 	}
214 	read_offset = line_offset = filter_offset = s - bp;
215     }
216     else
217 	read_offset = line_offset = filter_offset = 0;
218 
219   read_more:
220     extra_offset = mime_state == HTMLTEXT_MIME? 1024 : 0;
221     o = read_offset + extra_offset;
222     if (artbuf_size < artbuf_pos + o + LBUFLEN) {
223 	artbuf_size += LBUFLEN * 4;
224 	artbuf = saferealloc(artbuf,artbuf_size);
225 	bp = artbuf + artbuf_pos;
226     }
227     switch (mime_state) {
228       case IMAGE_MIME:
229       case AUDIO_MIME:
230 	break;
231       default:
232 	read_something = 1;
233 	/* The -1 leaves room for appending a newline, if needed */
234 	if (!readart(bp+o, artbuf_size-artbuf_pos-o-1)) {
235 	    if (!read_offset) {
236 		*bp = '\0';
237 		len = 0;
238 		bp = NULL;
239 		goto done;
240 	    }
241 	    strcpy(bp+o, "\n");
242 	    read_something = -1;
243 	}
244 	len = strlen(bp+o) + read_offset;
245 	if (bp[len+extra_offset-1] != '\n') {
246 	    if (read_something >= 0) {
247 		read_offset = len;
248 		goto read_more;
249 	    }
250 	    strcpy(bp + len++ + extra_offset, "\n");
251 	}
252 	if (!is_mime)
253 	    goto done;
254 	o = line_offset + extra_offset;
255 	mime_SetState(bp+o);
256 	if (bp[o] == '\0') {
257 	    strcpy(bp+o, "\n");
258 	    len = line_offset+1;
259 	}
260 	break;
261     }
262   mime_switch:
263     switch (mime_state) {
264       case ISOTEXT_MIME:
265 #if 0 /*def CHARSUBST*/
266 	charsubst = "a"; /*$$*/
267 #endif
268 	mime_state = TEXT_MIME;
269 	/* FALL THROUGH */
270       case TEXT_MIME:
271       case HTMLTEXT_MIME:
272 	if (mime_section->encoding == MENCODE_QPRINT) {
273 	    o = line_offset + extra_offset;
274 	    len = qp_decodestring(bp+o, bp+o, 0) + line_offset;
275 	    if (len == line_offset || bp[len+extra_offset-1] != '\n') {
276 		if (read_something >= 0) {
277 		    read_offset = line_offset = len;
278 		    goto read_more;
279 		}
280 		strcpy(bp + len++ + extra_offset, "\n");
281 	    }
282 	}
283 	else if (mime_section->encoding == MENCODE_BASE64) {
284 	    o = line_offset + extra_offset;
285 	    len = b64_decodestring(bp+o, bp+o) + line_offset;
286 	    if ((s = index(bp+o, '\n')) == NULL) {
287 		if (read_something >= 0) {
288 		    read_offset = line_offset = len;
289 		    goto read_more;
290 		}
291 		strcpy(bp + len++ + extra_offset, "\n");
292 	    }
293 	    else {
294 		extra_chars += len;
295 		len = s - bp - extra_offset + 1;
296 		extra_chars -= len;
297 	    }
298 	}
299 	if (mime_state != HTMLTEXT_MIME)
300 	    break;
301 	o = filter_offset + extra_offset;
302 	len = filter_html(bp+filter_offset, bp+o) + filter_offset;
303 	if (len == filter_offset || (s = index(bp,'\n')) == NULL) {
304 	    if (read_something >= 0) {
305 		read_offset = line_offset = filter_offset = len;
306 		goto read_more;
307 	    }
308 	    strcpy(bp + len++, "\n");
309 	    extra_chars = 0;
310 	}
311 	else {
312 	    extra_chars = len;
313 	    len = s - bp + 1;
314 	    extra_chars -= len;
315 	}
316 	break;
317       case DECODE_MIME: {
318 	MIMECAP_ENTRY* mcp;
319 	mcp = mime_FindMimecapEntry(mime_section->type_name,
320 				    MCF_NEEDSTERMINAL|MCF_COPIOUSOUTPUT);
321 	if (mcp) {
322 	    int save_term_line = term_line;
323 	    nowait_fork = TRUE;
324 	    color_object(COLOR_MIMEDESC, 1);
325 	    if (decode_piece(mcp,bp) != 0) {
326 		strcpy(bp = artbuf + artbuf_pos, art_line);
327 		mime_SetState(bp);
328 		if (mime_state == DECODE_MIME)
329 		    mime_state = SKIP_MIME;
330 	    }
331 	    else
332 		mime_state = SKIP_MIME;
333 	    color_pop();
334 	    chdir_newsdir();
335 	    erase_line(FALSE);
336 	    nowait_fork = FALSE;
337 	    first_view = artline;
338 	    term_line = save_term_line;
339 	    if (mime_state != SKIP_MIME)
340 		goto mime_switch;
341 	}
342 	/* FALL THROUGH */
343       }
344       case SKIP_MIME: {
345 	MIME_SECT* mp = mime_section;
346 	while ((mp = mp->prev) != NULL && !mp->boundary_len) ;
347 	if (!mp) {
348 	    artbuf_len = artbuf_pos;
349 	    artsize = artbuf_len + htype[PAST_HEADER].minpos;
350 	    read_something = 0;
351 	    bp = NULL;
352 	}
353 	else if (read_something >= 0) {
354 	    *bp = '\0';
355 	    read_offset = line_offset = filter_offset = 0;
356 	    goto read_more;
357 	}
358 	else
359 	    *bp = '\0';
360 	len = 0;
361 	break;
362       }
363     case END_OF_MIME:
364 	if (mime_section->prev)
365 	    mime_state = SKIP_MIME;
366 	else {
367 #ifdef SUPPORT_NNTP
368 	    if (datasrc->flags & DF_REMOTE) {
369 		nntp_finishbody(FB_SILENT);
370 		raw_artsize = nntp_artsize();
371 	    }
372 #endif
373 	    seekart(raw_artsize);
374 	}
375 	/* FALL THROUGH */
376       case BETWEEN_MIME:
377 	len = strlen(multipart_separator) + 1;
378 	if (extra_offset && filter_offset) {
379 	    extra_chars = len + 1;
380 	    len = o = read_offset + 1;
381 	    bp[o-1] = '\n';
382 	}
383 	else {
384 	    o = -1;
385 	    artbuf_pos++;
386 	    bp++;
387 	}
388 	sprintf(bp+o,"\002%s\n",multipart_separator);
389 	break;
390       case UNHANDLED_MIME:
391 	mime_state = SKIP_MIME;
392 	*bp++ = '\001';
393 	artbuf_pos++;
394 	mime_Description(mime_section,bp,tc_COLS);
395 	len = strlen(bp);
396 	break;
397       case ALTERNATE_MIME:
398 	mime_state = SKIP_MIME;
399 	*bp++ = '\001';
400 	artbuf_pos++;
401 	sprintf(bp,"[Alternative: %s]\n", mime_section->type_name);
402 	len = strlen(bp);
403 	break;
404       case IMAGE_MIME:
405       case AUDIO_MIME:
406 	if (!mime_article.total && !multimedia_mime)
407 	    multimedia_mime = TRUE;
408 	/* FALL THROUGH */
409       default:
410 	if (view_inline && first_view < artline
411 	 && (mime_section->flags & MSF_INLINE))
412 	    mime_state = DECODE_MIME;
413 	else
414 	    mime_state = SKIP_MIME;
415 	*bp++ = '\001';
416 	artbuf_pos++;
417 	mime_Description(mime_section,bp,tc_COLS);
418 	len = strlen(bp);
419 	break;
420     }
421 
422   done:
423     word_wrap = tc_COLS - word_wrap_offset;
424     if (read_something && word_wrap_offset >= 0 && word_wrap > 20 && bp) {
425 	char* cp;
426 	for (cp = bp; *cp && (s = index(cp, '\n')) != NULL; cp = s+1) {
427 	    if (s - cp > tc_COLS) {
428 		char* t;
429 		do {
430 		    for (t = cp+word_wrap; *t!=' ' && *t!='\t' && t > cp; t--) ;
431 		    if (t == cp) {
432 			for (t = cp+word_wrap; *t!=' ' && *t!='\t' && t<=cp+tc_COLS; t++) ;
433 			if (t > cp+tc_COLS) {
434 			    t = cp + tc_COLS - 1;
435 			    continue;
436 			}
437 		    }
438 		    if (cp == bp) {
439 			extra_chars += len;
440 			len = t - bp + 1;
441 			extra_chars -= len;
442 		    }
443 		    *t = wrapped_nl;
444 		    if (t[1] == ' ' || t[1] == '\t') {
445 			int spaces = 1;
446 			for (t++; *++t == ' ' || *t == '\t'; spaces++) ;
447 			safecpy(t-spaces,t,extra_chars);
448 			extra_chars -= spaces;
449 			t -= spaces + 1;
450 		    }
451 		} while (s - (cp = t+1) > word_wrap);
452 	    }
453 	}
454     }
455     artbuf_pos += len;
456     if (read_something) {
457     	artbuf_seek = tellart();
458 	artbuf_len = artbuf_pos + extra_chars;
459 	if (artsize >= 0)
460 	    artsize = raw_artsize-artbuf_seek+artbuf_len+htype[PAST_HEADER].minpos;
461     }
462 
463     return bp;
464 }
465