iconv: Bail out of the loop when an illegal sequence of bytes occurs.
[elinks/elinks-j605.git] / src / protocol / bittorrent / tracker.c
blob4e4b6face954a6bce0e56ddd7bbaae56a861bbf1
1 /* BitTorrent tracker HTTP protocol implementation */
3 #ifdef HAVE_CONFIG_H
4 #include "config.h"
5 #endif
7 #include "elinks.h"
9 #include "cache/cache.h"
10 #include "config/options.h"
11 #include "main/select.h"
12 #include "main/timer.h"
13 #include "network/connection.h"
14 #include "network/socket.h"
15 #include "protocol/bittorrent/bencoding.h"
16 #include "protocol/bittorrent/bittorrent.h"
17 #include "protocol/bittorrent/common.h"
18 #include "protocol/bittorrent/connection.h"
19 #include "protocol/bittorrent/tracker.h"
20 #include "protocol/protocol.h"
21 #include "protocol/uri.h"
22 #include "util/memory.h"
23 #include "util/string.h"
26 /* Y'all bases iz belong ta us. */
28 /* TODO: Support for the scrape convention. It should be optional through an
29 * update interval option indicating in seconds how often to request the scrape
30 * info. Zero means off. */
32 struct uri_list bittorrent_stopped_requests;
34 static void do_send_bittorrent_tracker_request(struct connection *conn);
36 static void
37 set_bittorrent_tracker_interval(struct connection *conn)
39 struct bittorrent_connection *bittorrent = conn->info;
40 int interval = get_opt_int("protocol.bittorrent.tracker.interval",
41 NULL);
43 /* The default HTTP_KEEPALIVE_TIMEOUT is set to 60 seconds so it
44 * probably makes sense to have a default tracker interval that is below
45 * that limit to get good performance. On the other hand it is probably
46 * also good to be nice and follow the trackers intructions. Rate limit
47 * the interval to be minimum twice each minute so it doesn't end up
48 * hogging the CPU too much. */
49 if (!interval)
50 interval = int_min(bittorrent->tracker.interval, 60 * 5);
52 if (interval <= 0)
53 interval = 60 * 5;
55 install_timer(&bittorrent->tracker.timer, sec_to_ms(interval),
56 (void (*)(void *)) do_send_bittorrent_tracker_request,
57 conn);
60 /* Handling of the regular HTTP request/response connnection to the tracker, */
61 /* XXX: The data pointer may be NULL when doing event=stopped because no
62 * connection is attached anymore. */
63 static void
64 bittorrent_tracker_callback(void *data, struct connection_state state,
65 struct bittorrent_const_string *response)
67 struct connection *conn = data;
68 struct bittorrent_connection *bittorrent = conn ? conn->info : NULL;
70 /* We just did event=stopped and don't have any connection attached. */
71 if (!bittorrent) return;
73 assert(bittorrent->tracker.event != BITTORRENT_EVENT_STOPPED);
75 bittorrent->fetch = NULL;
77 /* FIXME: We treat any error as fatal here, however, it might be better
78 * to relax that and allow a few errors before ending the connection. */
79 if (!is_in_state(state, S_OK)) {
80 if (is_in_state(state, S_INTERRUPTED))
81 return;
82 bittorrent->tracker.failed = 1;
83 add_bittorrent_message(conn->uri, state, NULL);
84 abort_connection(conn, connection_state(S_OK));
85 return;
88 switch (parse_bittorrent_tracker_response(bittorrent, response)) {
89 case BITTORRENT_STATE_ERROR:
90 /* This error means a parsing error, and it actually seems to
91 * happen so frequently that they are worth ignoring from a
92 * usability perspective, e.g. they may be caused by the peer
93 * info list suddenly ending without no notice. */
94 case BITTORRENT_STATE_OK:
95 if (bittorrent->tracker.event == BITTORRENT_EVENT_STARTED) {
96 assert(bittorrent->timer == TIMER_ID_UNDEF);
97 bittorrent->tracker.started = 1;
98 update_bittorrent_connection_state(conn);
101 set_bittorrent_tracker_interval(conn);
102 bittorrent->tracker.event = BITTORRENT_EVENT_REGULAR;
103 return;
105 case BITTORRENT_STATE_OUT_OF_MEM:
106 state = connection_state(S_OUT_OF_MEM);
107 break;
109 case BITTORRENT_STATE_REQUEST_FAILURE:
110 add_bittorrent_message(conn->uri, connection_state(S_OK),
111 response);
112 state = connection_state(S_OK);
113 break;
115 default:
116 state = connection_state(S_BITTORRENT_TRACKER);
119 abort_connection(conn, state);
122 static void
123 check_bittorrent_stopped_request(void *____)
125 struct uri *uri;
126 int index;
128 foreach_uri (uri, index, &bittorrent_stopped_requests) {
129 init_bittorrent_fetch(NULL, uri, bittorrent_tracker_callback, NULL, 1);
132 free_uri_list(&bittorrent_stopped_requests);
135 /* Timer callback for @bittorrent->tracker.timer. As explained in
136 * @install_timer, this function must erase the expired timer ID from
137 * all variables. Also called from the request sending front-end. */
138 /* XXX: When called with event set to ``stopped'' failure handling should not
139 * end the connection, since that is already in progress. */
140 /* TODO: Make a special timer callback entry point that can check if
141 * rerequestion is needed, that is if more peer info is needed etc. */
142 static void
143 do_send_bittorrent_tracker_request(struct connection *conn)
145 struct bittorrent_connection *bittorrent = conn->info;
146 int stopped = (bittorrent->tracker.event == BITTORRENT_EVENT_STOPPED);
147 unsigned char *ip, *key;
148 struct string request;
149 struct uri *uri = NULL;
150 int numwant, index, min_size;
152 bittorrent->tracker.timer = TIMER_ID_UNDEF;
153 /* The expired timer ID has now been erased. */
155 /* If the previous request didn't make it, nuke it. This shouldn't
156 * happen but not doing this makes it a potential leak. */
157 if (bittorrent->fetch)
158 done_bittorrent_fetch(&bittorrent->fetch);
160 if (!init_string(&request)) {
161 done_string(&request);
162 if (!stopped)
163 abort_connection(conn,
164 connection_state(S_OUT_OF_MEM));
165 return;
168 foreach_uri (uri, index, &bittorrent->meta.tracker_uris)
169 /* Pick the first ... */
170 break;
172 if (!uri) {
173 done_string(&request);
174 if (!stopped)
175 abort_connection(conn,
176 connection_state(S_BITTORRENT_ERROR));
177 return;
180 add_uri_to_string(&request, uri, URI_BASE);
182 add_to_string(&request, "?info_hash=");
183 encode_uri_string(&request, bittorrent->meta.info_hash,
184 sizeof(bittorrent->meta.info_hash), 1);
186 add_to_string(&request, "&peer_id=");
187 encode_uri_string(&request, bittorrent->peer_id,
188 sizeof(bittorrent->peer_id), 1);
190 add_format_to_string(&request, "&uploaded=%ld", bittorrent->uploaded);
191 add_format_to_string(&request, "&downloaded=%ld", bittorrent->downloaded);
192 add_format_to_string(&request, "&left=%ld", bittorrent->left);
194 /* Sending no IP-address is valid. The tracker figures it out
195 * automatically which is much easier. However, the user might want to
196 * configure a specific IP-address to send. */
197 ip = get_opt_str("protocol.bittorrent.tracker.ip_address", NULL);
198 if (*ip) add_format_to_string(&request, "&ip=%s", ip);
200 /* This one is required for each request. */
201 add_format_to_string(&request, "&port=%u", bittorrent->port);
203 key = get_opt_str("protocol.bittorrent.tracker.key", NULL);
204 if (*key) {
205 add_to_string(&request, "&key=");
206 encode_uri_string(&request, key, strlen(key), 1);
209 if (bittorrent->tracker.event != BITTORRENT_EVENT_REGULAR) {
210 unsigned char *event;
212 switch (bittorrent->tracker.event) {
213 case BITTORRENT_EVENT_STARTED:
214 event = "started";
215 break;
217 case BITTORRENT_EVENT_STOPPED:
218 event = "stopped";
219 break;
221 case BITTORRENT_EVENT_COMPLETED:
222 event = "completed";
223 break;
225 default:
226 INTERNAL("Bad tracker event.");
227 event = "";
230 add_format_to_string(&request, "&event=%s", event);
233 min_size = get_opt_int("protocol.bittorrent.tracker.min_skip_size",
234 NULL);
235 if (!min_size || list_size(&bittorrent->peer_pool) < min_size) {
236 numwant = get_opt_int("protocol.bittorrent.tracker.numwant",
237 NULL);
238 /* Should the server default be used? */
239 if (numwant == 0)
240 numwant = -1;
241 } else {
242 numwant = -1;
245 if (numwant >= 0)
246 add_format_to_string(&request, "&numwant=%d", numwant);
248 if (get_opt_bool("protocol.bittorrent.tracker.compact", NULL))
249 add_to_string(&request, "&compact=1");
251 uri = get_uri(request.source, 0);
252 done_string(&request);
253 if (!uri) {
254 if (!stopped)
255 abort_connection(conn,
256 connection_state(S_BITTORRENT_ERROR));
257 return;
260 if (stopped) {
261 /* We cannot start the event=stopped requesting directly here
262 * since we are nested inside a connection shutdown which means
263 * it will trigger a queue bug if we start adding a new
264 * connection. Solution: send the request in a bottom half. */
265 if (register_bottom_half(check_bittorrent_stopped_request, NULL) == 0)
266 add_to_uri_list(&bittorrent_stopped_requests, uri);
268 } else {
269 init_bittorrent_fetch(&bittorrent->fetch, uri,
270 bittorrent_tracker_callback, conn, 1);
273 done_uri(uri);
277 void
278 send_bittorrent_tracker_request(struct connection *conn)
280 struct bittorrent_connection *bittorrent = conn->info;
282 /* Kill the timer when we are not sending a periodic request to make
283 * sure that there are only one tracker request at any time. */
284 kill_timer(&bittorrent->tracker.timer);
286 do_send_bittorrent_tracker_request(conn);
289 void
290 done_bittorrent_tracker_connection(struct connection *conn)
292 struct bittorrent_connection *bittorrent = conn->info;
294 kill_timer(&bittorrent->tracker.timer);
296 /* Nothing to shut down. */
297 if (!bittorrent->tracker.started || bittorrent->tracker.failed)
298 return;
300 /* Send a tracker request with event=stopped. Note, the request won't be
301 * sent if we are shutting down due to an emergency, because the
302 * connection subsystem will be shut down soonish. */
303 bittorrent->tracker.event = BITTORRENT_EVENT_STOPPED;
304 send_bittorrent_tracker_request(conn);