1 /* Parses and converts NNTP responses to enum values and cache entry HTML */
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
)
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
)
58 /* Just to be safe NUL terminate the line */
61 if (line
[0] != '.') return line
;
63 if (!line
[1]) return NULL
;
64 if (line
[1] == '.') 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
;
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. */
92 static struct connection_state
93 init_nntp_header(struct connection
*conn
, struct read_buffer
*rb
)
95 struct nntp_connection_info
*nntp
= conn
->info
;
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
:
119 end
= get_nntp_message_header_end(rb
->data
, rb
->length
);
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
);
136 case NNTP_TARGET_ARTICLE_RANGE
:
137 case NNTP_TARGET_GROUP
:
138 case NNTP_TARGET_GROUPS
:
139 case NNTP_TARGET_QUIT
:
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
;
153 if (!init_string(&title
))
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
);
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
);
170 add_to_string(&title
, subject
);
175 add_format_to_string(&title
, "Article "),
176 add_string_to_string(&title
, &nntp
->message
);
178 if (nntp
->target
== NNTP_TARGET_MESSAGE_ID
)
181 add_format_to_string(&title
, " in ");
182 add_string_to_string(&title
, &nntp
->group
);
185 case NNTP_TARGET_GROUP
:
186 add_format_to_string(&title
, "Articles in "),
187 add_string_to_string(&title
, &nntp
->group
);
190 case NNTP_TARGET_GROUPS
:
191 add_format_to_string(&title
, "Newsgroups on "),
192 add_uri_to_string(&title
, conn
->uri
, URI_PUBLIC
);
195 case NNTP_TARGET_QUIT
:
204 decode_q_segment(struct string
*str
, unsigned char *in
, unsigned char *end
)
208 while ((c
= *in
++) != 0 && (in
<= end
)) {
213 break; /* drop trailing newline */
214 d
= (unhx(d
) << 4) | unhx(*in
++);
215 add_format_to_string(str
, "&#%d;", d
);
219 if (c
== '_') /* rfc2047 4.2 (2) */
221 add_char_to_string(str
, c
);
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
)) {
236 else if ('A' <= c
&& c
<= 'Z')
238 else if ('a' <= c
&& c
<= 'z')
240 else if ('0' <= c
&& c
<= '9')
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.
250 continue; /* garbage */
257 add_format_to_string(str
, "&#%d;", (acc
| (c
>> 4)));
261 add_format_to_string(str
, "&#%d;", (acc
| (c
>> 2)));
265 add_format_to_string(str
, "&#%d;", (acc
| c
));
273 add_header_to_string(struct string
*str
, unsigned char *header
)
277 while ((end
= strstr(header
, "=?")) != NULL
) {
279 unsigned char *cp
, *sp
;
282 add_html_to_string(str
, header
, end
- header
);
287 * ep : "=?iso-2022-jp?B?GyR...?= foo"
288 * ep : "=?ISO-8859-1?Q?Foo=FCbar?= baz"
291 cp
= strchr(end
, '?');
295 for (sp
= end
; sp
< cp
; sp
++)
297 encoding
= c_tolower(cp
[1]);
299 if (!encoding
|| cp
[2] != '?')
302 end
= strstr(cp
+ 3, "?=");
306 decode_b_segment(str
, cp
, end
);
307 else if (encoding
== 'q')
308 decode_q_segment(str
, cp
, end
);
315 add_html_to_string(str
, header
, strlen(header
));
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
,
327 "<head><title>%s</title></head>\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
);
355 add_format_to_string(html
, "<b>%s</b>: ", entry
);
356 add_header_to_string(html
, value
);
357 add_char_to_string(html
, '\n');
362 add_to_string(html
, "<hr />");
365 case NNTP_TARGET_ARTICLE_RANGE
:
366 case NNTP_TARGET_GROUP
:
367 case NNTP_TARGET_GROUPS
:
368 add_format_to_string(html
,
372 empty_string_or_(title
));
375 case NNTP_TARGET_QUIT
:
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>");
394 case NNTP_TARGET_ARTICLE_RANGE
:
395 case NNTP_TARGET_GROUP
:
396 case NNTP_TARGET_GROUPS
:
397 add_to_string(html
, "</ol>");
400 case NNTP_TARGET_QUIT
:
404 add_to_string(html
, "\n<hr />\n</body>\n</html>");
408 add_nntp_html_line(struct string
*html
, struct connection
*conn
,
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
));
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');
431 add_format_to_string(html
, "<li value=\"%s\"><a href=\"%s/%s\">",
432 field
, struri(conn
->uri
), field
);
435 line
= strchr(line
, '\t');
439 add_header_to_string(html
, field
);
440 add_to_string(html
, "</a> ");
444 line
= strchr(line
, '\t');
448 add_header_to_string(html
, field
);
450 add_to_string(html
, "</li>");
453 case NNTP_TARGET_QUIT
:
457 add_char_to_string(html
, '\n');
460 struct connection_state
461 read_nntp_response_data(struct connection
*conn
, struct read_buffer
*rb
)
465 struct connection_state state
= connection_state(S_TRANS
);
467 if (conn
->from
== 0) {
468 switch (init_nntp_header(conn
, rb
).basic
) {
473 return connection_state(S_OUT_OF_MEM
);
476 return connection_state(S_TRANS
);
479 return connection_state(S_NNTP_ERROR
);
483 if (!init_string(&html
))
484 return connection_state(S_OUT_OF_MEM
);
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
);
493 state
= connection_state(S_OK
);
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
;
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 */
518 parse_nntp_group_parameters(struct nntp_connection_info
*nntp
,
519 unsigned char *pos
, unsigned char *end
)
524 while (pos
< end
&& !isdigit(*pos
))
527 nntp
->articles
= strtol(pos
, (char **) &pos
, 10);
528 if (errno
|| pos
>= end
|| nntp
->articles
< 0)
531 if (nntp
->target
== NNTP_TARGET_ARTICLE_RANGE
)
534 /* Get <first-article> */
535 while (pos
< end
&& !isdigit(*pos
))
538 nntp
->current_article
= strtol(pos
, (char **) &pos
, 10);
539 if (errno
|| pos
>= end
|| nntp
->current_article
< 0)
542 /* Get <last-article> */
543 while (pos
< end
&& !isdigit(*pos
))
546 nntp
->end_article
= strtol(pos
, (char **) &pos
, 10);
547 if (errno
|| pos
>= end
|| nntp
->end_article
< 0)
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
);
562 if (!end
) return NNTP_CODE_NONE
;
564 /* Just to be safe NUL terminate the line */
567 linelen
= end
- line
;
569 if (linelen
< sizeof("xxx\r\n") - 1
574 return NNTP_CODE_INVALID
;
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
;