1
0
mirror of https://github.com/rkd77/elinks.git synced 2025-02-02 15:09:23 -05:00
elinks/src/protocol/nntp/response.c
M. Vefa Bicakci 96b3093519 Patch 2: Modifications to the remaining parts of ELinks
[Forward ported to 0.12 from bug 1004 attachment 499.  --KON]
2008-11-01 22:20:25 +02:00

599 lines
13 KiB
C

/* Parses and converts NNTP responses to enum values and cache entry HTML */
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include "elinks.h"
#include "cache/cache.h"
#include "intl/gettext/libintl.h"
#include "mime/backend/common.h"
#include "network/connection.h"
#include "network/socket.h"
#include "protocol/header.h"
#include "protocol/nntp/codes.h"
#include "protocol/nntp/connection.h"
#include "protocol/nntp/nntp.h"
#include "protocol/nntp/response.h"
#include "protocol/protocol.h"
#include "protocol/uri.h"
#include "util/conv.h"
#include "util/memory.h"
#include "util/string.h"
/* Search for line ending \r\n pair */
static unsigned char *
get_nntp_line_end(unsigned char *data, int datalen)
{
for (; datalen > 1; data++, datalen--)
if (data[0] == ASCII_CR && data[1] == ASCII_LF)
return data + 2;
return NULL;
}
/* RFC 977 - Section 2.4.1. Text Responses:
*
* A single line containing only a period (.) is sent to indicate the end of
* the text (i.e., the server will send a CR-LF pair at the end of the last
* line of text, a period, and another CR-LF pair).
*
* If the text contained a period as the first character of the text line in
* the original, that first period is doubled. Therefore, the client must
* examine the first character of each line received, and for those beginning
* with a period, determine either that this is the end of the text or whether
* to collapse the doubled period to a single one. */
/* Returns NULL if end-of-text is found else start of collapsed line */
static inline unsigned char *
check_nntp_line(unsigned char *line, unsigned char *end)
{
assert(line < end);
/* Just to be safe NUL terminate the line */
end[-2] = 0;
if (line[0] != '.') return line;
if (!line[1]) return NULL;
if (line[1] == '.') line++;
return line;
}
static inline unsigned char *
get_nntp_message_header_end(unsigned char *data, int datalen)
{
unsigned char *end, *prev_end = data;
while ((end = get_nntp_line_end(data, datalen))) {
datalen -= end - data;
data = end;
/* If only \r\n is there */
if (prev_end + 2 == end) {
/* NUL terminate the header so that it ends with just
* one \r\n usefull for appending to cached->head. */
end[-2] = 0;
return end;
}
prev_end = end;
}
return NULL;
}
static struct connection_state
init_nntp_header(struct connection *conn, struct read_buffer *rb)
{
struct nntp_connection_info *nntp = conn->info;
if (!conn->cached) {
conn->cached = get_cache_entry(conn->uri);
if (!conn->cached) return connection_state(S_OUT_OF_MEM);
} else if (conn->cached->head || conn->cached->content_type) {
/* If the head is set wipe out the content to be sure */
delete_entry_content(conn->cached);
mem_free_set(&conn->cached->head, NULL);
}
/* XXX: Override any Content-Type line in the header */
mem_free_set(&conn->cached->content_type, stracpy("text/html"));
if (!conn->cached->content_type)
return connection_state(S_OUT_OF_MEM);
switch (nntp->target) {
case NNTP_TARGET_ARTICLE_NUMBER:
case NNTP_TARGET_MESSAGE_ID:
case NNTP_TARGET_GROUP_MESSAGE_ID:
{
unsigned char *end;
end = get_nntp_message_header_end(rb->data, rb->length);
if (!end) {
/* Redo the whole cache entry thing next time */
return connection_state(S_TRANS);
}
/* FIXME: Add the NNTP response code line */
conn->cached->head = stracpy("FIXME NNTP response code\r\n");
if (!conn->cached->head) return connection_state(S_OUT_OF_MEM);
add_to_strn(&conn->cached->head, rb->data);
/* ... and remove it */
conn->received += end - rb->data;
kill_buffer_data(rb, end - rb->data);
break;
}
case NNTP_TARGET_ARTICLE_RANGE:
case NNTP_TARGET_GROUP:
case NNTP_TARGET_GROUPS:
case NNTP_TARGET_QUIT:
break;
}
return connection_state(S_OK);
}
static unsigned char *
get_nntp_title(struct connection *conn)
{
struct nntp_connection_info *nntp = conn->info;
struct string title;
if (!init_string(&title))
return NULL;
switch (nntp->target) {
case NNTP_TARGET_ARTICLE_RANGE:
add_format_to_string(&title, "Articles in the range %ld to %ld",
nntp->current_article, nntp->end_article);
break;
case NNTP_TARGET_ARTICLE_NUMBER:
case NNTP_TARGET_MESSAGE_ID:
case NNTP_TARGET_GROUP_MESSAGE_ID:
{
unsigned char *subject;
subject = parse_header(conn->cached->head, "Subject", NULL);
if (subject) {
add_to_string(&title, subject);
mem_free(subject);
break;
}
add_format_to_string(&title, "Article "),
add_string_to_string(&title, &nntp->message);
if (nntp->target == NNTP_TARGET_MESSAGE_ID)
break;
add_format_to_string(&title, " in ");
add_string_to_string(&title, &nntp->group);
break;
}
case NNTP_TARGET_GROUP:
add_format_to_string(&title, "Articles in "),
add_string_to_string(&title, &nntp->group);
break;
case NNTP_TARGET_GROUPS:
add_format_to_string(&title, "Newsgroups on "),
add_uri_to_string(&title, conn->uri, URI_PUBLIC);
break;
case NNTP_TARGET_QUIT:
break;
}
return title.source;
}
static void
decode_q_segment(struct string *str, unsigned char *in, unsigned char *end)
{
int c;
while ((c = *in++) != 0 && (in <= end)) {
if (c == '=') {
int d = *in++;
if (d == '\n' || !d)
break; /* drop trailing newline */
d = (unhx(d) << 4) | unhx(*in++);
add_format_to_string(str, "&#%d;", d);
continue;
}
if (c == '_') /* rfc2047 4.2 (2) */
c = 0x20;
add_char_to_string(str, c);
}
}
static void
decode_b_segment(struct string *str, unsigned char *in, unsigned char *end)
{
/* Decode in..ep, possibly in-place to ot */
int c, pos = 0, acc = 0;
while ((c = *in++) != 0 && (in <= end)) {
if (c == '+')
c = 62;
else if (c == '/')
c = 63;
else if ('A' <= c && c <= 'Z')
c -= 'A';
else if ('a' <= c && c <= 'z')
c -= 'a' - 26;
else if ('0' <= c && c <= '9')
c -= '0' - 52;
else if (c == '=') {
/* padding is almost like (c == 0), except we do
* not output NUL resulting only from it;
* for now we just trust the data.
*/
c = 0;
}
else
continue; /* garbage */
switch (pos++) {
case 0:
acc = (c << 2);
break;
case 1:
add_format_to_string(str, "&#%d;", (acc | (c >> 4)));
acc = (c & 15) << 4;
break;
case 2:
add_format_to_string(str, "&#%d;", (acc | (c >> 2)));
acc = (c & 3) << 6;
break;
case 3:
add_format_to_string(str, "&#%d;", (acc | c));
acc = pos = 0;
break;
}
}
}
static void
add_header_to_string(struct string *str, unsigned char *header)
{
unsigned char *end;
int rfc2047 = 0;
while ((end = strstr(header, "=?")) != NULL) {
int encoding;
unsigned char charset_q[256];
unsigned char *cp, *sp;
rfc2047 = 1;
if (header != end) {
add_html_to_string(str, header, end - header);
header = end;
}
/* E.g.
* ep : "=?iso-2022-jp?B?GyR...?= foo"
* ep : "=?ISO-8859-1?Q?Foo=FCbar?= baz"
*/
end += 2;
cp = strchr(end, '?');
if (!cp)
break;
for (sp = end; sp < cp; sp++)
charset_q[sp - end] = c_tolower(*sp);
charset_q[cp - end] = 0;
encoding = c_tolower(cp[1]);
if (!encoding || cp[2] != '?')
break;
cp += 3;
end = strstr(cp + 3, "?=");
if (!end)
break;
if (encoding == 'b')
decode_b_segment(str, cp, end);
else if (encoding == 'q')
decode_q_segment(str, cp, end);
else
break;
header = end + 2;
}
add_html_to_string(str, header, strlen(header));
}
static void
add_nntp_html_start(struct string *html, struct connection *conn)
{
struct nntp_connection_info *nntp = conn->info;
unsigned char *title = get_nntp_title(conn);
add_format_to_string(html,
"<html>\n"
"<head><title>%s</title></head>\n"
"<body>\n",
empty_string_or_(title));
switch (nntp->target) {
case NNTP_TARGET_ARTICLE_NUMBER:
case NNTP_TARGET_MESSAGE_ID:
case NNTP_TARGET_GROUP_MESSAGE_ID:
{
unsigned char *header_entries;
header_entries = get_nntp_header_entries();
if (!*header_entries) break;
add_to_string(html, "<pre>");
while (*header_entries) {
unsigned char *entry, *value;
entry = get_next_path_filename(&header_entries, ',');
if (!entry) continue;
value = parse_header(conn->cached->head, entry, NULL);
if (!value) {
mem_free(entry);
continue;
}
add_format_to_string(html, "<b>%s</b>: ", entry);
add_header_to_string(html, value);
add_char_to_string(html, '\n');
mem_free(value);
mem_free(entry);
}
add_to_string(html, "<hr />");
break;
}
case NNTP_TARGET_ARTICLE_RANGE:
case NNTP_TARGET_GROUP:
case NNTP_TARGET_GROUPS:
add_format_to_string(html,
"<h2>%s</h2>\n"
"<hr />\n"
"<ol>",
empty_string_or_(title));
break;
case NNTP_TARGET_QUIT:
break;
}
mem_free_if(title);
}
static void
add_nntp_html_end(struct string *html, struct connection *conn)
{
struct nntp_connection_info *nntp = conn->info;
switch (nntp->target) {
case NNTP_TARGET_ARTICLE_NUMBER:
case NNTP_TARGET_MESSAGE_ID:
case NNTP_TARGET_GROUP_MESSAGE_ID:
add_to_string(html, "</pre>");
break;
case NNTP_TARGET_ARTICLE_RANGE:
case NNTP_TARGET_GROUP:
case NNTP_TARGET_GROUPS:
add_to_string(html, "</ol>");
break;
case NNTP_TARGET_QUIT:
break;
}
add_to_string(html, "\n<hr />\n</body>\n</html>");
}
static void
add_nntp_html_line(struct string *html, struct connection *conn,
unsigned char *line)
{
struct nntp_connection_info *nntp = conn->info;
switch (nntp->target) {
case NNTP_TARGET_ARTICLE_NUMBER:
case NNTP_TARGET_MESSAGE_ID:
case NNTP_TARGET_GROUP_MESSAGE_ID:
add_html_to_string(html, line, strlen(line));
break;
case NNTP_TARGET_ARTICLE_RANGE:
case NNTP_TARGET_GROUP:
case NNTP_TARGET_GROUPS:
{
unsigned char *field = line;
line = strchr(line, '\t');
if (!line)
field = "";
else
*line++ = 0;
add_format_to_string(html, "<li value=\"%s\"><a href=\"%s/%s\">",
field, struri(conn->uri), field);
field = line;
line = strchr(line, '\t');
if (line)
*line++ = 0;
add_header_to_string(html, field);
add_to_string(html, "</a> ");
if (line) {
field = line;
line = strchr(line, '\t');
if (line)
*line++ = 0;
add_header_to_string(html, field);
}
add_to_string(html, "</li>");
break;
}
case NNTP_TARGET_QUIT:
break;
}
add_char_to_string(html, '\n');
}
struct connection_state
read_nntp_response_data(struct connection *conn, struct read_buffer *rb)
{
struct string html;
unsigned char *end;
struct connection_state state = connection_state(S_TRANS);
if (conn->from == 0) {
switch (init_nntp_header(conn, rb).basic) {
case S_OK:
break;
case S_OUT_OF_MEM:
return connection_state(S_OUT_OF_MEM);
case S_TRANS:
return connection_state(S_TRANS);
default:
return connection_state(S_NNTP_ERROR);
}
}
if (!init_string(&html))
return connection_state(S_OUT_OF_MEM);
if (conn->from == 0)
add_nntp_html_start(&html, conn);
while ((end = get_nntp_line_end(rb->data, rb->length))) {
unsigned char *line = check_nntp_line(rb->data, end);
if (!line) {
state = connection_state(S_OK);
break;
}
add_nntp_html_line(&html, conn, line);
conn->received += end - rb->data;
kill_buffer_data(rb, end - rb->data);
}
if (!is_in_state(state, S_TRANS))
add_nntp_html_end(&html, conn);
add_fragment(conn->cached, conn->from, html.source, html.length);
conn->from += html.length;
done_string(&html);
return state;
}
/* Interpret response code parameters for code 211 - after GROUP command */
/* The syntax is: 211 <articles> <first-article> <last-article> <name> */
/* Returns 1 on success and 0 on failure */
static int
parse_nntp_group_parameters(struct nntp_connection_info *nntp,
unsigned char *pos, unsigned char *end)
{
errno = 0;
/* Get <articles> */
while (pos < end && !isdigit(*pos))
pos++;
nntp->articles = strtol(pos, (char **) &pos, 10);
if (errno || pos >= end || nntp->articles < 0)
return 0;
if (nntp->target == NNTP_TARGET_ARTICLE_RANGE)
return 1;
/* Get <first-article> */
while (pos < end && !isdigit(*pos))
pos++;
nntp->current_article = strtol(pos, (char **) &pos, 10);
if (errno || pos >= end || nntp->current_article < 0)
return 0;
/* Get <last-article> */
while (pos < end && !isdigit(*pos))
pos++;
nntp->end_article = strtol(pos, (char **) &pos, 10);
if (errno || pos >= end || nntp->end_article < 0)
return 0;
return 1;
}
enum nntp_code
get_nntp_response_code(struct connection *conn, struct read_buffer *rb)
{
struct nntp_connection_info *nntp = conn->info;
unsigned char *line = rb->data;
unsigned char *end = get_nntp_line_end(rb->data, rb->length);
enum nntp_code code;
int linelen;
if (!end) return NNTP_CODE_NONE;
/* Just to be safe NUL terminate the line */
end[-1] = 0;
linelen = end - line;
if (linelen < sizeof("xxx\r\n") - 1
|| !isdigit(line[0])
|| !isdigit(line[1])
|| !isdigit(line[2])
|| isdigit(line[3]))
return NNTP_CODE_INVALID;
code = atoi(line);
if (!check_nntp_code_valid(code))
return NNTP_CODE_INVALID;
/* Only when listing all articles in group the parameters is needed */
if (code == NNTP_CODE_211_GROUP_SELECTED
&& nntp->target == NNTP_TARGET_GROUP
&& !parse_nntp_group_parameters(nntp, line + 4, end))
return NNTP_CODE_INVALID;
/* Remove the response line */
kill_buffer_data(rb, linelen);
conn->received += linelen;
return code;
}