iconv: Bail out of the loop when an illegal sequence of bytes occurs.
[elinks/elinks-j605.git] / src / protocol / nntp / response.c
blob2448b066bfbfbacb62572e214161cfd933d4e531
1 /* Parses and converts NNTP responses to enum values and cache entry HTML */
3 #ifdef HAVE_CONFIG_H
4 #include <config.h>
5 #endif
7 #include <errno.h>
8 #include <stdlib.h>
9 #include <string.h>
11 #include "elinks.h"
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"
30 /* Search for line ending \r\n pair */
31 static unsigned char *
32 get_nntp_line_end(unsigned char *data, int datalen)
34 for (; datalen > 1; data++, datalen--)
35 if (data[0] == ASCII_CR && data[1] == ASCII_LF)
36 return data + 2;
38 return NULL;
41 /* RFC 977 - Section 2.4.1. Text Responses:
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).
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 *
54 check_nntp_line(unsigned char *line, unsigned char *end)
56 assert(line < end);
58 /* Just to be safe NUL terminate the line */
59 end[-2] = 0;
61 if (line[0] != '.') return line;
63 if (!line[1]) return NULL;
64 if (line[1] == '.') line++;
66 return line;
69 static inline unsigned char *
70 get_nntp_message_header_end(unsigned char *data, int datalen)
72 unsigned char *end, *prev_end = data;
74 while ((end = get_nntp_line_end(data, datalen))) {
75 datalen -= end - data;
76 data = end;
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;
86 prev_end = end;
89 return NULL;
92 static struct connection_state
93 init_nntp_header(struct connection *conn, struct read_buffer *rb)
95 struct nntp_connection_info *nntp = conn->info;
97 if (!conn->cached) {
98 conn->cached = get_cache_entry(conn->uri);
99 if (!conn->cached) return connection_state(S_OUT_OF_MEM);
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);
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 connection_state(S_OUT_OF_MEM);
112 switch (nntp->target) {
113 case NNTP_TARGET_ARTICLE_NUMBER:
114 case NNTP_TARGET_MESSAGE_ID:
115 case NNTP_TARGET_GROUP_MESSAGE_ID:
117 unsigned char *end;
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 connection_state(S_TRANS);
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 connection_state(S_OUT_OF_MEM);
129 add_to_strn(&conn->cached->head, rb->data);
131 /* ... and remove it */
132 conn->received += end - rb->data;
133 kill_buffer_data(rb, end - rb->data);
134 break;
136 case NNTP_TARGET_ARTICLE_RANGE:
137 case NNTP_TARGET_GROUP:
138 case NNTP_TARGET_GROUPS:
139 case NNTP_TARGET_QUIT:
140 break;
143 return connection_state(S_OK);
147 static unsigned char *
148 get_nntp_title(struct connection *conn)
150 struct nntp_connection_info *nntp = conn->info;
151 struct string title;
153 if (!init_string(&title))
154 return NULL;
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;
162 case NNTP_TARGET_ARTICLE_NUMBER:
163 case NNTP_TARGET_MESSAGE_ID:
164 case NNTP_TARGET_GROUP_MESSAGE_ID:
166 unsigned char *subject;
168 subject = parse_header(conn->cached->head, "Subject", NULL);
169 if (subject) {
170 add_to_string(&title, subject);
171 mem_free(subject);
172 break;
175 add_format_to_string(&title, "Article "),
176 add_string_to_string(&title, &nntp->message);
178 if (nntp->target == NNTP_TARGET_MESSAGE_ID)
179 break;
181 add_format_to_string(&title, " in ");
182 add_string_to_string(&title, &nntp->group);
183 break;
185 case NNTP_TARGET_GROUP:
186 add_format_to_string(&title, "Articles in "),
187 add_string_to_string(&title, &nntp->group);
188 break;
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;
195 case NNTP_TARGET_QUIT:
196 break;
199 return title.source;
203 static void
204 decode_q_segment(struct string *str, unsigned char *in, unsigned char *end)
206 int c;
208 while ((c = *in++) != 0 && (in <= end)) {
209 if (c == '=') {
210 int d = *in++;
212 if (d == '\n' || !d)
213 break; /* drop trailing newline */
214 d = (unhx(d) << 4) | unhx(*in++);
215 add_format_to_string(str, "&#%d;", d);
216 continue;
219 if (c == '_') /* rfc2047 4.2 (2) */
220 c = 0x20;
221 add_char_to_string(str, c);
225 static void
226 decode_b_segment(struct string *str, unsigned char *in, unsigned char *end)
228 /* Decode in..ep, possibly in-place to ot */
229 int c, pos = 0, acc = 0;
231 while ((c = *in++) != 0 && (in <= end)) {
232 if (c == '+')
233 c = 62;
234 else if (c == '/')
235 c = 63;
236 else if ('A' <= c && c <= 'Z')
237 c -= 'A';
238 else if ('a' <= c && c <= 'z')
239 c -= 'a' - 26;
240 else if ('0' <= c && c <= '9')
241 c -= '0' - 52;
242 else if (c == '=') {
243 /* padding is almost like (c == 0), except we do
244 * not output NUL resulting only from it;
245 * for now we just trust the data.
247 c = 0;
249 else
250 continue; /* garbage */
252 switch (pos++) {
253 case 0:
254 acc = (c << 2);
255 break;
256 case 1:
257 add_format_to_string(str, "&#%d;", (acc | (c >> 4)));
258 acc = (c & 15) << 4;
259 break;
260 case 2:
261 add_format_to_string(str, "&#%d;", (acc | (c >> 2)));
262 acc = (c & 3) << 6;
263 break;
264 case 3:
265 add_format_to_string(str, "&#%d;", (acc | c));
266 acc = pos = 0;
267 break;
272 static void
273 add_header_to_string(struct string *str, unsigned char *header)
275 unsigned char *end;
277 while ((end = strstr(header, "=?")) != NULL) {
278 int encoding;
279 unsigned char *cp, *sp;
281 if (header != end) {
282 add_html_to_string(str, header, end - header);
283 header = end;
286 /* E.g.
287 * ep : "=?iso-2022-jp?B?GyR...?= foo"
288 * ep : "=?ISO-8859-1?Q?Foo=FCbar?= baz"
290 end += 2;
291 cp = strchr(end, '?');
292 if (!cp)
293 break;
295 for (sp = end; sp < cp; sp++)
296 /* charset */;
297 encoding = c_tolower(cp[1]);
299 if (!encoding || cp[2] != '?')
300 break;
301 cp += 3;
302 end = strstr(cp + 3, "?=");
303 if (!end)
304 break;
305 if (encoding == 'b')
306 decode_b_segment(str, cp, end);
307 else if (encoding == 'q')
308 decode_q_segment(str, cp, end);
309 else
310 break;
312 header = end + 2;
315 add_html_to_string(str, header, strlen(header));
319 static void
320 add_nntp_html_start(struct string *html, struct connection *conn)
322 struct nntp_connection_info *nntp = conn->info;
323 unsigned char *title = get_nntp_title(conn);
325 add_format_to_string(html,
326 "<html>\n"
327 "<head><title>%s</title></head>\n"
328 "<body>\n",
329 empty_string_or_(title));
331 switch (nntp->target) {
332 case NNTP_TARGET_ARTICLE_NUMBER:
333 case NNTP_TARGET_MESSAGE_ID:
334 case NNTP_TARGET_GROUP_MESSAGE_ID:
336 unsigned char *header_entries;
338 header_entries = get_nntp_header_entries();
339 if (!*header_entries) break;
341 add_to_string(html, "<pre>");
343 while (*header_entries) {
344 unsigned char *entry, *value;
346 entry = get_next_path_filename(&header_entries, ',');
347 if (!entry) continue;
349 value = parse_header(conn->cached->head, entry, NULL);
350 if (!value) {
351 mem_free(entry);
352 continue;
355 add_format_to_string(html, "<b>%s</b>: ", entry);
356 add_header_to_string(html, value);
357 add_char_to_string(html, '\n');
358 mem_free(value);
359 mem_free(entry);
362 add_to_string(html, "<hr />");
363 break;
365 case NNTP_TARGET_ARTICLE_RANGE:
366 case NNTP_TARGET_GROUP:
367 case NNTP_TARGET_GROUPS:
368 add_format_to_string(html,
369 "<h2>%s</h2>\n"
370 "<hr />\n"
371 "<ol>",
372 empty_string_or_(title));
373 break;
375 case NNTP_TARGET_QUIT:
376 break;
379 mem_free_if(title);
382 static void
383 add_nntp_html_end(struct string *html, struct connection *conn)
385 struct nntp_connection_info *nntp = conn->info;
387 switch (nntp->target) {
388 case NNTP_TARGET_ARTICLE_NUMBER:
389 case NNTP_TARGET_MESSAGE_ID:
390 case NNTP_TARGET_GROUP_MESSAGE_ID:
391 add_to_string(html, "</pre>");
392 break;
394 case NNTP_TARGET_ARTICLE_RANGE:
395 case NNTP_TARGET_GROUP:
396 case NNTP_TARGET_GROUPS:
397 add_to_string(html, "</ol>");
398 break;
400 case NNTP_TARGET_QUIT:
401 break;
404 add_to_string(html, "\n<hr />\n</body>\n</html>");
407 static void
408 add_nntp_html_line(struct string *html, struct connection *conn,
409 unsigned char *line)
411 struct nntp_connection_info *nntp = conn->info;
413 switch (nntp->target) {
414 case NNTP_TARGET_ARTICLE_NUMBER:
415 case NNTP_TARGET_MESSAGE_ID:
416 case NNTP_TARGET_GROUP_MESSAGE_ID:
417 add_html_to_string(html, line, strlen(line));
418 break;
420 case NNTP_TARGET_ARTICLE_RANGE:
421 case NNTP_TARGET_GROUP:
422 case NNTP_TARGET_GROUPS:
424 unsigned char *field = line;
426 line = strchr(line, '\t');
427 if (!line)
428 field = "";
429 else
430 *line++ = 0;
431 add_format_to_string(html, "<li value=\"%s\"><a href=\"%s/%s\">",
432 field, struri(conn->uri), field);
434 field = line;
435 line = strchr(line, '\t');
436 if (line)
437 *line++ = 0;
439 add_header_to_string(html, field);
440 add_to_string(html, "</a> ");
442 if (line) {
443 field = line;
444 line = strchr(line, '\t');
445 if (line)
446 *line++ = 0;
448 add_header_to_string(html, field);
450 add_to_string(html, "</li>");
451 break;
453 case NNTP_TARGET_QUIT:
454 break;
457 add_char_to_string(html, '\n');
460 struct connection_state
461 read_nntp_response_data(struct connection *conn, struct read_buffer *rb)
463 struct string html;
464 unsigned char *end;
465 struct connection_state state = connection_state(S_TRANS);
467 if (conn->from == 0) {
468 switch (init_nntp_header(conn, rb).basic) {
469 case S_OK:
470 break;
472 case S_OUT_OF_MEM:
473 return connection_state(S_OUT_OF_MEM);
475 case S_TRANS:
476 return connection_state(S_TRANS);
478 default:
479 return connection_state(S_NNTP_ERROR);
483 if (!init_string(&html))
484 return connection_state(S_OUT_OF_MEM);
486 if (conn->from == 0)
487 add_nntp_html_start(&html, conn);
489 while ((end = get_nntp_line_end(rb->data, rb->length))) {
490 unsigned char *line = check_nntp_line(rb->data, end);
492 if (!line) {
493 state = connection_state(S_OK);
494 break;
497 add_nntp_html_line(&html, conn, line);
499 conn->received += end - rb->data;
500 kill_buffer_data(rb, end - rb->data);
503 if (!is_in_state(state, S_TRANS))
504 add_nntp_html_end(&html, conn);
506 add_fragment(conn->cached, conn->from, html.source, html.length);
508 conn->from += html.length;
509 done_string(&html);
511 return state;
514 /* Interpret response code parameters for code 211 - after GROUP command */
515 /* The syntax is: 211 <articles> <first-article> <last-article> <name> */
516 /* Returns 1 on success and 0 on failure */
517 static int
518 parse_nntp_group_parameters(struct nntp_connection_info *nntp,
519 unsigned char *pos, unsigned char *end)
521 errno = 0;
523 /* Get <articles> */
524 while (pos < end && !isdigit(*pos))
525 pos++;
527 nntp->articles = strtol(pos, (char **) &pos, 10);
528 if (errno || pos >= end || nntp->articles < 0)
529 return 0;
531 if (nntp->target == NNTP_TARGET_ARTICLE_RANGE)
532 return 1;
534 /* Get <first-article> */
535 while (pos < end && !isdigit(*pos))
536 pos++;
538 nntp->current_article = strtol(pos, (char **) &pos, 10);
539 if (errno || pos >= end || nntp->current_article < 0)
540 return 0;
542 /* Get <last-article> */
543 while (pos < end && !isdigit(*pos))
544 pos++;
546 nntp->end_article = strtol(pos, (char **) &pos, 10);
547 if (errno || pos >= end || nntp->end_article < 0)
548 return 0;
550 return 1;
553 enum nntp_code
554 get_nntp_response_code(struct connection *conn, struct read_buffer *rb)
556 struct nntp_connection_info *nntp = conn->info;
557 unsigned char *line = rb->data;
558 unsigned char *end = get_nntp_line_end(rb->data, rb->length);
559 enum nntp_code code;
560 int linelen;
562 if (!end) return NNTP_CODE_NONE;
564 /* Just to be safe NUL terminate the line */
565 end[-1] = 0;
567 linelen = end - line;
569 if (linelen < sizeof("xxx\r\n") - 1
570 || !isdigit(line[0])
571 || !isdigit(line[1])
572 || !isdigit(line[2])
573 || isdigit(line[3]))
574 return NNTP_CODE_INVALID;
576 code = atoi(line);
578 if (!check_nntp_code_valid(code))
579 return NNTP_CODE_INVALID;
581 /* Only when listing all articles in group the parameters is needed */
582 if (code == NNTP_CODE_211_GROUP_SELECTED
583 && nntp->target == NNTP_TARGET_GROUP
584 && !parse_nntp_group_parameters(nntp, line + 4, end))
585 return NNTP_CODE_INVALID;
587 /* Remove the response line */
588 kill_buffer_data(rb, linelen);
590 conn->received += linelen;
592 return code;