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