tin  2.6.1
About: TIN is a threaded NNTP and spool based UseNet newsreader.
  Fossies Dox: tin-2.6.1.tar.xz  ("unofficial" and yet experimental doxygen-generated source code documentation)  

read.c
Go to the documentation of this file.
1/*
2 * Project : tin - a Usenet reader
3 * Module : read.c
4 * Author : Jason Faultless <jason@altarstone.com>
5 * Created : 1997-04-10
6 * Updated : 2021-02-23
7 *
8 * Copyright (c) 1997-2022 Jason Faultless <jason@altarstone.com>
9 * All rights reserved.
10 *
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 *
15 * 1. Redistributions of source code must retain the above copyright notice,
16 * this list of conditions and the following disclaimer.
17 *
18 * 2. Redistributions in binary form must reproduce the above copyright
19 * notice, this list of conditions and the following disclaimer in the
20 * documentation and/or other materials provided with the distribution.
21 *
22 * 3. Neither the name of the copyright holder nor the names of its
23 * contributors may be used to endorse or promote products derived from
24 * this software without specific prior written permission.
25 *
26 * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
27 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
28 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
29 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
30 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
31 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
32 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
33 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
34 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
36 * POSSIBILITY OF SUCH DAMAGE.
37 */
38
39
40#ifndef TIN_H
41# include "tin.h"
42#endif /* !TIN_H */
43#ifndef TNNTP_H
44# include "tnntp.h"
45#endif /* !TNNTP_H */
46#ifndef TIN_MISSING_FD_H
47# include <missing_fd.h>
48#endif /* TIN_MISSING_FD_H */
49
50/*
51 * The initial and expansion sizes to use for allocating read data
52 */
53#define INIT 512
54#define RCHUNK 256
55
56/*
57 * Global error flag. Set if something abnormal happens during file I/O
58 */
60
61/* How many chars we read at last tin_read() */
62static int offset = 0;
63
64/*
65 * local prototypes
66 */
67static char *tin_read(char *buffer, size_t len, FILE *fp, t_bool header);
68
69
70#ifdef HAVE_SELECT
71/*
72 * Used by the I/O read routine to look for keyboard input
73 * Returns TRUE if user aborted with 'q' or 'z' (lynx-style)
74 * FALSE otherwise
75 * TODO: document 'z' (, and allow it's remapping?) and 'Q'
76 * add a !HAVE_SELECT code path
77 */
79wait_for_input(
80 void)
81{
82 int nfds, ch;
83 fd_set readfds;
84 struct timeval tv;
85
86 /*
87 * Main loop. Wait for input from keyboard or file or for a timeout.
88 */
89 forever {
90 FD_ZERO(&readfds);
91 FD_SET(STDIN_FILENO, &readfds);
92/* FD_SET(fileno(NEED_REAL_NNTP_FD_HERE), &readfds); */
93
94 tv.tv_sec = 0; /* NNTP_READ_TIMEOUT; */
95 tv.tv_usec = 0;
96
97/* DEBUG_IO((stderr, "waiting on %d and %d...", STDIN_FILENO, fileno(fd))); */
98# ifdef HAVE_SELECT_INTP
99 if ((nfds = select(STDIN_FILENO + 1, (int *) &readfds, NULL, NULL, &tv)) == -1)
100# else
101 if ((nfds = select(STDIN_FILENO + 1, &readfds, NULL, NULL, &tv)) == -1)
102# endif /* HAVE_SELECT_INTP */
103 {
104 if (errno != EINTR) {
105 perror_message("select() failed");
106 free(tin_progname);
107 giveup();
108 } else
109 return FALSE;
110 }
111
112 /* No input pending */
113 if (nfds == 0)
114 return FALSE;
115
116 /*
117 * Something is waiting. See what's cooking...
118 */
119 if (nfds > 0) {
120 /*
121 * User pressed something. If 'q'uit, then handle this. Process
122 * user input 1st so they get chance to quit on busy (or stalled)
123 * reads
124 */
125 if (FD_ISSET(STDIN_FILENO, &readfds)) {
126 if ((ch = ReadCh()) == EOF)
127 return FALSE;
128
129 /*
130 * check if keymap key was pressed. Unfortunately, keymap keys
131 * are encoded by a sequence of bytes starting with ESC, so we
132 * must first see if it was really an ESC or a keymap key before
133 * asking the user if he wants to abort.
134 */
135 if (ch == iKeyAbort) {
136 int keymap_ch = get_arrow_key(ch);
137
138 if (keymap_ch != KEYMAP_UNKNOWN)
139 ch = keymap_ch;
140 }
141
142 if (ch == iKeyQuit || ch == 'z' || ch == iKeyAbort) {
144 return FALSE;
145 if (prompt_yn(_(txt_read_abort), FALSE) == 1)
146 return TRUE;
147 }
148
149 if (ch == iKeyQuitTin) {
151 return FALSE;
152 if (prompt_yn(_(txt_read_exit), FALSE) == 1)
153 tin_done(EXIT_SUCCESS, NULL);
154 }
155
156 }
157
158# if 0
159 /*
160 * Our file has something for us to read
161 */
162 if (FD_ISSET(fileno(NEED_NNTP_FD_HERE), &readfds))
163 return TRUE;
164# endif /* 0 */
165 }
166
167 }
168}
169#endif /* HAVE_SELECT */
170
171
172/*
173 * Support routine to read a fixed size buffer. This does most of the
174 * hard work for tin_fgets()
175 */
177
178
179static char *
181 char *buffer,
182 size_t len,
183 FILE *fp,
184 t_bool header)
185{
186 char *ptr;
187 int c;
188 int i;
189#ifdef NNTP_ABLE
190 t_bool check_dot_only_line;
191
192 /*
193 * We have to check '.' line when reading via NNTP and
194 * reading first line.
195 */
196 check_dot_only_line = (header && fp == FAKE_NNTP_FP && partial_read == FALSE);
197#endif /* NNTP_ABLE */
198
200
201#ifdef NNTP_ABLE
202# ifdef HAVE_SELECT
203 if (wait_for_input()) { /* Check if okay to read */
204 info_message(_("Aborting read, please wait..."));
205 drain_buffer(fp);
208 /* fflush(stdin); */
209 return NULL;
210 }
211# endif /* HAVE_SELECT */
212
213 errno = 0; /* To check errno after read, clear it here */
214
215 /*
216 * Initially try and fit into supplied buffer
217 */
218 if (fp == FAKE_NNTP_FP)
219 ptr = get_server(buffer, (int) len);
220 else
221 ptr = fgets(buffer, (int) len, fp);
222#else
223 errno = 0; /* To check errno after read, clear it here */
224
225 ptr = fgets(buffer, len, fp);
226#endif /* NNTP_ABLE */
227
228/* TODO: develop this next line? */
229#ifdef DEBUG
230 if (errno && (debug & DEBUG_MISC))
231 fprintf(stderr, "tin_read(%s)", strerror(errno));
232#endif /* DEBUG */
233
234 if (ptr == NULL) /* End of data? */
235 return NULL;
236
237 /*
238 * Was this only a partial read?
239 * We strip trailing \r and \n here and here _only_
240 * 'offset' is the # of chars which we read now
241 */
242 i = (int) strlen(buffer);
243 if (i >= 1 && buffer[i - 1] == '\n') {
244
245 if (i >= 2 && buffer[i - 2] == '\r') {
246 buffer[i - 2] = '\0';
247 offset = i -= 2;
248 } else {
249 buffer[i - 1] = '\0';
250 offset = --i;
251 }
252
253 /*
254 * If we're looking for continuation headers, mark this as a partial
255 * read and put back a newline. Unfolding (removing of this newline
256 * and whitespace, if necessary) must be done at a higher level --
257 * there are headers where whitespace is significant even in folded
258 * lines.
259 */
260#ifdef NNTP_ABLE
261 if (check_dot_only_line && i == 1 && buffer[0] == '.') { /* EMPTY */
262 /* Find a terminator, don't check next line. */
263 } else
264#endif /* NNTP_ABLE */
265 {
266 if (header) {
267 if (!i) { /* EMPTY */
268 /* Find a header separator, don't check next line. */
269 } else {
270 if ((c = fgetc(get_nntp_fp(fp))) == ' ' || c == '\t') {
272 /* This is safe because we removed at least one char above */
273 buffer[offset++] = '\n';
274 buffer[offset] = '\0';
275 }
276
277 /* Push back the 1st char of next line */
278 if (c != EOF)
279 ungetc(c, get_nntp_fp(fp));
280 }
281 }
282 }
283 } else {
285 offset = i;
286 }
287
288 return buffer;
289}
290
291
292/*
293 * This is the main routine for reading news data from local spool or NNTP.
294 * It can handle arbitrary length lines of data, failed connections and
295 * user initiated aborts (where possible)
296 *
297 * We simply request data from an fd and data is read up to the next \n
298 * Any trailing \r and \n will be stripped.
299 * If fp is FAKE_NNTP_FP, then we are reading via a socket to an NNTP
300 * server. The required post-processing of the data will be done such that
301 * we look like a local read to the calling function.
302 *
303 * Header lines: If header is TRUE, then we assume we're reading a news
304 * article header. In some cases, article headers are split over multiple
305 * lines. The rule is that if the next line starts with \t or ' ', then it
306 * will be included as part of the current line. Line breaks are NOT
307 * stripped (but replaced by \n) in continuated lines except the trailing
308 * one; unfolding MUST be done at a higher level because it may be
309 * significant or not.
310 *
311 * Dynamic read code based on code by <emcmanus@gr.osf.org>
312 *
313 * Caveat: We try to keep the code path for a trivial read as short as
314 * possible.
315 */
316char *
318 FILE *fp,
319 t_bool header)
320{
321 static char *dynbuf = NULL;
322 static int size = 0;
323
324 int next;
325
326 tin_errno = 0; /* Clear errors */
328
329#if 1
330 if (fp == NULL) {
331 FreeAndNull(dynbuf);
332 return NULL;
333 }
334 /* Allocate initial buffer */
335 if (dynbuf == NULL) {
336 dynbuf = my_malloc(INIT * sizeof(*dynbuf));
337 size = INIT;
338 }
339 /* Otherwise reuse last buffer */
340 /* TODO: Should we free too large buffer? */
341#else
342 FreeIfNeeded(dynbuf); /* Free any previous allocation */
343 dynbuf = my_malloc(INIT * sizeof(*dynbuf));
344 size = INIT;
345#endif /* 1 */
346
347 if (tin_read(dynbuf, (size_t) size, fp, header) == NULL)
348 return NULL;
349
350 if (tin_errno != 0) {
351 DEBUG_IO((stderr, _("Aborted read\n")));
352 return NULL;
353 }
354
355 next = offset;
356
357 while (partial_read) {
358 if (next + RCHUNK > size)
359 size = next + RCHUNK;
360 dynbuf = my_realloc(dynbuf, (size_t) size * sizeof(*dynbuf));
361 (void) tin_read(dynbuf + next, (size_t) (size - next), fp, header); /* What if == NULL? */
362 next += offset;
363
364 if (tin_errno != 0)
365 return NULL;
366 }
367
368 /*
369 * Do processing of leading . for NNTP
370 * This only occurs at the start of lines
371 * At this point, dynbuf won't be NULL
372 */
373#ifdef NNTP_ABLE
374 if (fp == FAKE_NNTP_FP) {
375 if (dynbuf[0] == '.') { /* reduce leading .'s */
376 if (dynbuf[1] == '\0') {
377 DEBUG_IO((stderr, "tin_fgets(NULL)\n"));
378 return NULL;
379 }
380 DEBUG_IO((stderr, "tin_fgets(%s)\n", dynbuf + 1));
381 return (dynbuf + 1);
382 }
383 }
384#endif /* NNTP_ABLE */
385
386DEBUG_IO((stderr, "tin_fgets(%s)\n", (dynbuf) ? dynbuf : "NULL"));
387
388 return dynbuf;
389}
390
391
392/*
393 * We can't just stop reading a socket once we are through with it. This
394 * drains out any pending data on the NNTP port
395 */
396#ifdef NNTP_ABLE
397void
398drain_buffer(
399 FILE *fp)
400{
401 int i = 0;
402
403 if (fp != FAKE_NNTP_FP)
404 return;
405
406DEBUG_IO((stderr, _("Draining\n")));
407 while (tin_fgets(fp, FALSE) != NULL) {
408 if (++i % MODULO_COUNT_NUM == 0)
409 spin_cursor();
410 }
411}
412#endif /* NNTP_ABLE */
413
414/* end of read.c */
unsigned t_bool
Definition: bool.h:77
#define TRUE
Definition: bool.h:74
#define FALSE
Definition: bool.h:70
#define DEBUG_MISC
Definition: debug.h:54
#define DEBUG_IO(x)
Definition: debug.h:62
t_bool post_article_and_exit
Definition: init.c:146
constext txt_read_abort[]
Definition: lang.c:770
char * tin_progname
Definition: init.c:105
unsigned short debug
Definition: debug.c:51
constext txt_read_exit[]
Definition: lang.c:771
#define iKeyQuit
Definition: keymap.h:145
#define iKeyQuitTin
Definition: keymap.h:144
#define iKeyAbort
Definition: keymap.h:143
int errno
int ReadCh(void)
Definition: curses.c:1110
_Noreturn void tin_done(int ret, const char *fmt,...)
Definition: misc.c:562
void perror_message(const char *fmt,...)
Definition: screen.c:260
void info_message(const char *fmt,...)
Definition: screen.c:102
void spin_cursor(void)
Definition: screen.c:497
void clear_message(void)
Definition: screen.c:283
FILE * get_nntp_fp(FILE *fp)
Definition: nntplib.c:79
int prompt_yn(const char *prompt, t_bool default_answer)
Definition: prompt.c:165
#define strerror(n)
Definition: proto.h:700
_Noreturn void giveup(void)
Definition: main.c:1058
int get_arrow_key(int prech)
Definition: curses.c:961
int tin_errno
Definition: read.c:59
static char * tin_read(char *buffer, size_t len, FILE *fp, t_bool header)
Definition: read.c:180
#define RCHUNK
Definition: read.c:54
char * tin_fgets(FILE *fp, t_bool header)
Definition: read.c:317
#define INIT
Definition: read.c:53
static t_bool partial_read
Definition: read.c:176
static int offset
Definition: read.c:62
#define MODULO_COUNT_NUM
Definition: tin.h:868
#define KEYMAP_UNKNOWN
Definition: tin.h:1076
#define EOF
Definition: tin.h:2516
#define my_malloc(size)
Definition: tin.h:2245
#define EXIT_SUCCESS
Definition: tin.h:1298
#define TIN_ABORT
Definition: tin.h:956
#define FreeIfNeeded(p)
Definition: tin.h:2252
#define _(Text)
Definition: tin.h:94
#define forever
Definition: tin.h:816
#define FreeAndNull(p)
Definition: tin.h:2253
#define my_realloc(ptr, size)
Definition: tin.h:2247
#define STDIN_FILENO
Definition: tin.h:394
#define FAKE_NNTP_FP
Definition: tin.h:458