Merge with git+ssh://pasky.or.cz/srv/git/elinks.git
[elinks.git] / src / protocol / nntp / response.c
blob77a0ecbd8e300dbe542f2901ff7c4a626525954b
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 enum 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 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 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 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 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 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;
202 static void
203 add_nntp_html_start(struct string *html, struct connection *conn)
205 struct nntp_connection_info *nntp = conn->info;
206 unsigned char *title = get_nntp_title(conn);
208 add_format_to_string(html,
209 "<html>\n"
210 "<head><title>%s</title></head>\n"
211 "<body>\n",
212 empty_string_or_(title));
214 switch (nntp->target) {
215 case NNTP_TARGET_ARTICLE_NUMBER:
216 case NNTP_TARGET_MESSAGE_ID:
217 case NNTP_TARGET_GROUP_MESSAGE_ID:
219 unsigned char *header_entries;
221 header_entries = get_nntp_header_entries();
222 if (!*header_entries) break;
224 add_to_string(html, "<pre>");
226 while (*header_entries) {
227 unsigned char *entry, *value;
229 entry = get_next_path_filename(&header_entries, ',');
230 if (!entry) continue;
232 value = parse_header(conn->cached->head, entry, NULL);
233 if (!value) {
234 mem_free(entry);
235 continue;
238 add_format_to_string(html, "<b>%s</b>: %s\n", entry, value);
239 mem_free(value);
240 mem_free(entry);
243 add_to_string(html, "<hr />");
244 break;
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;
256 case NNTP_TARGET_QUIT:
257 break;
260 mem_free_if(title);
263 static void
264 add_nntp_html_end(struct string *html, struct connection *conn)
266 struct nntp_connection_info *nntp = conn->info;
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;
275 case NNTP_TARGET_ARTICLE_RANGE:
276 case NNTP_TARGET_GROUP:
277 case NNTP_TARGET_GROUPS:
278 add_to_string(html, "</dl>");
279 break;
281 case NNTP_TARGET_QUIT:
282 break;
285 add_to_string(html, "\n<hr />\n</body>\n</html>");
288 static void
289 add_nntp_html_line(struct string *html, struct connection *conn,
290 unsigned char *line)
292 struct nntp_connection_info *nntp = conn->info;
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;
301 case NNTP_TARGET_ARTICLE_RANGE:
302 case NNTP_TARGET_GROUP:
303 case NNTP_TARGET_GROUPS:
305 unsigned char *desc = strchr(line, '\t');
307 if (desc) {
308 *desc++ = 0;
309 } else {
310 desc = "";
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;
317 case NNTP_TARGET_QUIT:
318 break;
321 add_char_to_string(html, '\n');
324 enum connection_state
325 read_nntp_response_data(struct connection *conn, struct read_buffer *rb)
327 struct string html;
328 unsigned char *end;
329 enum connection_state state = S_TRANS;
331 if (conn->from == 0) {
332 switch (init_nntp_header(conn, rb)) {
333 case S_OK:
334 break;
336 case S_OUT_OF_MEM:
337 return S_OUT_OF_MEM;
339 case S_TRANS:
340 return S_TRANS;
342 default:
343 return S_NNTP_ERROR;
347 if (!init_string(&html))
348 return S_OUT_OF_MEM;
350 if (conn->from == 0)
351 add_nntp_html_start(&html, conn);
353 while ((end = get_nntp_line_end(rb->data, rb->length))) {
354 unsigned char *line = check_nntp_line(rb->data, end);
356 if (!line) {
357 state = S_OK;
358 break;
361 add_nntp_html_line(&html, conn, line);
363 conn->received += end - rb->data;
364 kill_buffer_data(rb, end - rb->data);
367 if (state != S_TRANS)
368 add_nntp_html_end(&html, conn);
370 add_fragment(conn->cached, conn->from, html.source, html.length);
372 conn->from += html.length;
373 done_string(&html);
375 return state;
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
382 parse_nntp_group_parameters(struct nntp_connection_info *nntp,
383 unsigned char *pos, unsigned char *end)
385 errno = 0;
387 /* Get <articles> */
388 while (pos < end && !isdigit(*pos))
389 pos++;
391 nntp->articles = strtol(pos, (char **) &pos, 10);
392 if (errno || pos >= end || nntp->articles < 0)
393 return 0;
395 if (nntp->target == NNTP_TARGET_ARTICLE_RANGE)
396 return 1;
398 /* Get <first-article> */
399 while (pos < end && !isdigit(*pos))
400 pos++;
402 nntp->current_article = strtol(pos, (char **) &pos, 10);
403 if (errno || pos >= end || nntp->current_article < 0)
404 return 0;
406 /* Get <last-article> */
407 while (pos < end && !isdigit(*pos))
408 pos++;
410 nntp->end_article = strtol(pos, (char **) &pos, 10);
411 if (errno || pos >= end || nntp->end_article < 0)
412 return 0;
414 return 1;
417 enum nntp_code
418 get_nntp_response_code(struct connection *conn, struct read_buffer *rb)
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;
426 if (!end) return NNTP_CODE_NONE;
428 /* Just to be safe NUL terminate the line */
429 end[-1] = 0;
431 linelen = end - line;
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;
440 code = atoi(line);
442 if (!check_nntp_code_valid(code))
443 return NNTP_CODE_INVALID;
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;
451 /* Remove the response line */
452 kill_buffer_data(rb, linelen);
454 conn->received += linelen;
456 return code;