diff options
| author | erdgeist <> | 2007-12-03 00:47:09 +0000 |
|---|---|---|
| committer | erdgeist <> | 2007-12-03 00:47:09 +0000 |
| commit | afea7d5ee236e73d340b79f509c090c4f40fc7e0 (patch) | |
| tree | 0f9639f322e503f6ae91bd4dbbc60f45df7d1059 | |
| parent | 0a1cc183588dfd3f02c8ff6519c3214355404b8b (diff) | |
Move http handling to its own sources
| -rw-r--r-- | ot_http.c | 544 | ||||
| -rw-r--r-- | ot_http.h | 28 |
2 files changed, 572 insertions, 0 deletions
diff --git a/ot_http.c b/ot_http.c new file mode 100644 index 0000000..0ae14c1 --- /dev/null +++ b/ot_http.c | |||
| @@ -0,0 +1,544 @@ | |||
| 1 | /* This software was written by Dirk Engling <erdgeist@erdgeist.org> | ||
| 2 | It is considered beerware. Prost. Skol. Cheers or whatever. */ | ||
| 3 | |||
| 4 | /* System */ | ||
| 5 | #include <sys/types.h> | ||
| 6 | #include <sys/uio.h> | ||
| 7 | #include <stdlib.h> | ||
| 8 | #include <stdio.h> | ||
| 9 | #include <string.h> | ||
| 10 | #include <unistd.h> | ||
| 11 | |||
| 12 | /* Libowfat */ | ||
| 13 | #include "byte.h" | ||
| 14 | #include "array.h" | ||
| 15 | #include "iob.h" | ||
| 16 | |||
| 17 | /* Opentracker */ | ||
| 18 | #include "trackerlogic.h" | ||
| 19 | #include "ot_mutex.h" | ||
| 20 | #include "ot_http.h" | ||
| 21 | #include "ot_iovec.h" | ||
| 22 | #include "scan_urlencoded_query.h" | ||
| 23 | #include "ot_fullscrape.h" | ||
| 24 | #include "ot_stats.h" | ||
| 25 | #include "ot_accesslist.h" | ||
| 26 | #include "ot_sync.h" | ||
| 27 | |||
| 28 | #ifndef WANT_TRACKER_SYNC | ||
| 29 | #define add_peer_to_torrent(A,B,C) add_peer_to_torrent(A,B) | ||
| 30 | #endif | ||
| 31 | |||
| 32 | #define OT_MAXMULTISCRAPE_COUNT 64 | ||
| 33 | static ot_hash multiscrape_buf[OT_MAXMULTISCRAPE_COUNT]; | ||
| 34 | |||
| 35 | enum { | ||
| 36 | SUCCESS_HTTP_HEADER_LENGTH = 80, | ||
| 37 | SUCCESS_HTTP_HEADER_LENGHT_CONTENT_ENCODING = 32, | ||
| 38 | SUCCESS_HTTP_SIZE_OFF = 17 }; | ||
| 39 | |||
| 40 | /* Our static output buffer */ | ||
| 41 | static char static_outbuf[8192]; | ||
| 42 | #ifdef _DEBUG_HTTPERROR | ||
| 43 | static char debug_request[8192]; | ||
| 44 | #endif | ||
| 45 | |||
| 46 | static void http_senddata( const int64 client_socket, char *buffer, size_t size ) { | ||
| 47 | struct http_data *h = io_getcookie( client_socket ); | ||
| 48 | ssize_t written_size; | ||
| 49 | |||
| 50 | /* whoever sends data is not interested in its input-array */ | ||
| 51 | if( h && ( h->flag & STRUCT_HTTP_FLAG_ARRAY_USED ) ) { | ||
| 52 | h->flag &= ~STRUCT_HTTP_FLAG_ARRAY_USED; | ||
| 53 | array_reset( &h->request ); | ||
| 54 | } | ||
| 55 | |||
| 56 | written_size = write( client_socket, buffer, size ); | ||
| 57 | if( ( written_size < 0 ) || ( (size_t)written_size == size ) ) { | ||
| 58 | free( h ); io_close( client_socket ); | ||
| 59 | } else { | ||
| 60 | char * outbuf; | ||
| 61 | tai6464 t; | ||
| 62 | |||
| 63 | if( !h ) return; | ||
| 64 | if( !( outbuf = malloc( size - written_size ) ) ) { | ||
| 65 | free(h); io_close( client_socket ); | ||
| 66 | return; | ||
| 67 | } | ||
| 68 | |||
| 69 | iob_reset( &h->batch ); | ||
| 70 | memmove( outbuf, buffer + written_size, size - written_size ); | ||
| 71 | iob_addbuf_free( &h->batch, outbuf, size - written_size ); | ||
| 72 | h->flag |= STRUCT_HTTP_FLAG_IOB_USED; | ||
| 73 | |||
| 74 | /* writeable short data sockets just have a tcp timeout */ | ||
| 75 | taia_uint( &t, 0 ); io_timeout( client_socket, t ); | ||
| 76 | io_dontwantread( client_socket ); | ||
| 77 | io_wantwrite( client_socket ); | ||
| 78 | } | ||
| 79 | } | ||
| 80 | |||
| 81 | #define HTTPERROR_400 return http_issue_error( client_socket, "400 Invalid Request", "This server only understands GET." ) | ||
| 82 | #define HTTPERROR_400_PARAM return http_issue_error( client_socket, "400 Invalid Request", "Invalid parameter" ) | ||
| 83 | #define HTTPERROR_400_COMPACT return http_issue_error( client_socket, "400 Invalid Request", "This server only delivers compact results." ) | ||
| 84 | #define HTTPERROR_403_IP return http_issue_error( client_socket, "403 Access Denied", "Your ip address is not allowed to administrate this server." ) | ||
| 85 | #define HTTPERROR_404 return http_issue_error( client_socket, "404 Not Found", "No such file or directory." ) | ||
| 86 | #define HTTPERROR_500 return http_issue_error( client_socket, "500 Internal Server Error", "A server error has occured. Please retry later." ) | ||
| 87 | ssize_t http_issue_error( const int64 client_socket, const char *title, const char *message ) { | ||
| 88 | size_t reply_size = sprintf( static_outbuf, | ||
| 89 | "HTTP/1.0 %s\r\nContent-Type: text/html\r\nConnection: close\r\nContent-Length: %zd\r\n\r\n<title>%s</title>\n", | ||
| 90 | title, strlen(message)+strlen(title)+16-4,title+4); | ||
| 91 | #ifdef _DEBUG_HTTPERROR | ||
| 92 | fprintf( stderr, "DEBUG: invalid request was: %s\n", debug_request ); | ||
| 93 | #endif | ||
| 94 | http_senddata( client_socket, static_outbuf, reply_size); | ||
| 95 | return -2; | ||
| 96 | } | ||
| 97 | |||
| 98 | ssize_t http_sendiovecdata( const int64 client_socket, int iovec_entries, struct iovec *iovector ) { | ||
| 99 | struct http_data *h = io_getcookie( client_socket ); | ||
| 100 | char *header; | ||
| 101 | int i; | ||
| 102 | size_t header_size, size = iovec_length( &iovec_entries, &iovector ); | ||
| 103 | tai6464 t; | ||
| 104 | |||
| 105 | /* No cookie? Bad socket. Leave. */ | ||
| 106 | if( !h ) { | ||
| 107 | iovec_free( &iovec_entries, &iovector ); | ||
| 108 | HTTPERROR_500; | ||
| 109 | } | ||
| 110 | |||
| 111 | /* If this socket collected request in a buffer, | ||
| 112 | free it now */ | ||
| 113 | if( h->flag & STRUCT_HTTP_FLAG_ARRAY_USED ) { | ||
| 114 | h->flag &= ~STRUCT_HTTP_FLAG_ARRAY_USED; | ||
| 115 | array_reset( &h->request ); | ||
| 116 | } | ||
| 117 | |||
| 118 | /* If we came here, wait for the answer is over */ | ||
| 119 | h->flag &= ~STRUCT_HTTP_FLAG_WAITINGFORTASK; | ||
| 120 | |||
| 121 | /* Our answers never are 0 vectors. Return an error. */ | ||
| 122 | if( !iovec_entries ) { | ||
| 123 | HTTPERROR_500; | ||
| 124 | } | ||
| 125 | |||
| 126 | /* Prepare space for http header */ | ||
| 127 | header = malloc( SUCCESS_HTTP_HEADER_LENGTH + SUCCESS_HTTP_HEADER_LENGHT_CONTENT_ENCODING ); | ||
| 128 | if( !header ) { | ||
| 129 | iovec_free( &iovec_entries, &iovector ); | ||
| 130 | HTTPERROR_500; | ||
| 131 | } | ||
| 132 | |||
| 133 | if( h->flag & STRUCT_HTTP_FLAG_GZIP ) | ||
| 134 | header_size = sprintf( header, "HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\nContent-Encoding: gzip\r\nContent-Length: %zd\r\n\r\n", size ); | ||
| 135 | else if( h->flag & STRUCT_HTTP_FLAG_BZIP2 ) | ||
| 136 | header_size = sprintf( header, "HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\nContent-Encoding: bzip2\r\nContent-Length: %zd\r\n\r\n", size ); | ||
| 137 | else | ||
| 138 | header_size = sprintf( header, "HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\nContent-Length: %zd\r\n\r\n", size ); | ||
| 139 | |||
| 140 | iob_reset( &h->batch ); | ||
| 141 | iob_addbuf_free( &h->batch, header, header_size ); | ||
| 142 | |||
| 143 | /* Will move to ot_iovec.c */ | ||
| 144 | for( i=0; i<iovec_entries; ++i ) | ||
| 145 | iob_addbuf_munmap( &h->batch, iovector[i].iov_base, iovector[i].iov_len ); | ||
| 146 | free( iovector ); | ||
| 147 | |||
| 148 | h->flag |= STRUCT_HTTP_FLAG_IOB_USED; | ||
| 149 | |||
| 150 | /* writeable sockets timeout after twice the pool timeout | ||
| 151 | which defaults to 5 minutes (e.g. after 10 minutes) */ | ||
| 152 | taia_now( &t ); taia_addsec( &t, &t, OT_CLIENT_TIMEOUT_SEND ); | ||
| 153 | io_timeout( client_socket, t ); | ||
| 154 | io_dontwantread( client_socket ); | ||
| 155 | io_wantwrite( client_socket ); | ||
| 156 | return 0; | ||
| 157 | } | ||
| 158 | |||
| 159 | #ifdef WANT_TRACKER_SYNC | ||
| 160 | static ssize_t http_handle_sync( const int64 client_socket, char *data ) { | ||
| 161 | struct http_data* h = io_getcookie( client_socket ); | ||
| 162 | size_t len; | ||
| 163 | int mode = SYNC_OUT, scanon = 1; | ||
| 164 | char *c = data; | ||
| 165 | |||
| 166 | if( !accesslist_isblessed( h->ip, OT_PERMISSION_MAY_SYNC ) ) | ||
| 167 | HTTPERROR_403_IP; | ||
| 168 | |||
| 169 | while( scanon ) { | ||
| 170 | switch( scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_PARAM ) ) { | ||
| 171 | case -2: scanon = 0; break; /* TERMINATOR */ | ||
| 172 | case -1: HTTPERROR_400_PARAM; /* PARSE ERROR */ | ||
| 173 | default: scan_urlencoded_skipvalue( &c ); break; | ||
| 174 | case 9: | ||
| 175 | if(byte_diff(data,9,"changeset")) { | ||
| 176 | scan_urlencoded_skipvalue( &c ); | ||
| 177 | continue; | ||
| 178 | } | ||
| 179 | /* ignore this, when we dont at least see "d4:syncdee" */ | ||
| 180 | if( ( len = scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_VALUE ) ) < 10 ) HTTPERROR_400_PARAM; | ||
| 181 | if( add_changeset_to_tracker( (uint8_t*)data, len ) ) HTTPERROR_400_PARAM; | ||
| 182 | if( mode == SYNC_OUT ) { | ||
| 183 | stats_issue_event( EVENT_SYNC_IN, 1, 0 ); | ||
| 184 | mode = SYNC_IN; | ||
| 185 | } | ||
| 186 | break; | ||
| 187 | } | ||
| 188 | } | ||
| 189 | |||
| 190 | if( mode == SYNC_OUT ) { | ||
| 191 | /* Pass this task to the worker thread */ | ||
| 192 | h->flag |= STRUCT_HTTP_FLAG_WAITINGFORTASK; | ||
| 193 | stats_issue_event( EVENT_SYNC_OUT_REQUEST, 1, 0 ); | ||
| 194 | sync_deliver( client_socket ); | ||
| 195 | io_dontwantread( client_socket ); | ||
| 196 | return -2; | ||
| 197 | } | ||
| 198 | |||
| 199 | /* Simple but proof for now */ | ||
| 200 | memmove( static_outbuf + SUCCESS_HTTP_HEADER_LENGTH, "OK", 2); | ||
| 201 | return 2; | ||
| 202 | } | ||
| 203 | #endif | ||
| 204 | |||
| 205 | static ssize_t http_handle_stats( const int64 client_socket, char *data, char *d, size_t l ) { | ||
| 206 | struct http_data* h = io_getcookie( client_socket ); | ||
| 207 | char *c = data; | ||
| 208 | int mode = TASK_STATS_PEERS, scanon = 1, format = 0; | ||
| 209 | |||
| 210 | while( scanon ) { | ||
| 211 | switch( scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_PARAM ) ) { | ||
| 212 | case -2: scanon = 0; break; /* TERMINATOR */ | ||
| 213 | case -1: HTTPERROR_400_PARAM; /* PARSE ERROR */ | ||
| 214 | default: scan_urlencoded_skipvalue( &c ); break; | ||
| 215 | case 4: | ||
| 216 | if( byte_diff(data,4,"mode")) { | ||
| 217 | scan_urlencoded_skipvalue( &c ); | ||
| 218 | continue; | ||
| 219 | } | ||
| 220 | if( scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_VALUE ) != 4 ) HTTPERROR_400_PARAM; | ||
| 221 | if( !byte_diff(data,4,"peer")) | ||
| 222 | mode = TASK_STATS_PEERS; | ||
| 223 | else if( !byte_diff(data,4,"conn")) | ||
| 224 | mode = TASK_STATS_CONNS; | ||
| 225 | else if( !byte_diff(data,4,"top5")) | ||
| 226 | mode = TASK_STATS_TOP5; | ||
| 227 | else if( !byte_diff(data,4,"scrp")) | ||
| 228 | mode = TASK_STATS_SCRAPE; | ||
| 229 | else if( !byte_diff(data,4,"fscr")) | ||
| 230 | mode = TASK_STATS_FULLSCRAPE; | ||
| 231 | else if( !byte_diff(data,4,"tcp4")) | ||
| 232 | mode = TASK_STATS_TCP; | ||
| 233 | else if( !byte_diff(data,4,"udp4")) | ||
| 234 | mode = TASK_STATS_UDP; | ||
| 235 | else if( !byte_diff(data,4,"s24s")) | ||
| 236 | mode = TASK_STATS_SLASH24S; | ||
| 237 | else if( !byte_diff(data,4,"tpbs")) | ||
| 238 | mode = TASK_STATS_TPB; | ||
| 239 | else | ||
| 240 | HTTPERROR_400_PARAM; | ||
| 241 | break; | ||
| 242 | case 6: | ||
| 243 | if( byte_diff(data,6,"format")) { | ||
| 244 | scan_urlencoded_skipvalue( &c ); | ||
| 245 | continue; | ||
| 246 | } | ||
| 247 | if( scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_VALUE ) != 3 ) HTTPERROR_400_PARAM; | ||
| 248 | if( !byte_diff(data,3,"bin")) | ||
| 249 | format = TASK_FULLSCRAPE_TPB_BINARY; | ||
| 250 | else if( !byte_diff(data,3,"ben")) | ||
| 251 | format = TASK_FULLSCRAPE; | ||
| 252 | else if( !byte_diff(data,3,"url")) | ||
| 253 | format = TASK_FULLSCRAPE_TPB_URLENCODED; | ||
| 254 | else if( !byte_diff(data,3,"txt")) | ||
| 255 | format = TASK_FULLSCRAPE_TPB_ASCII; | ||
| 256 | else | ||
| 257 | HTTPERROR_400_PARAM; | ||
| 258 | break; | ||
| 259 | } | ||
| 260 | } | ||
| 261 | |||
| 262 | if( mode == TASK_STATS_TPB ) { | ||
| 263 | tai6464 t; | ||
| 264 | #ifdef WANT_COMPRESSION_GZIP | ||
| 265 | d[l-1] = 0; | ||
| 266 | if( strstr( d, "gzip" ) ) { | ||
| 267 | h->flag |= STRUCT_HTTP_FLAG_GZIP; | ||
| 268 | format |= TASK_FLAG_GZIP; | ||
| 269 | } | ||
| 270 | #endif | ||
| 271 | /* Pass this task to the worker thread */ | ||
| 272 | h->flag |= STRUCT_HTTP_FLAG_WAITINGFORTASK; | ||
| 273 | |||
| 274 | /* Clients waiting for us should not easily timeout */ | ||
| 275 | taia_uint( &t, 0 ); io_timeout( client_socket, t ); | ||
| 276 | fullscrape_deliver( client_socket, format ); | ||
| 277 | io_dontwantread( client_socket ); | ||
| 278 | return -2; | ||
| 279 | } | ||
| 280 | |||
| 281 | // default format for now | ||
| 282 | if( !( l = return_stats_for_tracker( static_outbuf + SUCCESS_HTTP_HEADER_LENGTH, mode, 0 ) ) ) HTTPERROR_500; | ||
| 283 | return l; | ||
| 284 | } | ||
| 285 | |||
| 286 | static ssize_t http_handle_fullscrape( const int64 client_socket, char *d, size_t l ) { | ||
| 287 | struct http_data* h = io_getcookie( client_socket ); | ||
| 288 | int format = 0; | ||
| 289 | tai6464 t; | ||
| 290 | |||
| 291 | #ifdef WANT_COMPRESSION_GZIP | ||
| 292 | d[l-1] = 0; | ||
| 293 | if( strstr( d, "gzip" ) ) { | ||
| 294 | h->flag |= STRUCT_HTTP_FLAG_GZIP; | ||
| 295 | format = TASK_FLAG_GZIP; | ||
| 296 | stats_issue_event( EVENT_FULLSCRAPE_REQUEST_GZIP, *(int*)h->ip, 0 ); | ||
| 297 | } else | ||
| 298 | #endif | ||
| 299 | stats_issue_event( EVENT_FULLSCRAPE_REQUEST, *(int*)h->ip, 0 ); | ||
| 300 | |||
| 301 | #ifdef _DEBUG_HTTPERROR | ||
| 302 | write( 2, debug_request, l ); | ||
| 303 | #endif | ||
| 304 | |||
| 305 | /* Pass this task to the worker thread */ | ||
| 306 | h->flag |= STRUCT_HTTP_FLAG_WAITINGFORTASK; | ||
| 307 | /* Clients waiting for us should not easily timeout */ | ||
| 308 | taia_uint( &t, 0 ); io_timeout( client_socket, t ); | ||
| 309 | fullscrape_deliver( client_socket, TASK_FULLSCRAPE | format ); | ||
| 310 | io_dontwantread( client_socket ); | ||
| 311 | return -2; | ||
| 312 | } | ||
| 313 | |||
| 314 | static ssize_t http_handle_scrape( const int64 client_socket, char *data ) { | ||
| 315 | int scanon = 1, numwant = 0; | ||
| 316 | char *c = data; | ||
| 317 | size_t l; | ||
| 318 | |||
| 319 | /* This is to hack around stupid clients that send "scrape ?info_hash" */ | ||
| 320 | if( c[-1] != '?' ) { | ||
| 321 | while( ( *c != '?' ) && ( *c != '\n' ) ) ++c; | ||
| 322 | if( *c == '\n' ) HTTPERROR_400_PARAM; | ||
| 323 | ++c; | ||
| 324 | } | ||
| 325 | |||
| 326 | while( scanon ) { | ||
| 327 | switch( scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_PARAM ) ) { | ||
| 328 | case -2: scanon = 0; break; /* TERMINATOR */ | ||
| 329 | case -1: | ||
| 330 | if( numwant ) | ||
| 331 | goto UTORRENT1600_WORKAROUND; | ||
| 332 | HTTPERROR_400_PARAM; /* PARSE ERROR */ | ||
| 333 | default: scan_urlencoded_skipvalue( &c ); break; | ||
| 334 | case 9: | ||
| 335 | if(byte_diff(data,9,"info_hash")) { | ||
| 336 | scan_urlencoded_skipvalue( &c ); | ||
| 337 | continue; | ||
| 338 | } | ||
| 339 | /* ignore this, when we have less than 20 bytes */ | ||
| 340 | if( scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_VALUE ) != (ssize_t)sizeof(ot_hash) ) { | ||
| 341 | #ifdef WANT_UTORRENT1600_WORKAROUND | ||
| 342 | if( data[20] != '?' ) | ||
| 343 | #endif | ||
| 344 | HTTPERROR_400_PARAM; | ||
| 345 | } | ||
| 346 | if( numwant < OT_MAXMULTISCRAPE_COUNT ) | ||
| 347 | memmove( multiscrape_buf + numwant++, data, sizeof(ot_hash) ); | ||
| 348 | break; | ||
| 349 | } | ||
| 350 | } | ||
| 351 | |||
| 352 | UTORRENT1600_WORKAROUND: | ||
| 353 | |||
| 354 | /* No info_hash found? Inform user */ | ||
| 355 | if( !numwant ) HTTPERROR_400_PARAM; | ||
| 356 | |||
| 357 | /* Enough for http header + whole scrape string */ | ||
| 358 | if( !( l = return_tcp_scrape_for_torrent( multiscrape_buf, numwant, SUCCESS_HTTP_HEADER_LENGTH + static_outbuf ) ) ) HTTPERROR_500; | ||
| 359 | stats_issue_event( EVENT_SCRAPE, 1, l ); | ||
| 360 | return l; | ||
| 361 | } | ||
| 362 | |||
| 363 | static ssize_t http_handle_announce( const int64 client_socket, char *data ) { | ||
| 364 | char *c = data; | ||
| 365 | int numwant, tmp, scanon; | ||
| 366 | ot_peer peer; | ||
| 367 | ot_torrent *torrent; | ||
| 368 | ot_hash *hash = NULL; | ||
| 369 | unsigned short port = htons(6881); | ||
| 370 | ssize_t len; | ||
| 371 | |||
| 372 | /* This is to hack around stupid clients that send "announce ?info_hash" */ | ||
| 373 | if( c[-1] != '?' ) { | ||
| 374 | while( ( *c != '?' ) && ( *c != '\n' ) ) ++c; | ||
| 375 | if( *c == '\n' ) HTTPERROR_400_PARAM; | ||
| 376 | ++c; | ||
| 377 | } | ||
| 378 | |||
| 379 | OT_SETIP( &peer, ((struct http_data*)io_getcookie( client_socket ) )->ip ); | ||
| 380 | OT_SETPORT( &peer, &port ); | ||
| 381 | OT_FLAG( &peer ) = 0; | ||
| 382 | numwant = 50; | ||
| 383 | scanon = 1; | ||
| 384 | |||
| 385 | while( scanon ) { | ||
| 386 | switch( scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_PARAM ) ) { | ||
| 387 | case -2: scanon = 0; break; /* TERMINATOR */ | ||
| 388 | case -1: HTTPERROR_400_PARAM; /* PARSE ERROR */ | ||
| 389 | default: scan_urlencoded_skipvalue( &c ); break; | ||
| 390 | #ifdef WANT_IP_FROM_QUERY_STRING | ||
| 391 | case 2: | ||
| 392 | if(!byte_diff(data,2,"ip")) { | ||
| 393 | unsigned char ip[4]; | ||
| 394 | len = scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_VALUE ); | ||
| 395 | if( ( len <= 0 ) || scan_fixed_ip( data, len, ip ) ) HTTPERROR_400_PARAM; | ||
| 396 | OT_SETIP( &peer, ip ); | ||
| 397 | } else | ||
| 398 | scan_urlencoded_skipvalue( &c ); | ||
| 399 | break; | ||
| 400 | #endif | ||
| 401 | case 4: | ||
| 402 | if( !byte_diff( data, 4, "port" ) ) { | ||
| 403 | len = scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_VALUE ); | ||
| 404 | if( ( len <= 0 ) || scan_fixed_int( data, len, &tmp ) || ( tmp > 0xffff ) ) HTTPERROR_400_PARAM; | ||
| 405 | port = htons( tmp ); OT_SETPORT( &peer, &port ); | ||
| 406 | } else if( !byte_diff( data, 4, "left" ) ) { | ||
| 407 | if( ( len = scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_VALUE ) ) <= 0 ) HTTPERROR_400_PARAM; | ||
| 408 | if( scan_fixed_int( data, len, &tmp ) ) tmp = 0; | ||
| 409 | if( !tmp ) OT_FLAG( &peer ) |= PEER_FLAG_SEEDING; | ||
| 410 | } else | ||
| 411 | scan_urlencoded_skipvalue( &c ); | ||
| 412 | break; | ||
| 413 | case 5: | ||
| 414 | if( byte_diff( data, 5, "event" ) ) | ||
| 415 | scan_urlencoded_skipvalue( &c ); | ||
| 416 | else switch( scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_VALUE ) ) { | ||
| 417 | case -1: | ||
| 418 | HTTPERROR_400_PARAM; | ||
| 419 | case 7: | ||
| 420 | if( !byte_diff( data, 7, "stopped" ) ) OT_FLAG( &peer ) |= PEER_FLAG_STOPPED; | ||
| 421 | break; | ||
| 422 | case 9: | ||
| 423 | if( !byte_diff( data, 9, "completed" ) ) OT_FLAG( &peer ) |= PEER_FLAG_COMPLETED; | ||
| 424 | default: /* Fall through intended */ | ||
| 425 | break; | ||
| 426 | } | ||
| 427 | break; | ||
| 428 | case 7: | ||
| 429 | if(!byte_diff(data,7,"numwant")) { | ||
| 430 | len = scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_VALUE ); | ||
| 431 | if( ( len <= 0 ) || scan_fixed_int( data, len, &numwant ) ) HTTPERROR_400_PARAM; | ||
| 432 | if( numwant < 0 ) numwant = 50; | ||
| 433 | if( numwant > 200 ) numwant = 200; | ||
| 434 | } else if(!byte_diff(data,7,"compact")) { | ||
| 435 | len = scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_VALUE ); | ||
| 436 | if( ( len <= 0 ) || scan_fixed_int( data, len, &tmp ) ) HTTPERROR_400_PARAM; | ||
| 437 | if( !tmp ) HTTPERROR_400_COMPACT; | ||
| 438 | } else | ||
| 439 | scan_urlencoded_skipvalue( &c ); | ||
| 440 | break; | ||
| 441 | case 9: | ||
| 442 | if(byte_diff(data,9,"info_hash")) { | ||
| 443 | scan_urlencoded_skipvalue( &c ); | ||
| 444 | continue; | ||
| 445 | } | ||
| 446 | /* ignore this, when we have less than 20 bytes */ | ||
| 447 | if( scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_VALUE ) != 20 ) HTTPERROR_400_PARAM; | ||
| 448 | hash = (ot_hash*)data; | ||
| 449 | break; | ||
| 450 | } | ||
| 451 | } | ||
| 452 | |||
| 453 | /* Scanned whole query string */ | ||
| 454 | if( !hash ) | ||
| 455 | return sprintf( static_outbuf + SUCCESS_HTTP_HEADER_LENGTH, "d14:failure reason81:Your client forgot to send your torrent's info_hash. Please upgrade your client.e" ); | ||
| 456 | |||
| 457 | if( OT_FLAG( &peer ) & PEER_FLAG_STOPPED ) | ||
| 458 | len = remove_peer_from_torrent( hash, &peer, SUCCESS_HTTP_HEADER_LENGTH + static_outbuf, 1 ); | ||
| 459 | else { | ||
| 460 | torrent = add_peer_to_torrent( hash, &peer, 0 ); | ||
| 461 | if( !torrent || !( len = return_peers_for_torrent( hash, numwant, SUCCESS_HTTP_HEADER_LENGTH + static_outbuf, 1 ) ) ) HTTPERROR_500; | ||
| 462 | } | ||
| 463 | stats_issue_event( EVENT_ANNOUNCE, 1, len); | ||
| 464 | return len; | ||
| 465 | } | ||
| 466 | |||
| 467 | ssize_t http_handle_request( const int64 client_socket, char *data, size_t recv_length ) { | ||
| 468 | char *c, *recv_header=data; | ||
| 469 | ssize_t reply_size = 0, reply_off, len; | ||
| 470 | |||
| 471 | #ifdef _DEBUG_HTTPERROR | ||
| 472 | if( recv_length >= sizeof( debug_request ) ) | ||
| 473 | recv_length = sizeof( debug_request) - 1; | ||
| 474 | memcpy( debug_request, recv_header, recv_length ); | ||
| 475 | debug_request[ recv_length ] = 0; | ||
| 476 | #endif | ||
| 477 | |||
| 478 | /* This one implicitely tests strlen < 5, too -- remember, it is \n terminated */ | ||
| 479 | if( byte_diff( data, 5, "GET /") ) HTTPERROR_400; | ||
| 480 | |||
| 481 | /* Query string MUST terminate with SP -- we know that theres at least a '\n' where this search terminates */ | ||
| 482 | for( c = data + 5; *c!=' ' && *c != '\t' && *c != '\n' && *c != '\r'; ++c ) ; | ||
| 483 | if( *c != ' ' ) HTTPERROR_400; | ||
| 484 | |||
| 485 | /* Skip leading '/' */ | ||
| 486 | for( c = data+4; *c == '/'; ++c); | ||
| 487 | |||
| 488 | /* Try to parse the request. | ||
| 489 | In reality we abandoned requiring the url to be correct. This now | ||
| 490 | only decodes url encoded characters, we check for announces and | ||
| 491 | scrapes by looking for "a*" or "sc" */ | ||
| 492 | len = scan_urlencoded_query( &c, data = c, SCAN_PATH ); | ||
| 493 | |||
| 494 | /* If parsing returned an error, leave with not found*/ | ||
| 495 | if( len <= 0 ) HTTPERROR_404; | ||
| 496 | |||
| 497 | /* This is the hardcore match for announce*/ | ||
| 498 | if( ( *data == 'a' ) || ( *data == '?' ) ) | ||
| 499 | reply_size = http_handle_announce( client_socket, c ); | ||
| 500 | else if( !byte_diff( data, 12, "scrape HTTP/" ) ) | ||
| 501 | reply_size = http_handle_fullscrape( client_socket, recv_header, recv_length ); | ||
| 502 | /* This is the hardcore match for scrape */ | ||
| 503 | else if( !byte_diff( data, 2, "sc" ) ) | ||
| 504 | reply_size = http_handle_scrape( client_socket, c ); | ||
| 505 | /* All the rest is matched the standard way */ | ||
| 506 | else switch( len ) { | ||
| 507 | #ifdef WANT_TRACKER_SYNC | ||
| 508 | case 4: /* sync ? */ | ||
| 509 | if( byte_diff( data, 4, "sync") ) HTTPERROR_404; | ||
| 510 | reply_size = http_handle_sync( client_socket, c ); | ||
| 511 | break; | ||
| 512 | #endif | ||
| 513 | case 5: /* stats ? */ | ||
| 514 | if( byte_diff(data,5,"stats")) HTTPERROR_404; | ||
| 515 | reply_size = http_handle_stats( client_socket, c, recv_header, recv_length ); | ||
| 516 | break; | ||
| 517 | default: | ||
| 518 | HTTPERROR_404; | ||
| 519 | } | ||
| 520 | |||
| 521 | /* If routines handled sending themselves, just return */ | ||
| 522 | if( reply_size == -2 ) return 0; | ||
| 523 | /* If routine failed, let http error take over */ | ||
| 524 | if( reply_size == -1 ) HTTPERROR_500; | ||
| 525 | |||
| 526 | /* This one is rather ugly, so I take you step by step through it. | ||
| 527 | |||
| 528 | 1. In order to avoid having two buffers, one for header and one for content, we allow all above functions from trackerlogic to | ||
| 529 | write to a fixed location, leaving SUCCESS_HTTP_HEADER_LENGTH bytes in our static buffer, which is enough for the static string | ||
| 530 | plus dynamic space needed to expand our Content-Length value. We reserve SUCCESS_HTTP_SIZE_OFF for its expansion and calculate | ||
| 531 | the space NOT needed to expand in reply_off | ||
| 532 | */ | ||
| 533 | reply_off = SUCCESS_HTTP_SIZE_OFF - snprintf( static_outbuf, 0, "%zd", reply_size ); | ||
| 534 | |||
| 535 | /* 2. Now we sprintf our header so that sprintf writes its terminating '\0' exactly one byte before content starts. Complete | ||
| 536 | packet size is increased by size of header plus one byte '\n', we will copy over '\0' in next step */ | ||
| 537 | reply_size += 1 + sprintf( static_outbuf + reply_off, "HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\nContent-Length: %zd\r\n\r", reply_size ); | ||
| 538 | |||
| 539 | /* 3. Finally we join both blocks neatly */ | ||
| 540 | static_outbuf[ SUCCESS_HTTP_HEADER_LENGTH - 1 ] = '\n'; | ||
| 541 | |||
| 542 | http_senddata( client_socket, static_outbuf + reply_off, reply_size ); | ||
| 543 | return reply_size; | ||
| 544 | } | ||
diff --git a/ot_http.h b/ot_http.h new file mode 100644 index 0000000..84b9028 --- /dev/null +++ b/ot_http.h | |||
| @@ -0,0 +1,28 @@ | |||
| 1 | /* This software was written by Dirk Engling <erdgeist@erdgeist.org> | ||
| 2 | It is considered beerware. Prost. Skol. Cheers or whatever. */ | ||
| 3 | |||
| 4 | #ifndef __OT_HTTP_H__ | ||
| 5 | #define __OT_HTTP_H__ | ||
| 6 | |||
| 7 | typedef enum { | ||
| 8 | STRUCT_HTTP_FLAG_ARRAY_USED = 1, | ||
| 9 | STRUCT_HTTP_FLAG_IOB_USED = 2, | ||
| 10 | STRUCT_HTTP_FLAG_WAITINGFORTASK = 4, | ||
| 11 | STRUCT_HTTP_FLAG_GZIP = 8, | ||
| 12 | STRUCT_HTTP_FLAG_BZIP2 = 16 | ||
| 13 | } STRUCT_HTTP_FLAG; | ||
| 14 | |||
| 15 | struct http_data { | ||
| 16 | union { | ||
| 17 | array request; | ||
| 18 | io_batch batch; | ||
| 19 | }; | ||
| 20 | char ip[4]; | ||
| 21 | STRUCT_HTTP_FLAG flag; | ||
| 22 | }; | ||
| 23 | |||
| 24 | ssize_t http_handle_request( const int64 s, char *data, size_t l ); | ||
| 25 | ssize_t http_sendiovecdata( const int64 s, int iovec_entries, struct iovec *iovector ); | ||
| 26 | ssize_t http_issue_error( const int64 s, const char *title, const char *message ); | ||
| 27 | |||
| 28 | #endif \ No newline at end of file | ||
