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 enum 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 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
)
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 */
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
);
136 case NNTP_TARGET_ARTICLE_RANGE
:
137 case NNTP_TARGET_GROUP
:
138 case NNTP_TARGET_GROUPS
:
139 case NNTP_TARGET_QUIT
:
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
:
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
,
210 "<head><title>%s</title></head>\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
);
238 add_format_to_string(html
, "<b>%s</b>: %s\n", entry
, value
);
243 add_to_string(html
, "<hr />");
246 case NNTP_TARGET_ARTICLE_RANGE
:
247 case NNTP_TARGET_GROUP
:
248 case NNTP_TARGET_GROUPS
:
249 add_format_to_string(html
,
253 empty_string_or_(title
));
256 case NNTP_TARGET_QUIT
:
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>");
275 case NNTP_TARGET_ARTICLE_RANGE
:
276 case NNTP_TARGET_GROUP
:
277 case NNTP_TARGET_GROUPS
:
278 add_to_string(html
, "</dl>");
281 case NNTP_TARGET_QUIT
:
285 add_to_string(html
, "\n<hr />\n</body>\n</html>");
289 add_nntp_html_line(struct string
*html
, struct connection
*conn
,
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
));
301 case NNTP_TARGET_ARTICLE_RANGE
:
302 case NNTP_TARGET_GROUP
:
303 case NNTP_TARGET_GROUPS
:
305 unsigned char *desc
= strchr(line
, '\t');
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
);
317 case NNTP_TARGET_QUIT
:
321 add_char_to_string(html
, '\n');
324 enum connection_state
325 read_nntp_response_data(struct connection
*conn
, struct read_buffer
*rb
)
329 enum connection_state state
= S_TRANS
;
331 if (conn
->from
== 0) {
332 switch (init_nntp_header(conn
, rb
)) {
347 if (!init_string(&html
))
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
);
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
;
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 */
382 parse_nntp_group_parameters(struct nntp_connection_info
*nntp
,
383 unsigned char *pos
, unsigned char *end
)
388 while (pos
< end
&& !isdigit(*pos
))
391 nntp
->articles
= strtol(pos
, (char **) &pos
, 10);
392 if (errno
|| pos
>= end
|| nntp
->articles
< 0)
395 if (nntp
->target
== NNTP_TARGET_ARTICLE_RANGE
)
398 /* Get <first-article> */
399 while (pos
< end
&& !isdigit(*pos
))
402 nntp
->current_article
= strtol(pos
, (char **) &pos
, 10);
403 if (errno
|| pos
>= end
|| nntp
->current_article
< 0)
406 /* Get <last-article> */
407 while (pos
< end
&& !isdigit(*pos
))
410 nntp
->end_article
= strtol(pos
, (char **) &pos
, 10);
411 if (errno
|| pos
>= end
|| nntp
->end_article
< 0)
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
);
426 if (!end
) return NNTP_CODE_NONE
;
428 /* Just to be safe NUL terminate the line */
431 linelen
= end
- line
;
433 if (linelen
< sizeof("xxx\r\n") - 1
438 return NNTP_CODE_INVALID
;
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
;