Merge with git+ssh://pasky.or.cz/srv/git/elinks.git
[elinks.git] / src / protocol / nntp / connection.c
blobe054fcfd0324cf6f7657d7a77a67e567b49c6119
1 /* Connection and data transport handling */
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 "main/module.h"
16 #include "network/connection.h"
17 #include "network/socket.h"
18 #include "protocol/nntp/codes.h"
19 #include "protocol/nntp/connection.h"
20 #include "protocol/nntp/nntp.h"
21 #include "protocol/nntp/response.h"
22 #include "protocol/protocol.h"
23 #include "protocol/uri.h"
24 #include "util/memory.h"
25 #include "util/string.h"
28 /* The official color for this planet is green,
29 * which grows in pockets of them people willing to scheme. --delasoul */
31 static void nntp_send_command(struct connection *conn);
34 /* Connection setup: */
36 /* Resolves the target by looking at the part of the URI _after_ last '/' */
37 static enum nntp_target
38 get_nntp_target(unsigned char *data, int datalen)
40 enum nntp_target target = NNTP_TARGET_ARTICLE_NUMBER;
41 int pos;
43 /* List groups on the server if there is nothing specified */
44 if (!datalen)
45 return NNTP_TARGET_GROUPS;
47 /* Check for <message-id> */
48 if (memchr(data, '@', datalen))
49 return NNTP_TARGET_MESSAGE_ID;
51 /* Check for <article-number> or <start-number>-<end-number> */
52 for (pos = 0; pos < datalen; pos++) {
53 if (!isdigit(data[pos])) {
54 /* Assume <group> if it is not a range dash
55 * or a range dash was already detected */
56 if (data[pos] != '-'
57 || target == NNTP_TARGET_ARTICLE_RANGE)
58 return NNTP_TARGET_GROUP;
60 target = NNTP_TARGET_ARTICLE_RANGE;
64 /* If the scanning saw only numberical char and possibly one '-' it
65 * defaults to assume the URI part describes article number or range.
66 * This rules out numerical only group names. */
67 return target;
70 /* Init article range and check that both <start-number> and <end-number> are
71 * non empty and valid numbers and that the range is valid. */
72 static int
73 init_nntp_article_range(struct nntp_connection_info *nntp,
74 unsigned char *data, int datalen)
76 long start_number, end_number;
77 unsigned char *end;
79 errno = 0;
80 start_number = strtol(data, (char **) &end, 10);
81 if (errno || !end || *end != '-' || start_number < 0)
82 return 0;
84 end_number = strtol(end + 1, (char **) &end, 10);
85 if (errno || end != data + datalen || end_number < 0)
86 return 0;
88 nntp->current_article = start_number;
89 nntp->end_article = end_number;
91 nntp->message.source = data;
92 nntp->message.length = datalen;
94 return start_number <= end_number ? 1 : 0;
97 static struct nntp_connection_info *
98 init_nntp_connection_info(struct connection *conn)
100 struct uri *uri = conn->uri;
101 struct nntp_connection_info *nntp;
102 unsigned char *groupend;
103 unsigned char *data;
104 int datalen;
106 nntp = mem_calloc(1, sizeof(*nntp));
107 if (!nntp) {
108 abort_connection(conn, S_OUT_OF_MEM);
109 return NULL;
112 conn->info = nntp;
114 set_string_magic(&nntp->group);
115 set_string_magic(&nntp->message);
117 data = uri->data;
118 datalen = uri->datalen;
120 /* Check for <group>/ */
121 groupend = memchr(data, '/', datalen);
122 if (groupend) {
123 int grouplen = groupend - data;
125 nntp->group.source = data;
126 nntp->group.length = grouplen;
128 datalen -= grouplen + 1;
129 data += grouplen + 1;
131 /* Nothing after last '/'? */
132 if (!datalen) {
133 nntp->target = NNTP_TARGET_GROUP;
134 return nntp;
138 nntp->target = get_nntp_target(data, datalen);
140 switch (nntp->target) {
141 case NNTP_TARGET_ARTICLE_RANGE:
142 if (init_nntp_article_range(nntp, data, datalen))
143 break;
145 /* FIXME: Special S_NNTP_BAD_RANGE */
146 abort_connection(conn, S_BAD_URL);
147 return NULL;
149 case NNTP_TARGET_ARTICLE_NUMBER:
150 case NNTP_TARGET_MESSAGE_ID:
151 case NNTP_TARGET_GROUP_MESSAGE_ID:
152 nntp->message.source = data;
153 nntp->message.length = datalen;
154 break;
156 case NNTP_TARGET_GROUP:
157 /* Map nntp://<server>/<group> to nntp://<server>/<group>/ so
158 * we get only one cache entry with content. */
159 if (!groupend) {
160 enum connection_state state = S_OK;
162 conn->cached = get_cache_entry(conn->uri);
163 if (!conn->cached
164 || !redirect_cache(conn->cached, "/", 0, 0))
165 state = S_OUT_OF_MEM;
167 abort_connection(conn, state);
168 return NULL;
171 /* Reject nntp://<server>/<group>/<group> */
172 if (nntp->group.source) {
173 abort_connection(conn, S_BAD_URL);
174 return NULL;
177 nntp->group.source = data;
178 nntp->group.length = datalen;
179 break;
181 case NNTP_TARGET_GROUPS:
182 case NNTP_TARGET_QUIT:
183 break;
186 return nntp;
190 /* Connection teardown: */
192 static void
193 nntp_quit(struct connection *conn)
195 struct nntp_connection_info *info;
197 info = mem_calloc(1, sizeof(*info));
198 if (!info) {
199 abort_connection(conn, S_OUT_OF_MEM);
200 return;
203 info->target = NNTP_TARGET_QUIT;
205 conn->info = info;
207 nntp_send_command(conn);
210 static void
211 nntp_end_request(struct connection *conn, enum connection_state state)
213 struct nntp_connection_info *nntp = conn->info;
215 if (nntp->target == NNTP_TARGET_QUIT) {
216 abort_connection(conn, state);
217 return;
220 if (state == S_OK) {
221 if (conn->cached) {
222 normalize_cache_entry(conn->cached, conn->from);
224 } else if (state == S_OUT_OF_MEM) {
225 /* FIXME: Clear the socket buffers before ending so the one
226 * grabing the keepalive connection will be able to go on. */
229 set_connection_state(conn, state);
230 add_keepalive_connection(conn, NNTP_KEEPALIVE_TIMEOUT, nntp_quit);
234 /* Reponse receiving: */
236 static void
237 read_nntp_data(struct socket *socket, struct read_buffer *rb)
239 struct connection *conn = socket->conn;
241 if (socket->state == SOCKET_CLOSED) {
242 nntp_end_request(conn, S_OK);
243 return;
246 switch (read_nntp_response_data(conn, rb)) {
247 case S_OK:
248 nntp_send_command(conn);
249 break;
251 case S_OUT_OF_MEM:
252 nntp_end_request(conn, S_OUT_OF_MEM);
253 break;
255 case S_TRANS:
256 default:
257 read_from_socket(conn->socket, rb, S_TRANS, read_nntp_data);
261 /* Translate NNTP code to the internal connection state. */
262 static enum connection_state
263 get_nntp_connection_state(enum nntp_code code)
265 switch (code) {
266 case NNTP_CODE_400_GOODBYE: return S_NNTP_SERVER_HANG_UP;
267 case NNTP_CODE_411_GROUP_UNKNOWN: return S_NNTP_GROUP_UNKNOWN;
268 case NNTP_CODE_423_ARTICLE_NONUMBER: return S_NNTP_ARTICLE_UNKNOWN;
269 case NNTP_CODE_430_ARTICLE_NOID: return S_NNTP_ARTICLE_UNKNOWN;
270 case NNTP_CODE_436_ARTICLE_TRANSFER: return S_NNTP_TRANSFER_ERROR;
271 case NNTP_CODE_480_AUTH_REQUIRED: return S_NNTP_AUTH_REQUIRED;
272 case NNTP_CODE_502_ACCESS_DENIED: return S_NNTP_ACCESS_DENIED;
273 case NNTP_CODE_503_PROGRAM_FAULT: return S_NNTP_SERVER_ERROR;
275 case NNTP_CODE_412_GROUP_UNSET:
276 case NNTP_CODE_420_ARTICLE_UNSET:
277 case NNTP_CODE_421_ARTICLE_NONEXT:
278 case NNTP_CODE_422_ARTICLE_NOPREV:
279 case NNTP_CODE_435_ARTICLE_NOSEND:
280 case NNTP_CODE_437_ARTICLE_REJECTED:
281 case NNTP_CODE_440_POSTING_DENIED:
282 case NNTP_CODE_441_POSTING_FAILED:
283 case NNTP_CODE_482_AUTH_REJECTED:
284 case NNTP_CODE_580_AUTH_FAILED:
285 case NNTP_CODE_500_COMMAND_UNKNOWN:
286 case NNTP_CODE_501_COMMAND_SYNTAX:
287 default:
288 /* Notice and error codes for stuff which is either not
289 * supported or which is not supposed to happen. */
290 return S_NNTP_ERROR;
294 static void
295 nntp_got_response(struct socket *socket, struct read_buffer *rb)
297 struct connection *conn = socket->conn;
298 struct nntp_connection_info *nntp = conn->info;
300 if (socket->state == SOCKET_CLOSED) {
301 nntp_end_request(conn, S_OK);
302 return;
305 nntp->code = get_nntp_response_code(conn, rb);
307 switch (nntp->code) {
308 case NNTP_CODE_NONE:
309 read_from_socket(conn->socket, rb, S_TRANS, nntp_got_response);
310 break;
312 case NNTP_CODE_INVALID:
313 nntp_end_request(conn, S_NNTP_ERROR);
314 break;
316 case NNTP_CODE_200_HELLO:
317 case NNTP_CODE_201_HELLO_NOPOST:
318 case NNTP_CODE_211_GROUP_SELECTED:
319 nntp_send_command(conn);
320 break;
322 case NNTP_CODE_205_GOODBYE:
323 nntp_end_request(conn, S_OK);
324 break;
326 case NNTP_CODE_215_FOLLOW_GROUPS:
327 case NNTP_CODE_220_FOLLOW_ARTICLE:
328 case NNTP_CODE_221_FOLLOW_HEAD:
329 case NNTP_CODE_222_FOLLOW_BODY:
330 case NNTP_CODE_223_FOLLOW_NOTEXT:
331 case NNTP_CODE_224_FOLLOW_XOVER:
332 case NNTP_CODE_230_FOLLOW_NEWNEWS:
333 case NNTP_CODE_231_FOLLOW_NEWGROUPS:
334 read_nntp_data(socket, rb);
335 break;
337 case NNTP_CODE_500_COMMAND_UNKNOWN:
338 if (nntp->command == NNTP_COMMAND_LIST_ARTICLES
339 && !nntp->xover_unsupported) {
340 nntp->xover_unsupported = 1;
341 nntp_send_command(conn);
342 break;
345 default:
346 /* FIXME: Handle (error) response codes */
347 nntp_end_request(conn, get_nntp_connection_state(nntp->code));
348 break;
352 static void
353 nntp_get_response(struct socket *socket)
355 struct connection *conn = socket->conn;
356 struct read_buffer *rb = alloc_read_buffer(conn->socket);
358 if (!rb) return;
360 socket->state = SOCKET_END_ONCLOSE;
361 read_from_socket(conn->socket, rb, conn->state, nntp_got_response);
365 /* Command sending: */
367 /* Figure out the next command by looking at the target and the previous
368 * command. */
369 static enum nntp_command
370 get_nntp_command(struct nntp_connection_info *nntp)
372 /* The more logical approach of making the switch noodle use the target
373 * actually will make it bigger altho' assertions of invalid states
374 * would be easier. I chose to make it simpler for now. --jonas */
375 /* All valid states should return from within the outer switch. Anything
376 * else should break out to the function end and cause an internal
377 * error message. */
378 switch (nntp->command) {
379 case NNTP_COMMAND_NONE:
380 switch (nntp->target) {
381 case NNTP_TARGET_GROUPS:
382 return NNTP_COMMAND_LIST_NEWSGROUPS;
384 case NNTP_TARGET_GROUP:
385 case NNTP_TARGET_ARTICLE_NUMBER:
386 case NNTP_TARGET_ARTICLE_RANGE:
387 case NNTP_TARGET_GROUP_MESSAGE_ID:
388 return NNTP_COMMAND_GROUP;
390 case NNTP_TARGET_MESSAGE_ID:
391 return NNTP_COMMAND_ARTICLE_MESSAGE_ID;
393 case NNTP_TARGET_QUIT:
394 return NNTP_COMMAND_QUIT;
396 break;
398 case NNTP_COMMAND_GROUP:
399 switch (nntp->target) {
400 case NNTP_TARGET_GROUP:
401 case NNTP_TARGET_ARTICLE_RANGE:
402 return NNTP_COMMAND_LIST_ARTICLES;
404 case NNTP_TARGET_ARTICLE_NUMBER:
405 return NNTP_COMMAND_ARTICLE_NUMBER;
407 case NNTP_TARGET_GROUP_MESSAGE_ID:
408 return NNTP_COMMAND_ARTICLE_MESSAGE_ID;
410 case NNTP_TARGET_MESSAGE_ID:
411 case NNTP_TARGET_QUIT:
412 case NNTP_TARGET_GROUPS:
413 break;
415 break;
417 case NNTP_COMMAND_LIST_ARTICLES:
418 if (nntp->target != NNTP_TARGET_GROUP
419 && nntp->target != NNTP_TARGET_ARTICLE_RANGE)
420 break;
422 /* Are there more articles to list? */
423 if (nntp->xover_unsupported
424 && nntp->current_article <= nntp->end_article)
425 return NNTP_COMMAND_LIST_ARTICLES;
427 return NNTP_COMMAND_NONE;
429 case NNTP_COMMAND_ARTICLE_NUMBER:
430 if (nntp->target == NNTP_TARGET_ARTICLE_NUMBER)
431 return NNTP_COMMAND_NONE;
433 return NNTP_COMMAND_NONE;
435 case NNTP_COMMAND_ARTICLE_MESSAGE_ID:
436 case NNTP_COMMAND_LIST_NEWSGROUPS:
437 case NNTP_COMMAND_QUIT:
438 /* FIXME: assert()? --jonas */
439 return NNTP_COMMAND_NONE;
442 INTERNAL("Bad command %d handling %d", nntp->target, nntp->command);
444 return NNTP_COMMAND_NONE;
447 /* Command lines shall not exceed 512 characters in length, counting all
448 * characters including spaces, separators, punctuation, and the trailing CR-LF
449 * (Carriage Return - Line Feed) pair. */
450 #define NNTP_MAX_COMMAND_LENGTH 512
452 /* FIXME: For the beauty maybe %.*s could be used. --jonas */
453 static void
454 add_nntp_command_to_string(struct string *req, struct nntp_connection_info *nntp)
456 switch (nntp->command) {
457 case NNTP_COMMAND_GROUP:
458 add_to_string(req, "GROUP ");
459 add_string_to_string(req, &nntp->group);
460 break;
462 case NNTP_COMMAND_ARTICLE_NUMBER:
463 add_to_string(req, "ARTICLE ");
464 add_string_to_string(req, &nntp->message);
465 break;
467 case NNTP_COMMAND_ARTICLE_MESSAGE_ID:
468 add_to_string(req, "ARTICLE <");
469 add_string_to_string(req, &nntp->message);
470 add_char_to_string(req, '>');
471 break;
473 case NNTP_COMMAND_LIST_ARTICLES:
474 if (!nntp->xover_unsupported) {
475 int first = nntp->current_article++;
476 int last = nntp->end_article;
478 add_format_to_string(req, "XOVER %d-%d", first, last);
479 break;
482 assert(nntp->current_article <= nntp->end_article);
484 add_format_to_string(req, "HEAD %ld", nntp->current_article++);
485 break;
487 case NNTP_COMMAND_QUIT:
488 add_to_string(req, "QUIT");
489 break;
491 case NNTP_COMMAND_LIST_NEWSGROUPS:
492 add_to_string(req, "LIST NEWSGROUPS");
493 break;
495 case NNTP_COMMAND_NONE:
496 INTERNAL("Trying to add 'no' command.");
497 return;
500 add_crlf_to_string(req);
503 static void
504 nntp_send_command(struct connection *conn)
506 struct nntp_connection_info *nntp = conn->info;
507 struct string req;
509 nntp->command = get_nntp_command(nntp);
511 if (nntp->command == NNTP_COMMAND_NONE) {
512 nntp_end_request(conn, S_OK);
513 return;
516 if (!init_string(&req)) {
517 nntp_end_request(conn, S_OUT_OF_MEM);
518 return;
521 /* FIXME: Check non empty and < NNTP_MAX_COMMAND_LENGTH */
522 add_nntp_command_to_string(&req, nntp);
524 request_from_socket(conn->socket, req.source, req.length, S_SENT,
525 SOCKET_END_ONCLOSE, nntp_got_response);
526 done_string(&req);
530 /* The ``nntp://'' and ``news:'' protocol handlers: */
532 void
533 nntp_protocol_handler(struct connection *conn)
535 if (!init_nntp_connection_info(conn))
536 return;
538 if (!has_keepalive_connection(conn)) {
539 make_connection(conn->socket, conn->uri, nntp_get_response,
540 conn->cache_mode >= CACHE_MODE_FORCE_RELOAD);
541 } else {
542 nntp_send_command(conn);
546 /* Note that while nntp:// URIs specify a unique location for the article
547 * resource, most NNTP servers currently on the Internet today are configured
548 * only to allow access from local clients, and thus nntp:// URIs do not
549 * designate globally accessible resources. Thus, the news: form of URI is
550 * preferred as a way of identifying news articles.
552 * Whenever dealing with news: URIs the <server> part is retrieved from either
553 * protocol.nntp.server or the NNTPSERVER environment variable. The news: URI
554 * is then redirected to a nntp:// URI using the <server> so that:
556 * news:<message-id> -> nntp://<server>/<message-id>
557 * news:<group> -> nntp://<server>/<group>
558 * news:<group>/<...> -> nntp://<server>/<group>/<...>
561 void
562 news_protocol_handler(struct connection *conn)
564 unsigned char *protocol;
565 unsigned char *server = get_nntp_server();
566 struct string location;
568 if (!*server) server = getenv("NNTPSERVER");
569 if (!server || !*server) {
570 abort_connection(conn, S_NNTP_NEWS_SERVER);
571 return;
574 conn->cached = get_cache_entry(conn->uri);
575 if (!conn->cached || !init_string(&location)) {
576 abort_connection(conn, S_OUT_OF_MEM);
577 return;
580 protocol = conn->uri->protocol == PROTOCOL_NEWS ? "nntp" : "nntps";
582 add_format_to_string(&location, "%s://%s/", protocol, server);
583 add_uri_to_string(&location, conn->uri, URI_DATA);
585 redirect_cache(conn->cached, location.source, 0, 0);
587 done_string(&location);
589 abort_connection(conn, S_OK);