1 /* Parses and converts NNTP responses to enum values and cache entry HTML */
2 
3 #ifdef HAVE_CONFIG_H
4 #include <config.h>
5 #endif
6 
7 #include <errno.h>
8 #include <stdlib.h>
9 #include <string.h>
10 
11 #include "elinks.h"
12 
13 #include "cache/cache.h"
14 #include "intl/gettext/libintl.h"
15 #include "mime/backend/common.h"
16 #include "network/connection.h"
17 #include "network/socket.h"
18 #include "protocol/header.h"
19 #include "protocol/nntp/codes.h"
20 #include "protocol/nntp/connection.h"
21 #include "protocol/nntp/nntp.h"
22 #include "protocol/nntp/response.h"
23 #include "protocol/protocol.h"
24 #include "protocol/uri.h"
25 #include "util/conv.h"
26 #include "util/memory.h"
27 #include "util/string.h"
28 
29 
30 /* Search for line ending \r\n pair */
31 static unsigned char *
get_nntp_line_end(unsigned char * data,int datalen)32 get_nntp_line_end(unsigned char *data, int datalen)
33 {
34 	for (; datalen > 1; data++, datalen--)
35 		if (data[0] == ASCII_CR && data[1] == ASCII_LF)
36 			return data + 2;
37 
38 	return NULL;
39 }
40 
41 /* RFC 977 - Section 2.4.1.  Text Responses:
42  *
43  * A single line containing only a period (.) is sent to indicate the end of
44  * the text (i.e., the server will send a CR-LF pair at the end of the last
45  * line of text, a period, and another CR-LF pair).
46  *
47  * If the text contained a period as the first character of the text line in
48  * the original, that first period is doubled.  Therefore, the client must
49  * examine the first character of each line received, and for those beginning
50  * with a period, determine either that this is the end of the text or whether
51  * to collapse the doubled period to a single one. */
52 /* Returns NULL if end-of-text is found else start of collapsed line */
53 static inline unsigned char *
check_nntp_line(unsigned char * line,unsigned char * end)54 check_nntp_line(unsigned char *line, unsigned char *end)
55 {
56 	assert(line < end);
57 
58 	/* Just to be safe NUL terminate the line */
59 	end[-2] = 0;
60 
61 	if (line[0] != '.') return line;
62 
63 	if (!line[1]) return NULL;
64 	if (line[1] == '.') line++;
65 
66 	return line;
67 }
68 
69 static inline unsigned char *
get_nntp_message_header_end(unsigned char * data,int datalen)70 get_nntp_message_header_end(unsigned char *data, int datalen)
71 {
72 	unsigned char *end, *prev_end = data;
73 
74 	while ((end = get_nntp_line_end(data, datalen))) {
75 		datalen	-= end - data;
76 		data	 = end;
77 
78 		/* If only \r\n is there */
79 		if (prev_end + 2 == end) {
80 			/* NUL terminate the header so that it ends with just
81 			 * one \r\n usefull for appending to cached->head. */
82 			end[-2] = 0;
83 			return end;
84 		}
85 
86 		prev_end = end;
87 	}
88 
89 	return NULL;
90 }
91 
92 static enum connection_state
init_nntp_header(struct connection * conn,struct read_buffer * rb)93 init_nntp_header(struct connection *conn, struct read_buffer *rb)
94 {
95 	struct nntp_connection_info *nntp = conn->info;
96 
97 	if (!conn->cached) {
98 		conn->cached = get_cache_entry(conn->uri);
99 		if (!conn->cached) return S_OUT_OF_MEM;
100 
101 	} else if (conn->cached->head || conn->cached->content_type) {
102 		/* If the head is set wipe out the content to be sure */
103 		delete_entry_content(conn->cached);
104 		mem_free_set(&conn->cached->head, NULL);
105 	}
106 
107 	/* XXX: Override any Content-Type line in the header */
108 	mem_free_set(&conn->cached->content_type, stracpy("text/html"));
109 	if (!conn->cached->content_type)
110 		return S_OUT_OF_MEM;
111 
112 	switch (nntp->target) {
113 	case NNTP_TARGET_ARTICLE_NUMBER:
114 	case NNTP_TARGET_MESSAGE_ID:
115 	case NNTP_TARGET_GROUP_MESSAGE_ID:
116 	{
117 		unsigned char *end;
118 
119 		end = get_nntp_message_header_end(rb->data, rb->length);
120 		if (!end) {
121 			/* Redo the whole cache entry thing next time */
122 			return S_TRANS;
123 		}
124 
125 		/* FIXME: Add the NNTP response code line */
126 		conn->cached->head = stracpy("FIXME NNTP response code\r\n");
127 		if (!conn->cached->head) return S_OUT_OF_MEM;
128 
129 		add_to_strn(&conn->cached->head, rb->data);
130 
131 		/* ... and remove it */
132 		conn->received += end - rb->data;
133 		kill_buffer_data(rb, end - rb->data);
134 		break;
135 	}
136 	case NNTP_TARGET_ARTICLE_RANGE:
137 	case NNTP_TARGET_GROUP:
138 	case NNTP_TARGET_GROUPS:
139 	case NNTP_TARGET_QUIT:
140 		break;
141 	}
142 
143 	return S_OK;
144 }
145 
146 
147 static unsigned char *
get_nntp_title(struct connection * conn)148 get_nntp_title(struct connection *conn)
149 {
150 	struct nntp_connection_info *nntp = conn->info;
151 	struct string title;
152 
153 	if (!init_string(&title))
154 		return NULL;
155 
156 	switch (nntp->target) {
157 	case NNTP_TARGET_ARTICLE_RANGE:
158 		add_format_to_string(&title, "Articles in the range %ld to %ld",
159 				     nntp->current_article, nntp->end_article);
160 		break;
161 
162 	case NNTP_TARGET_ARTICLE_NUMBER:
163 	case NNTP_TARGET_MESSAGE_ID:
164 	case NNTP_TARGET_GROUP_MESSAGE_ID:
165 	{
166 		unsigned char *subject;
167 
168 		subject = parse_header(conn->cached->head, "Subject", NULL);
169 		if (subject) {
170 			add_to_string(&title, subject);
171 			mem_free(subject);
172 			break;
173 		}
174 
175 		add_format_to_string(&title, "Article "),
176 		add_string_to_string(&title, &nntp->message);
177 
178 		if (nntp->target == NNTP_TARGET_MESSAGE_ID)
179 			break;
180 
181 		add_format_to_string(&title, " in ");
182 		add_string_to_string(&title, &nntp->group);
183 		break;
184 	}
185 	case NNTP_TARGET_GROUP:
186 		add_format_to_string(&title, "Articles in "),
187 		add_string_to_string(&title, &nntp->group);
188 		break;
189 
190 	case NNTP_TARGET_GROUPS:
191 		add_format_to_string(&title, "Newsgroups on "),
192 		add_uri_to_string(&title, conn->uri, URI_PUBLIC);
193 		break;
194 
195 	case NNTP_TARGET_QUIT:
196 		break;
197 	}
198 
199 	return title.source;
200 }
201 
202 static void
add_nntp_html_start(struct string * html,struct connection * conn)203 add_nntp_html_start(struct string *html, struct connection *conn)
204 {
205 	struct nntp_connection_info *nntp = conn->info;
206 	unsigned char *title = get_nntp_title(conn);
207 
208 	add_format_to_string(html,
209 		"<html>\n"
210 		"<head><title>%s</title></head>\n"
211 		"<body>\n",
212 		empty_string_or_(title));
213 
214 	switch (nntp->target) {
215 	case NNTP_TARGET_ARTICLE_NUMBER:
216 	case NNTP_TARGET_MESSAGE_ID:
217 	case NNTP_TARGET_GROUP_MESSAGE_ID:
218 	{
219 		unsigned char *header_entries;
220 
221 		header_entries = get_nntp_header_entries();
222 		if (!*header_entries) break;
223 
224 		add_to_string(html, "<pre>");
225 
226 		while (*header_entries) {
227 			unsigned char *entry, *value;
228 
229 			entry = get_next_path_filename(&header_entries, ',');
230 			if (!entry) continue;
231 
232 			value = parse_header(conn->cached->head, entry, NULL);
233 			if (!value) {
234 				mem_free(entry);
235 				continue;
236 			}
237 
238 			add_format_to_string(html, "<b>%s</b>: %s\n", entry, value);
239 			mem_free(value);
240 			mem_free(entry);
241 		}
242 
243 		add_to_string(html, "<hr />");
244 		break;
245 	}
246 	case NNTP_TARGET_ARTICLE_RANGE:
247 	case NNTP_TARGET_GROUP:
248 	case NNTP_TARGET_GROUPS:
249 		add_format_to_string(html,
250 			"<h2>%s</h2>\n"
251 			"<hr />\n"
252 			"<dl>",
253 			empty_string_or_(title));
254 		break;
255 
256 	case NNTP_TARGET_QUIT:
257 		break;
258 	}
259 
260 	mem_free_if(title);
261 }
262 
263 static void
add_nntp_html_end(struct string * html,struct connection * conn)264 add_nntp_html_end(struct string *html, struct connection *conn)
265 {
266 	struct nntp_connection_info *nntp = conn->info;
267 
268 	switch (nntp->target) {
269 	case NNTP_TARGET_ARTICLE_NUMBER:
270 	case NNTP_TARGET_MESSAGE_ID:
271 	case NNTP_TARGET_GROUP_MESSAGE_ID:
272 		add_to_string(html, "</pre>");
273 		break;
274 
275 	case NNTP_TARGET_ARTICLE_RANGE:
276 	case NNTP_TARGET_GROUP:
277 	case NNTP_TARGET_GROUPS:
278 		add_to_string(html, "</dl>");
279 		break;
280 
281 	case NNTP_TARGET_QUIT:
282 		break;
283 	}
284 
285 	add_to_string(html, "\n<hr />\n</body>\n</html>");
286 }
287 
288 static void
add_nntp_html_line(struct string * html,struct connection * conn,unsigned char * line)289 add_nntp_html_line(struct string *html, struct connection *conn,
290 		   unsigned char *line)
291 {
292 	struct nntp_connection_info *nntp = conn->info;
293 
294 	switch (nntp->target) {
295 	case NNTP_TARGET_ARTICLE_NUMBER:
296 	case NNTP_TARGET_MESSAGE_ID:
297 	case NNTP_TARGET_GROUP_MESSAGE_ID:
298 		add_html_to_string(html, line, strlen(line));
299 		break;
300 
301 	case NNTP_TARGET_ARTICLE_RANGE:
302 	case NNTP_TARGET_GROUP:
303 	case NNTP_TARGET_GROUPS:
304 	{
305 		unsigned char *desc = strchr(line, '\t');
306 
307 		if (desc) {
308 			*desc++ = 0;
309 		} else {
310 			desc = "";
311 		}
312 
313 		add_format_to_string(html, "<dt><a href=\"%s/%s\">%s</a></dt><dd>%s</dd>\n",
314 			struri(conn->uri), line, line, desc);
315 		break;
316 	}
317 	case NNTP_TARGET_QUIT:
318 		break;
319 	}
320 
321 	add_char_to_string(html, '\n');
322 }
323 
324 enum connection_state
read_nntp_response_data(struct connection * conn,struct read_buffer * rb)325 read_nntp_response_data(struct connection *conn, struct read_buffer *rb)
326 {
327 	struct string html;
328 	unsigned char *end;
329 	enum connection_state state = S_TRANS;
330 
331 	if (conn->from == 0) {
332 		switch (init_nntp_header(conn, rb)) {
333 		case S_OK:
334 			break;
335 
336 		case S_OUT_OF_MEM:
337 			return S_OUT_OF_MEM;
338 
339 		case S_TRANS:
340 			return S_TRANS;
341 
342 		default:
343 			return S_NNTP_ERROR;
344 		}
345 	}
346 
347 	if (!init_string(&html))
348 		return S_OUT_OF_MEM;
349 
350 	if (conn->from == 0)
351 		add_nntp_html_start(&html, conn);
352 
353 	while ((end = get_nntp_line_end(rb->data, rb->length))) {
354 		unsigned char *line = check_nntp_line(rb->data, end);
355 
356 		if (!line) {
357 			state = S_OK;
358 			break;
359 		}
360 
361 		add_nntp_html_line(&html, conn, line);
362 
363 		conn->received += end - rb->data;
364 		kill_buffer_data(rb, end - rb->data);
365 	}
366 
367 	if (state != S_TRANS)
368 		add_nntp_html_end(&html, conn);
369 
370 	add_fragment(conn->cached, conn->from, html.source, html.length);
371 
372 	conn->from += html.length;
373 	done_string(&html);
374 
375 	return state;
376 }
377 
378 /* Interpret response code parameters for code 211 - after GROUP command */
379 /* The syntax is: 211 <articles> <first-article> <last-article> <name> */
380 /* Returns 1 on success and 0 on failure */
381 static int
parse_nntp_group_parameters(struct nntp_connection_info * nntp,unsigned char * pos,unsigned char * end)382 parse_nntp_group_parameters(struct nntp_connection_info *nntp,
383 			    unsigned char *pos, unsigned char *end)
384 {
385 	errno = 0;
386 
387 	/* Get <articles> */
388 	while (pos < end && !isdigit(*pos))
389 		pos++;
390 
391 	nntp->articles = strtol(pos, (char **) &pos, 10);
392 	if (errno || pos >= end || nntp->articles < 0)
393 		return 0;
394 
395 	if (nntp->target == NNTP_TARGET_ARTICLE_RANGE)
396 		return 1;
397 
398 	/* Get <first-article> */
399 	while (pos < end && !isdigit(*pos))
400 		pos++;
401 
402 	nntp->current_article = strtol(pos, (char **) &pos, 10);
403 	if (errno || pos >= end || nntp->current_article < 0)
404 		return 0;
405 
406 	/* Get <last-article> */
407 	while (pos < end && !isdigit(*pos))
408 		pos++;
409 
410 	nntp->end_article = strtol(pos, (char **) &pos, 10);
411 	if (errno || pos >= end || nntp->end_article < 0)
412 		return 0;
413 
414 	return 1;
415 }
416 
417 enum nntp_code
get_nntp_response_code(struct connection * conn,struct read_buffer * rb)418 get_nntp_response_code(struct connection *conn, struct read_buffer *rb)
419 {
420 	struct nntp_connection_info *nntp = conn->info;
421 	unsigned char *line = rb->data;
422 	unsigned char *end = get_nntp_line_end(rb->data, rb->length);
423 	enum nntp_code code;
424 	int linelen;
425 
426 	if (!end) return NNTP_CODE_NONE;
427 
428 	/* Just to be safe NUL terminate the line */
429 	end[-1] = 0;
430 
431 	linelen = end - line;
432 
433 	if (linelen < sizeof("xxx\r\n") - 1
434 	    || !isdigit(line[0])
435 	    || !isdigit(line[1])
436 	    || !isdigit(line[2])
437 	    ||  isdigit(line[3]))
438 		return NNTP_CODE_INVALID;
439 
440 	code = atoi(line);
441 
442 	if (!check_nntp_code_valid(code))
443 		return NNTP_CODE_INVALID;
444 
445 	/* Only when listing all articles in group the parameters is needed */
446 	if (code == NNTP_CODE_211_GROUP_SELECTED
447 	    && nntp->target == NNTP_TARGET_GROUP
448 	    && !parse_nntp_group_parameters(nntp, line + 4, end))
449 		return NNTP_CODE_INVALID;
450 
451 	/* Remove the response line */
452 	kill_buffer_data(rb, linelen);
453 
454 	conn->received += linelen;
455 
456 	return code;
457 }
458