Make chunked transfers use gzip also
[opentracker.git] / ot_http.c
blobc82bcdf2f9660edbba3415df7299a1f62392dcaf
1 /* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever.
4 $id$ */
6 /* System */
7 #include <sys/types.h>
8 #include <arpa/inet.h>
9 #include <stdlib.h>
10 #include <stdio.h>
11 #include <string.h>
12 #include <unistd.h>
13 #include <pthread.h>
15 /* Libowfat */
16 #include "byte.h"
17 #include "array.h"
18 #include "iob.h"
19 #include "ip6.h"
20 #include "scan.h"
21 #include "case.h"
23 /* Opentracker */
24 #include "trackerlogic.h"
25 #include "ot_mutex.h"
26 #include "ot_http.h"
27 #include "ot_iovec.h"
28 #include "scan_urlencoded_query.h"
29 #include "ot_fullscrape.h"
30 #include "ot_stats.h"
31 #include "ot_accesslist.h"
33 #define OT_MAXMULTISCRAPE_COUNT 64
34 #define OT_BATCH_LIMIT (1024*1024*16)
35 extern char *g_redirecturl;
37 char *g_stats_path;
38 ssize_t g_stats_path_len;
40 enum {
41 SUCCESS_HTTP_HEADER_LENGTH = 80,
42 SUCCESS_HTTP_HEADER_LENGTH_CONTENT_ENCODING = 32,
43 SUCCESS_HTTP_SIZE_OFF = 17 };
45 static void http_senddata( const int64 sock, struct ot_workstruct *ws ) {
46 struct http_data *cookie = io_getcookie( sock );
47 ssize_t written_size;
49 if( !cookie ) { io_close(sock); return; }
51 /* whoever sends data is not interested in its input-array */
52 if( ws->keep_alive && ws->header_size != ws->request_size ) {
53 size_t rest = ws->request_size - ws->header_size;
54 if( array_start(&cookie->request) ) {
55 memmove( array_start(&cookie->request), ws->request + ws->header_size, rest );
56 array_truncate( &cookie->request, 1, rest );
57 } else
58 array_catb(&cookie->request, ws->request + ws->header_size, rest );
59 } else
60 array_reset( &cookie->request );
62 written_size = write( sock, ws->reply, ws->reply_size );
63 if( ( written_size < 0 ) || ( ( written_size == ws->reply_size ) && !ws->keep_alive ) ) {
64 array_reset( &cookie->request );
65 free( cookie ); io_close( sock ); return;
68 if( written_size < ws->reply_size ) {
69 char * outbuf;
70 tai6464 t;
72 if( !( outbuf = malloc( ws->reply_size - written_size ) ) ) {
73 array_reset( &cookie->request );
74 free(cookie); io_close( sock );
75 return;
78 memcpy( outbuf, ws->reply + written_size, ws->reply_size - written_size );
79 if ( !cookie->batch ) {
80 cookie->batch = malloc( sizeof(io_batch) );
81 memset( cookie->batch, 0, sizeof(io_batch) );
82 cookie->batches = 1;
85 iob_addbuf_free( cookie->batch, outbuf, ws->reply_size - written_size );
87 /* writeable short data sockets just have a tcp timeout */
88 if( !ws->keep_alive ) {
89 taia_uint( &t, 0 ); io_timeout( sock, t );
90 io_dontwantread( sock );
92 io_wantwrite( sock );
96 #define HTTPERROR_302 return http_issue_error( sock, ws, CODE_HTTPERROR_302 )
97 #define HTTPERROR_400 return http_issue_error( sock, ws, CODE_HTTPERROR_400 )
98 #define HTTPERROR_400_PARAM return http_issue_error( sock, ws, CODE_HTTPERROR_400_PARAM )
99 #define HTTPERROR_400_COMPACT return http_issue_error( sock, ws, CODE_HTTPERROR_400_COMPACT )
100 #define HTTPERROR_400_DOUBLEHASH return http_issue_error( sock, ws, CODE_HTTPERROR_400_PARAM )
101 #define HTTPERROR_402_NOTMODEST return http_issue_error( sock, ws, CODE_HTTPERROR_402_NOTMODEST )
102 #define HTTPERROR_403_IP return http_issue_error( sock, ws, CODE_HTTPERROR_403_IP )
103 #define HTTPERROR_404 return http_issue_error( sock, ws, CODE_HTTPERROR_404 )
104 #define HTTPERROR_500 return http_issue_error( sock, ws, CODE_HTTPERROR_500 )
105 ssize_t http_issue_error( const int64 sock, struct ot_workstruct *ws, int code ) {
106 char *error_code[] = { "302 Found", "400 Invalid Request", "400 Invalid Request", "400 Invalid Request", "402 Payment Required",
107 "403 Not Modest", "403 Access Denied", "404 Not Found", "500 Internal Server Error" };
108 char *title = error_code[code];
110 ws->reply = ws->outbuf;
111 if( code == CODE_HTTPERROR_302 )
112 ws->reply_size = snprintf( ws->reply, G_OUTBUF_SIZE, "HTTP/1.0 302 Found\r\nContent-Length: 0\r\nLocation: %s\r\n\r\n", g_redirecturl );
113 else
114 ws->reply_size = snprintf( ws->reply, G_OUTBUF_SIZE, "HTTP/1.0 %s\r\nContent-Type: text/html\r\nContent-Length: %zd\r\n\r\n<title>%s</title>\n", title, strlen(title)+16-4,title+4);
116 #ifdef _DEBUG_HTTPERROR
117 fprintf( stderr, "DEBUG: invalid request was: %s\n", ws->debugbuf );
118 #endif
119 stats_issue_event( EVENT_FAILED, FLAG_TCP, code );
120 http_senddata( sock, ws );
121 return ws->reply_size = -2;
124 ssize_t http_sendiovecdata( const int64 sock, struct ot_workstruct *ws, int iovec_entries, struct iovec *iovector, int is_partial ) {
125 struct http_data *cookie = io_getcookie( sock );
126 io_batch *current;
127 char *header;
128 const char *encoding = "";
129 int i;
130 size_t header_size, size = iovec_length( &iovec_entries, (const struct iovec **)&iovector );
131 tai6464 t;
133 /* No cookie? Bad socket. Leave. */
134 if( !cookie ) {
135 iovec_free( &iovec_entries, &iovector );
136 HTTPERROR_500;
139 /* If this socket collected request in a buffer, free it now */
140 array_reset( &cookie->request );
142 /* If we came here, wait for the answer is over */
143 cookie->flag &= ~STRUCT_HTTP_FLAG_WAITINGFORTASK;
145 fprintf(stderr, "http_sendiovecdata sending %d iovec entries found cookie->batch == %p\n", iovec_entries, cookie->batch);
147 if( iovec_entries ) {
149 /* Prepare space for http header */
150 header = malloc( SUCCESS_HTTP_HEADER_LENGTH + SUCCESS_HTTP_HEADER_LENGTH_CONTENT_ENCODING );
151 if( !header ) {
152 iovec_free( &iovec_entries, &iovector );
153 HTTPERROR_500;
156 if( cookie->flag & STRUCT_HTTP_FLAG_GZIP )
157 encoding = "Content-Encoding: gzip\r\n";
158 else if( cookie->flag & STRUCT_HTTP_FLAG_BZIP2 )
159 encoding = "Content-Encoding: bzip2\r\n";
161 if( !(cookie->flag & STRUCT_HTTP_FLAG_CHUNKED) )
162 header_size = sprintf( header, "HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\n%sContent-Length: %zd\r\n\r\n", encoding, size );
163 else {
164 if ( !(cookie->flag & STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER )) {
165 header_size = sprintf( header, "HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\n%sTransfer-Encoding: chunked\r\n\r\n%zx\r\n", encoding, size );
166 cookie->flag |= STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER;
167 } else
168 header_size = sprintf( header, "%zx\r\n", size );
171 if (!cookie->batch ) {
172 cookie->batch = malloc( sizeof(io_batch) );
173 if (!cookie->batch) {
174 free(header);
175 iovec_free( &iovec_entries, &iovector );
176 HTTPERROR_500;
178 memset( cookie->batch, 0, sizeof(io_batch) );
179 cookie->batches = 1;
181 current = cookie->batch + cookie->batches - 1;
182 iob_addbuf_free( current, header, header_size );
184 /* Split huge iovectors into separate io_batches */
185 for( i=0; i<iovec_entries; ++i ) {
186 /* If the current batch's limit is reached, try to reallocate a new batch to work on */
187 if( current->bytesleft > OT_BATCH_LIMIT ) {
188 fprintf(stderr, "http_sendiovecdata found batch above limit: %zd\n", current->bytesleft);
189 io_batch * new_batch = realloc( cookie->batch, (cookie->batches + 1) * sizeof(io_batch) );
190 if( new_batch ) {
191 cookie->batch = new_batch;
192 current = cookie->batch + cookie->batches++;
193 memset( current, 0, sizeof(io_batch) );
196 fprintf(stderr, "http_sendiovecdata calling iob_addbuf_free with %zd\n", iovector[i].iov_len);
197 iob_addbuf_free( current, iovector[i].iov_base, iovector[i].iov_len );
199 free( iovector );
200 if ( cookie->flag & STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER )
201 iob_addbuf(current, "\r\n", 2);
204 if ((cookie->flag & STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER) && cookie->batch && !is_partial) {
205 fprintf(stderr, "http_sendiovecdata adds a terminating 0 size buffer to batch\n");
206 current = cookie->batch + cookie->batches - 1;
207 iob_addbuf(current, "0\r\n\r\n", 5);
208 cookie->flag &= ~STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER;
211 /* writeable sockets timeout after 10 minutes */
212 taia_now( &t ); taia_addsec( &t, &t, OT_CLIENT_TIMEOUT_SEND );
213 io_timeout( sock, t );
214 io_dontwantread( sock );
215 fprintf (stderr, "http_sendiovecdata marks socket %lld as wantwrite\n", sock);
216 io_wantwrite( sock );
217 return 0;
220 static ssize_t http_handle_stats( const int64 sock, struct ot_workstruct *ws, char *read_ptr ) {
221 static const ot_keywords keywords_main[] =
222 { { "mode", 1 }, {"format", 2 }, {"info_hash", 3}, { NULL, -3 } };
223 static const ot_keywords keywords_mode[] =
224 { { "peer", TASK_STATS_PEERS }, { "conn", TASK_STATS_CONNS }, { "scrp", TASK_STATS_SCRAPE }, { "udp4", TASK_STATS_UDP }, { "tcp4", TASK_STATS_TCP },
225 { "busy", TASK_STATS_BUSY_NETWORKS }, { "torr", TASK_STATS_TORRENTS }, { "fscr", TASK_STATS_FULLSCRAPE },
226 { "s24s", TASK_STATS_SLASH24S }, { "tpbs", TASK_STATS_TPB }, { "herr", TASK_STATS_HTTPERRORS }, { "completed", TASK_STATS_COMPLETED },
227 { "top100", TASK_STATS_TOP100 }, { "top10", TASK_STATS_TOP10 }, { "renew", TASK_STATS_RENEW }, { "syncs", TASK_STATS_SYNCS }, { "version", TASK_STATS_VERSION },
228 { "everything", TASK_STATS_EVERYTHING }, { "statedump", TASK_FULLSCRAPE_TRACKERSTATE }, { "fulllog", TASK_STATS_FULLLOG },
229 { "woodpeckers", TASK_STATS_WOODPECKERS},
230 #ifdef WANT_LOG_NUMWANT
231 { "numwants", TASK_STATS_NUMWANTS},
232 #endif
233 { NULL, -3 } };
234 static const ot_keywords keywords_format[] =
235 { { "bin", TASK_FULLSCRAPE_TPB_BINARY }, { "ben", TASK_FULLSCRAPE }, { "url", TASK_FULLSCRAPE_TPB_URLENCODED },
236 { "txt", TASK_FULLSCRAPE_TPB_ASCII }, { "txtp", TASK_FULLSCRAPE_TPB_ASCII_PLUS }, { NULL, -3 } };
238 int mode = TASK_STATS_PEERS, scanon = 1, format = 0;
240 #ifdef WANT_RESTRICT_STATS
241 struct http_data *cookie = io_getcookie( sock );
243 if( !cookie || !accesslist_is_blessed( cookie->ip, OT_PERMISSION_MAY_STAT ) )
244 HTTPERROR_403_IP;
245 #endif
247 while( scanon ) {
248 switch( scan_find_keywords( keywords_main, &read_ptr, SCAN_SEARCHPATH_PARAM ) ) {
249 case -2: scanon = 0; break; /* TERMINATOR */
250 case -1: HTTPERROR_400_PARAM; /* PARSE ERROR */
251 case -3: scan_urlencoded_skipvalue( &read_ptr ); break;
252 case 1: /* matched "mode" */
253 if( ( mode = scan_find_keywords( keywords_mode, &read_ptr, SCAN_SEARCHPATH_VALUE ) ) <= 0 ) HTTPERROR_400_PARAM;
254 break;
255 case 2: /* matched "format" */
256 if( ( format = scan_find_keywords( keywords_format, &read_ptr, SCAN_SEARCHPATH_VALUE ) ) <= 0 ) HTTPERROR_400_PARAM;
257 break;
258 case 3: HTTPERROR_400_PARAM; /* If the stats URL was mistakenly added as announce URL, return a 400 */
262 #ifdef WANT_FULLSCRAPE
263 if( mode == TASK_FULLSCRAPE_TRACKERSTATE ) {
264 format = mode; mode = TASK_STATS_TPB;
267 if( mode == TASK_STATS_TPB ) {
268 struct http_data* cookie = io_getcookie( sock );
269 tai6464 t;
270 #ifdef WANT_COMPRESSION_GZIP
271 ws->request[ws->request_size] = 0;
272 #ifdef WANT_COMPRESSION_GZIP_ALWAYS
273 if( strstr( read_ptr - 1, "gzip" ) ) {
274 #endif
275 cookie->flag |= STRUCT_HTTP_FLAG_GZIP;
276 format |= TASK_FLAG_GZIP;
277 #ifdef WANT_COMPRESSION_GZIP_ALWAYS
279 #endif
280 #endif
281 /* Pass this task to the worker thread */
282 cookie->flag |= STRUCT_HTTP_FLAG_WAITINGFORTASK | STRUCT_HTTP_FLAG_CHUNKED;
284 /* Clients waiting for us should not easily timeout */
285 taia_uint( &t, 0 ); io_timeout( sock, t );
286 fullscrape_deliver( sock, format );
287 io_dontwantread( sock );
288 return ws->reply_size = -2;
290 #endif
292 /* default format for now */
293 if( ( mode & TASK_CLASS_MASK ) == TASK_STATS ) {
294 tai6464 t;
295 /* Complex stats also include expensive memory debugging tools */
296 taia_uint( &t, 0 ); io_timeout( sock, t );
297 stats_deliver( sock, mode );
298 return ws->reply_size = -2;
301 /* Simple stats can be answerred immediately */
302 return ws->reply_size = return_stats_for_tracker( ws->reply, mode, 0 );
305 #ifdef WANT_MODEST_FULLSCRAPES
306 static pthread_mutex_t g_modest_fullscrape_mutex = PTHREAD_MUTEX_INITIALIZER;
307 static ot_vector g_modest_fullscrape_timeouts;
308 typedef struct { ot_ip6 ip; ot_time last_fullscrape; } ot_scrape_log;
309 #endif
311 #ifdef WANT_FULLSCRAPE
312 static ssize_t http_handle_fullscrape( const int64 sock, struct ot_workstruct *ws ) {
313 struct http_data* cookie = io_getcookie( sock );
314 int format = 0;
315 tai6464 t;
317 #ifdef WANT_MODEST_FULLSCRAPES
319 ot_scrape_log this_peer, *new_peer;
320 int exactmatch;
321 memcpy( this_peer.ip, cookie->ip, sizeof(ot_ip6));
322 this_peer.last_fullscrape = g_now_seconds;
323 pthread_mutex_lock(&g_modest_fullscrape_mutex);
324 new_peer = vector_find_or_insert( &g_modest_fullscrape_timeouts, &this_peer, sizeof(ot_scrape_log), sizeof(ot_ip6), &exactmatch );
325 if( !new_peer ) {
326 pthread_mutex_unlock(&g_modest_fullscrape_mutex);
327 HTTPERROR_500;
329 if( exactmatch && ( this_peer.last_fullscrape - new_peer->last_fullscrape ) < OT_MODEST_PEER_TIMEOUT ) {
330 pthread_mutex_unlock(&g_modest_fullscrape_mutex);
331 HTTPERROR_402_NOTMODEST;
333 memcpy( new_peer, &this_peer, sizeof(ot_scrape_log));
334 pthread_mutex_unlock(&g_modest_fullscrape_mutex);
336 #endif
338 #ifdef WANT_COMPRESSION_GZIP
339 ws->request[ws->request_size-1] = 0;
340 if( strstr( ws->request, "gzip" ) ) {
341 cookie->flag |= STRUCT_HTTP_FLAG_GZIP;
342 format = TASK_FLAG_GZIP;
343 stats_issue_event( EVENT_FULLSCRAPE_REQUEST_GZIP, 0, (uintptr_t)cookie->ip );
344 } else
345 #endif
346 stats_issue_event( EVENT_FULLSCRAPE_REQUEST, 0, (uintptr_t)cookie->ip );
348 #ifdef _DEBUG_HTTPERROR
349 fprintf( stderr, "%s", ws->debugbuf );
350 #endif
352 /* Pass this task to the worker thread */
353 cookie->flag |= STRUCT_HTTP_FLAG_WAITINGFORTASK | STRUCT_HTTP_FLAG_CHUNKED;
354 /* Clients waiting for us should not easily timeout */
355 taia_uint( &t, 0 ); io_timeout( sock, t );
356 fullscrape_deliver( sock, TASK_FULLSCRAPE | format );
357 io_dontwantread( sock );
358 return ws->reply_size = -2;
360 #endif
362 static ssize_t http_handle_scrape( const int64 sock, struct ot_workstruct *ws, char *read_ptr ) {
363 static const ot_keywords keywords_scrape[] = { { "info_hash", 1 }, { NULL, -3 } };
365 ot_hash * multiscrape_buf = (ot_hash*)ws->request;
366 int scanon = 1, numwant = 0;
368 /* This is to hack around stupid clients that send "scrape ?info_hash" */
369 if( read_ptr[-1] != '?' ) {
370 while( ( *read_ptr != '?' ) && ( *read_ptr != '\n' ) ) ++read_ptr;
371 if( *read_ptr == '\n' ) HTTPERROR_400_PARAM;
372 ++read_ptr;
375 while( scanon ) {
376 switch( scan_find_keywords( keywords_scrape, &read_ptr, SCAN_SEARCHPATH_PARAM ) ) {
377 case -2: scanon = 0; break; /* TERMINATOR */
378 default: HTTPERROR_400_PARAM; /* PARSE ERROR */
379 case -3: scan_urlencoded_skipvalue( &read_ptr ); break;
380 case 1: /* matched "info_hash" */
381 /* ignore this, when we have less than 20 bytes */
382 if( scan_urlencoded_query( &read_ptr, (char*)(multiscrape_buf + numwant++), SCAN_SEARCHPATH_VALUE ) != (ssize_t)sizeof(ot_hash) )
383 HTTPERROR_400_PARAM;
384 break;
388 /* No info_hash found? Inform user */
389 if( !numwant ) HTTPERROR_400_PARAM;
391 /* Limit number of hashes to process */
392 if( numwant > OT_MAXMULTISCRAPE_COUNT )
393 numwant = OT_MAXMULTISCRAPE_COUNT;
395 /* Enough for http header + whole scrape string */
396 ws->reply_size = return_tcp_scrape_for_torrent( (const ot_hash*)multiscrape_buf, numwant, ws->reply );
397 stats_issue_event( EVENT_SCRAPE, FLAG_TCP, ws->reply_size );
398 return ws->reply_size;
401 #ifdef WANT_LOG_NUMWANT
402 unsigned long long numwants[201];
403 #endif
405 #if defined( WANT_KEEPALIVE ) || defined( WANT_IP_FROM_PROXY )
406 static char* http_header( char *data, size_t byte_count, char *header ) {
407 size_t i;
408 long sl = strlen( header );
409 for( i = 0; i + sl + 2 < byte_count; ++i ) {
410 if( data[i] != '\n' || data[ i + sl + 1] != ':' ) continue;
411 if( !case_equalb( data + i + 1, sl, header ) ) continue;
412 data += i + sl + 2;
413 while( *data == ' ' || *data == '\t' ) ++data;
414 return data;
416 return 0;
418 #endif
420 static ot_keywords keywords_announce[] = { { "port", 1 }, { "left", 2 }, { "event", 3 }, { "numwant", 4 }, { "compact", 5 }, { "compact6", 5 }, { "info_hash", 6 },
421 #ifdef WANT_IP_FROM_QUERY_STRING
422 { "ip", 7 },
423 #endif
424 #ifdef WANT_FULLLOG_NETWORKS
425 { "lognet", 8 },
426 #endif
427 { "peer_id", 9 },
428 { NULL, -3 } };
429 static ot_keywords keywords_announce_event[] = { { "completed", 1 }, { "stopped", 2 }, { NULL, -3 } };
430 static ssize_t http_handle_announce( const int64 sock, struct ot_workstruct *ws, char *read_ptr ) {
431 int numwant, tmp, scanon;
432 unsigned short port = 0;
433 char *write_ptr;
434 ssize_t len;
435 struct http_data *cookie = io_getcookie( sock );
437 /* This is to hack around stupid clients that send "announce ?info_hash" */
438 if( read_ptr[-1] != '?' ) {
439 while( ( *read_ptr != '?' ) && ( *read_ptr != '\n' ) ) ++read_ptr;
440 if( *read_ptr == '\n' ) HTTPERROR_400_PARAM;
441 ++read_ptr;
444 #ifdef WANT_IP_FROM_PROXY
445 if( accesslist_is_blessed( cookie->ip, OT_PERMISSION_MAY_PROXY ) ) {
446 ot_ip6 proxied_ip;
447 char *fwd = http_header( ws->request, ws->header_size, "x-forwarded-for" );
448 if( fwd && scan_ip6( fwd, proxied_ip ) ) {
449 OT_SETIP( ws->peer, proxied_ip );
450 } else
451 OT_SETIP( ws->peer, cookie->ip );
452 } else
453 #endif
454 OT_SETIP( ws->peer, cookie->ip );
456 ws->peer_id = NULL;
457 ws->hash = NULL;
459 OT_SETPORT( ws->peer, &port );
460 OT_PEERFLAG( ws->peer ) = 0;
461 numwant = 50;
462 scanon = 1;
464 while( scanon ) {
465 switch( scan_find_keywords(keywords_announce, &read_ptr, SCAN_SEARCHPATH_PARAM ) ) {
466 case -2: scanon = 0; break; /* TERMINATOR */
467 case -1: HTTPERROR_400_PARAM; /* PARSE ERROR */
468 case -3: scan_urlencoded_skipvalue( &read_ptr ); break;
469 case 1: /* matched "port" */
470 len = scan_urlencoded_query( &read_ptr, write_ptr = read_ptr, SCAN_SEARCHPATH_VALUE );
471 if( ( len <= 0 ) || scan_fixed_int( write_ptr, len, &tmp ) || ( tmp > 0xffff ) ) HTTPERROR_400_PARAM;
472 port = htons( tmp ); OT_SETPORT( &ws->peer, &port );
473 break;
474 case 2: /* matched "left" */
475 if( ( len = scan_urlencoded_query( &read_ptr, write_ptr = read_ptr, SCAN_SEARCHPATH_VALUE ) ) <= 0 ) HTTPERROR_400_PARAM;
476 if( scan_fixed_int( write_ptr, len, &tmp ) ) tmp = 0;
477 if( !tmp ) OT_PEERFLAG( &ws->peer ) |= PEER_FLAG_SEEDING;
478 break;
479 case 3: /* matched "event" */
480 switch( scan_find_keywords( keywords_announce_event, &read_ptr, SCAN_SEARCHPATH_VALUE ) ) {
481 case -1: HTTPERROR_400_PARAM;
482 case 1: /* matched "completed" */
483 OT_PEERFLAG( &ws->peer ) |= PEER_FLAG_COMPLETED;
484 break;
485 case 2: /* matched "stopped" */
486 OT_PEERFLAG( &ws->peer ) |= PEER_FLAG_STOPPED;
487 break;
488 default:
489 break;
491 break;
492 case 4: /* matched "numwant" */
493 len = scan_urlencoded_query( &read_ptr, write_ptr = read_ptr, SCAN_SEARCHPATH_VALUE );
494 if( ( len <= 0 ) || scan_fixed_int( write_ptr, len, &numwant ) ) HTTPERROR_400_PARAM;
495 if( numwant < 0 ) numwant = 50;
496 if( numwant > 200 ) numwant = 200;
497 break;
498 case 5: /* matched "compact" */
499 len = scan_urlencoded_query( &read_ptr, write_ptr = read_ptr, SCAN_SEARCHPATH_VALUE );
500 if( ( len <= 0 ) || scan_fixed_int( write_ptr, len, &tmp ) ) HTTPERROR_400_PARAM;
501 if( !tmp ) HTTPERROR_400_COMPACT;
502 break;
503 case 6: /* matched "info_hash" */
504 if( ws->hash ) HTTPERROR_400_DOUBLEHASH;
505 /* ignore this, when we have less than 20 bytes */
506 if( scan_urlencoded_query( &read_ptr, write_ptr = read_ptr, SCAN_SEARCHPATH_VALUE ) != 20 ) HTTPERROR_400_PARAM;
507 ws->hash = (ot_hash*)write_ptr;
508 break;
509 #ifdef WANT_IP_FROM_QUERY_STRING
510 case 7: /* matched "ip" */
512 char *tmp_buf1 = ws->reply, *tmp_buf2 = ws->reply+16;
513 len = scan_urlencoded_query( &read_ptr, tmp_buf2, SCAN_SEARCHPATH_VALUE );
514 tmp_buf2[len] = 0;
515 if( ( len <= 0 ) || !scan_ip6( tmp_buf2, tmp_buf1 ) ) HTTPERROR_400_PARAM;
516 OT_SETIP( &ws->peer, tmp_buf1 );
518 break;
519 #endif
520 #ifdef WANT_FULLLOG_NETWORKS
521 case 8: /* matched "lognet" */
523 //if( accesslist_is_blessed( cookie->ip, OT_PERMISSION_MAY_STAT ) ) {
524 char *tmp_buf = ws->reply;
525 ot_net net;
526 signed short parsed, bits;
528 len = scan_urlencoded_query( &read_ptr, tmp_buf, SCAN_SEARCHPATH_VALUE );
529 tmp_buf[len] = 0;
530 if( len <= 0 ) HTTPERROR_400_PARAM;
531 if( *tmp_buf == '-' ) {
532 loglist_reset( );
533 return ws->reply_size = sprintf( ws->reply, "Successfully removed.\n" );
535 parsed = scan_ip6( tmp_buf, net.address );
536 if( !parsed ) HTTPERROR_400_PARAM;
537 if( tmp_buf[parsed++] != '/' )
538 bits = 128;
539 else {
540 parsed = scan_short( tmp_buf + parsed, &bits );
541 if( !parsed ) HTTPERROR_400_PARAM;
542 if( ip6_isv4mapped( net.address ) )
543 bits += 96;
545 net.bits = bits;
546 loglist_add_network( &net );
547 return ws->reply_size = sprintf( ws->reply, "Successfully added.\n" );
550 break;
551 #endif
552 case 9: /* matched "peer_id" */
553 /* ignore this, when we have less than 20 bytes */
554 if( scan_urlencoded_query( &read_ptr, write_ptr = read_ptr, SCAN_SEARCHPATH_VALUE ) != 20 ) HTTPERROR_400_PARAM;
555 ws->peer_id = write_ptr;
556 break;
560 #ifdef WANT_LOG_NUMWANT
561 numwants[numwant]++;
562 #endif
564 /* XXX DEBUG
565 stats_issue_event( EVENT_ACCEPT, FLAG_TCP, (uintptr_t)ws->reply );
568 /* Scanned whole query string */
569 if( !ws->hash )
570 return ws->reply_size = sprintf( ws->reply, "d14:failure reason80:Your client forgot to send your torrent's info_hash. Please upgrade your client.e" );
572 if( OT_PEERFLAG( &ws->peer ) & PEER_FLAG_STOPPED )
573 ws->reply_size = remove_peer_from_torrent( FLAG_TCP, ws );
574 else
575 ws->reply_size = add_peer_to_torrent_and_return_peers( FLAG_TCP, ws, numwant );
577 stats_issue_event( EVENT_ANNOUNCE, FLAG_TCP, ws->reply_size);
578 return ws->reply_size;
581 ssize_t http_handle_request( const int64 sock, struct ot_workstruct *ws ) {
582 ssize_t reply_off, len;
583 char *read_ptr = ws->request, *write_ptr;
585 #ifdef WANT_FULLLOG_NETWORKS
586 struct http_data *cookie = io_getcookie( sock );
587 if( loglist_check_address( cookie->ip ) ) {
588 ot_log *log = malloc( sizeof( ot_log ) );
589 if( log ) {
590 log->size = ws->request_size;
591 log->data = malloc( ws->request_size );
592 log->next = 0;
593 log->time = g_now_seconds;
594 memcpy( log->ip, cookie->ip, sizeof(ot_ip6));
595 if( log->data ) {
596 memcpy( log->data, ws->request, ws->request_size );
597 if( !g_logchain_first )
598 g_logchain_first = g_logchain_last = log;
599 else {
600 g_logchain_last->next = log;
601 g_logchain_last = log;
603 } else
604 free( log );
607 #endif
609 #ifdef _DEBUG_HTTPERROR
610 reply_off = ws->request_size;
611 if( ws->request_size >= G_DEBUGBUF_SIZE )
612 reply_off = G_DEBUGBUF_SIZE - 1;
613 memcpy( ws->debugbuf, ws->request, reply_off );
614 ws->debugbuf[ reply_off ] = 0;
615 #endif
617 /* Tell subroutines where to put reply data */
618 ws->reply = ws->outbuf + SUCCESS_HTTP_HEADER_LENGTH;
620 /* This one implicitely tests strlen < 5, too -- remember, it is \n terminated */
621 if( memcmp( read_ptr, "GET /", 5) ) HTTPERROR_400;
623 /* Skip leading '/' */
624 for( read_ptr+=4; *read_ptr == '/'; ++read_ptr);
626 /* Try to parse the request.
627 In reality we abandoned requiring the url to be correct. This now
628 only decodes url encoded characters, we check for announces and
629 scrapes by looking for "a*" or "sc" */
630 len = scan_urlencoded_query( &read_ptr, write_ptr = read_ptr, SCAN_PATH );
632 /* If parsing returned an error, leave with not found */
633 if( g_redirecturl && ( len == -2 ) ) HTTPERROR_302;
634 if( len <= 0 ) HTTPERROR_404;
636 /* This is the hardcore match for announce*/
637 if( ( *write_ptr == 'a' ) || ( *write_ptr == '?' ) )
638 http_handle_announce( sock, ws, read_ptr );
639 #ifdef WANT_FULLSCRAPE
640 else if( !memcmp( write_ptr, "scrape HTTP/", 12 ) )
641 http_handle_fullscrape( sock, ws );
642 #endif
643 /* This is the hardcore match for scrape */
644 else if( !memcmp( write_ptr, "sc", 2 ) )
645 http_handle_scrape( sock, ws, read_ptr );
646 /* All the rest is matched the standard way */
647 else if( len == g_stats_path_len && !memcmp( write_ptr, g_stats_path, len ) )
648 http_handle_stats( sock, ws, read_ptr );
649 else
650 HTTPERROR_404;
652 /* Find out if the client wants to keep this connection alive */
653 ws->keep_alive = 0;
654 #ifdef WANT_KEEPALIVE
655 read_ptr=http_header( ws->request, ws->header_size, "connection");
656 if( read_ptr && ( *read_ptr == 'K' || *read_ptr == 'k' ) ) ws->keep_alive = 1;
657 #endif
659 /* If routines handled sending themselves, just return */
660 if( ws->reply_size == -2 ) return 0;
661 /* If routine failed, let http error take over */
662 if( ws->reply_size <= 0 ) HTTPERROR_500;
664 /* This one is rather ugly, so I take you step by step through it.
666 1. In order to avoid having two buffers, one for header and one for content, we allow all above functions from trackerlogic to
667 write to a fixed location, leaving SUCCESS_HTTP_HEADER_LENGTH bytes in our work buffer, which is enough for the static string
668 plus dynamic space needed to expand our Content-Length value. We reserve SUCCESS_HTTP_SIZE_OFF for its expansion and calculate
669 the space NOT needed to expand in reply_off
671 reply_off = SUCCESS_HTTP_SIZE_OFF - snprintf( ws->outbuf, 0, "%zd", ws->reply_size );
672 ws->reply = ws->outbuf + reply_off;
674 /* 2. Now we sprintf our header so that sprintf writes its terminating '\0' exactly one byte before content starts. Complete
675 packet size is increased by size of header plus one byte '\n', we will copy over '\0' in next step */
676 ws->reply_size += 1 + sprintf( ws->reply, "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: %zd\r\n\r", ws->reply_size );
678 /* 3. Finally we join both blocks neatly */
679 ws->outbuf[ SUCCESS_HTTP_HEADER_LENGTH - 1 ] = '\n';
681 http_senddata( sock, ws );
682 return ws->reply_size;
685 const char *g_version_http_c = "$Source$: $Revision$\n";