diff options
Diffstat (limited to 'ot_http.c')
-rw-r--r-- | ot_http.c | 544 |
1 files changed, 544 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 | } | ||