2 * This file Copyright (C) Mnemosyne LLC
4 * This file is licensed by the GPL version 2. Works owned by the
5 * Transmission project are granted a special exemption to clause 2(b)
6 * so that the bulk of its code can remain under the MIT license.
7 * This exemption does not extend to derived works not owned by
8 * the Transmission project.
10 * $Id: web.c 13245 2012-03-04 13:21:42Z jordan $
13 #include <string.h> /* strlen(), strstr() */
14 #include <stdlib.h> /* getenv() */
19 #include <sys/select.h>
22 #include <curl/curl.h>
24 #include <event2/buffer.h>
26 #include "transmission.h"
27 #include "net.h" /* tr_address */
28 #include "platform.h" /* mutex */
30 #include "trevent.h" /* tr_runInEventThread() */
32 #include "version.h" /* User-Agent */
35 #if LIBCURL_VERSION_NUM >= 0x070F06 /* CURLOPT_SOCKOPT* was added in 7.15.6 */
36 #define USE_LIBCURL_SOCKOPT
41 THREADFUNC_MAX_SLEEP_MSEC
= 1000,
47 fprintf( stderr, __VA_ARGS__ ); \
48 fprintf( stderr, "\n" ); \
51 #define dbgmsg( ... ) \
53 if( tr_deepLoggingIsActive( ) ) \
54 tr_deepLog( __FILE__, __LINE__, "web", __VA_ARGS__ ); \
68 struct evbuffer
* response
;
69 struct evbuffer
* freebuf
;
74 tr_web_done_func
* done_func
;
75 void * done_func_user_data
;
77 struct tr_web_task
* next
;
81 task_free( struct tr_web_task
* task
)
84 evbuffer_free( task
->freebuf
);
85 tr_free( task
->cookies
);
86 tr_free( task
->range
);
99 const char * curl_ca_bundle
;
101 struct tr_web_task
* tasks
;
103 char * cookie_filename
;
111 writeFunc( void * ptr
, size_t size
, size_t nmemb
, void * vtask
)
113 const size_t byteCount
= size
* nmemb
;
114 struct tr_web_task
* task
= vtask
;
115 evbuffer_add( task
->response
, ptr
, byteCount
);
116 dbgmsg( "wrote %zu bytes to task %p's buffer", byteCount
, task
);
120 #ifdef USE_LIBCURL_SOCKOPT
122 sockoptfunction( void * vtask
, curl_socket_t fd
, curlsocktype purpose UNUSED
)
124 struct tr_web_task
* task
= vtask
;
125 const bool isScrape
= strstr( task
->url
, "scrape" ) != NULL
;
126 const bool isAnnounce
= strstr( task
->url
, "announce" ) != NULL
;
128 /* announce and scrape requests have tiny payloads. */
129 if( isScrape
|| isAnnounce
)
131 const int sndbuf
= 1024;
132 const int rcvbuf
= isScrape
? 2048 : 3072;
133 setsockopt( fd
, SOL_SOCKET
, SO_SNDBUF
, &sndbuf
, sizeof(sndbuf
) );
134 setsockopt( fd
, SOL_SOCKET
, SO_RCVBUF
, &rcvbuf
, sizeof(rcvbuf
) );
137 /* return nonzero if this function encountered an error */
143 getTimeoutFromURL( const struct tr_web_task
* task
)
146 const tr_session
* session
= task
->session
;
148 if( !session
|| session
->isClosed
) timeout
= 20L;
149 else if( strstr( task
->url
, "scrape" ) != NULL
) timeout
= 30L;
150 else if( strstr( task
->url
, "announce" ) != NULL
) timeout
= 90L;
157 createEasy( tr_session
* s
, struct tr_web
* web
, struct tr_web_task
* task
)
159 bool is_default_value
;
160 const tr_address
* addr
;
161 CURL
* e
= task
->curl_easy
= curl_easy_init( );
163 task
->timeout_secs
= getTimeoutFromURL( task
);
165 curl_easy_setopt( e
, CURLOPT_AUTOREFERER
, 1L );
166 curl_easy_setopt( e
, CURLOPT_COOKIEFILE
, web
->cookie_filename
);
167 curl_easy_setopt( e
, CURLOPT_ENCODING
, "gzip;q=1.0, deflate, identity" );
168 curl_easy_setopt( e
, CURLOPT_FOLLOWLOCATION
, 1L );
169 curl_easy_setopt( e
, CURLOPT_MAXREDIRS
, -1L );
170 curl_easy_setopt( e
, CURLOPT_NOSIGNAL
, 1L );
171 curl_easy_setopt( e
, CURLOPT_PRIVATE
, task
);
172 #ifdef USE_LIBCURL_SOCKOPT
173 curl_easy_setopt( e
, CURLOPT_SOCKOPTFUNCTION
, sockoptfunction
);
174 curl_easy_setopt( e
, CURLOPT_SOCKOPTDATA
, task
);
176 if( web
->curl_ssl_verify
)
177 curl_easy_setopt( e
, CURLOPT_CAINFO
, web
->curl_ca_bundle
);
179 curl_easy_setopt( e
, CURLOPT_SSL_VERIFYHOST
, 0L );
180 curl_easy_setopt( e
, CURLOPT_SSL_VERIFYPEER
, 0L );
182 curl_easy_setopt( e
, CURLOPT_TIMEOUT
, task
->timeout_secs
);
183 curl_easy_setopt( e
, CURLOPT_URL
, task
->url
);
184 curl_easy_setopt( e
, CURLOPT_USERAGENT
, TR_NAME
"/" SHORT_VERSION_STRING
);
185 curl_easy_setopt( e
, CURLOPT_VERBOSE
, (long)(web
->curl_verbose
?1:0) );
186 curl_easy_setopt( e
, CURLOPT_WRITEDATA
, task
);
187 curl_easy_setopt( e
, CURLOPT_WRITEFUNCTION
, writeFunc
);
189 if((( addr
= tr_sessionGetPublicAddress( s
, TR_AF_INET
, &is_default_value
))) && !is_default_value
)
190 curl_easy_setopt( e
, CURLOPT_INTERFACE
, tr_address_to_string( addr
) );
191 else if ((( addr
= tr_sessionGetPublicAddress( s
, TR_AF_INET6
, &is_default_value
))) && !is_default_value
)
192 curl_easy_setopt( e
, CURLOPT_INTERFACE
, tr_address_to_string( addr
) );
194 if( task
->cookies
!= NULL
)
195 curl_easy_setopt( e
, CURLOPT_COOKIE
, task
->cookies
);
197 if( task
->range
!= NULL
) {
198 curl_easy_setopt( e
, CURLOPT_RANGE
, task
->range
);
199 /* don't bother asking the server to compress webseed fragments */
200 curl_easy_setopt( e
, CURLOPT_ENCODING
, "identity" );
211 task_finish_func( void * vtask
)
213 struct tr_web_task
* task
= vtask
;
214 dbgmsg( "finished web task %p; got %ld", task
, task
->code
);
216 if( task
->done_func
!= NULL
)
217 task
->done_func( task
->session
,
221 evbuffer_pullup( task
->response
, -1 ),
222 evbuffer_get_length( task
->response
),
223 task
->done_func_user_data
);
233 tr_webRun( tr_session
* session
,
236 const char * cookies
,
237 tr_web_done_func done_func
,
238 void * done_func_user_data
)
240 return tr_webRunWithBuffer( session
, url
, range
, cookies
,
241 done_func
, done_func_user_data
,
246 tr_webRunWithBuffer( tr_session
* session
,
249 const char * cookies
,
250 tr_web_done_func done_func
,
251 void * done_func_user_data
,
252 struct evbuffer
* buffer
)
254 struct tr_web
* web
= session
->web
;
258 struct tr_web_task
* task
= tr_new0( struct tr_web_task
, 1 );
260 task
->session
= session
;
261 task
->url
= tr_strdup( url
);
262 task
->range
= tr_strdup( range
);
263 task
->cookies
= tr_strdup( cookies
);
264 task
->done_func
= done_func
;
265 task
->done_func_user_data
= done_func_user_data
;
266 task
->response
= buffer
? buffer
: evbuffer_new( );
267 task
->freebuf
= buffer
? NULL
: task
->response
;
269 tr_lockLock( web
->taskLock
);
270 task
->next
= web
->tasks
;
272 tr_lockUnlock( web
->taskLock
);
279 * Portability wrapper for select().
281 * http://msdn.microsoft.com/en-us/library/ms740141%28VS.85%29.aspx
282 * On win32, any two of the parameters, readfds, writefds, or exceptfds,
283 * can be given as null. At least one must be non-null, and any non-null
284 * descriptor set must contain at least one handle to a socket.
288 fd_set
* r_fd_set
, fd_set
* w_fd_set
, fd_set
* c_fd_set
,
292 if( !r_fd_set
->fd_count
&& !w_fd_set
->fd_count
&& !c_fd_set
->fd_count
)
294 const long int msec
= t
->tv_sec
*1000 + t
->tv_usec
/1000;
295 tr_wait_msec( msec
);
297 else if( select( 0, r_fd_set
->fd_count
? r_fd_set
: NULL
,
298 w_fd_set
->fd_count
? w_fd_set
: NULL
,
299 c_fd_set
->fd_count
? c_fd_set
: NULL
, t
) < 0 )
302 const int e
= EVUTIL_SOCKET_ERROR( );
303 tr_net_strerror( errstr
, sizeof( errstr
), e
);
304 dbgmsg( "Error: select (%d) %s", e
, errstr
);
307 select( nfds
, r_fd_set
, w_fd_set
, c_fd_set
, t
);
312 tr_webThreadFunc( void * vsession
)
317 struct tr_web_task
* task
;
318 tr_session
* session
= vsession
;
320 /* try to enable ssl for https support; but if that fails,
321 * try a plain vanilla init */
322 if( curl_global_init( CURL_GLOBAL_SSL
) )
323 curl_global_init( 0 );
325 web
= tr_new0( struct tr_web
, 1 );
326 web
->close_mode
= ~0;
327 web
->taskLock
= tr_lockNew( );
329 web
->curl_verbose
= getenv( "TR_CURL_VERBOSE" ) != NULL
;
330 web
->curl_ssl_verify
= getenv( "TR_CURL_SSL_VERIFY" ) != NULL
;
331 web
->curl_ca_bundle
= getenv( "CURL_CA_BUNDLE" );
332 if( web
->curl_ssl_verify
) {
333 tr_ninf( "web", "will verify tracker certs using envvar CURL_CA_BUNDLE: %s",
334 web
->curl_ca_bundle
== NULL
? "none" : web
->curl_ca_bundle
);
335 tr_ninf( "web", "NB: this only works if you built against libcurl with openssl or gnutls, NOT nss" );
336 tr_ninf( "web", "NB: invalid certs will show up as 'Could not connect to tracker' like many other errors" );
338 web
->cookie_filename
= tr_buildPath( session
->configDir
, "cookies.txt", NULL
);
340 multi
= curl_multi_init( );
350 if( web
->close_mode
== TR_WEB_CLOSE_NOW
)
352 if( ( web
->close_mode
== TR_WEB_CLOSE_WHEN_IDLE
) && ( web
->tasks
== NULL
) )
355 /* add tasks from the queue */
356 tr_lockLock( web
->taskLock
);
357 while( web
->tasks
!= NULL
)
361 web
->tasks
= task
->next
;
364 dbgmsg( "adding task to curl: [%s]", task
->url
);
365 curl_multi_add_handle( multi
, createEasy( session
, web
, task
));
366 /*fprintf( stderr, "adding a task.. taskCount is now %d\n", taskCount );*/
369 tr_lockUnlock( web
->taskLock
);
371 /* maybe wait a little while before calling curl_multi_perform() */
373 curl_multi_timeout( multi
, &msec
);
375 msec
= THREADFUNC_MAX_SLEEP_MSEC
;
376 if( session
->isClosed
)
377 msec
= 100; /* on shutdown, call perform() more frequently */
383 fd_set r_fd_set
, w_fd_set
, c_fd_set
;
386 FD_ZERO( &r_fd_set
);
387 FD_ZERO( &w_fd_set
);
388 FD_ZERO( &c_fd_set
);
389 curl_multi_fdset( multi
, &r_fd_set
, &w_fd_set
, &c_fd_set
, &max_fd
);
391 if( msec
> THREADFUNC_MAX_SLEEP_MSEC
)
392 msec
= THREADFUNC_MAX_SLEEP_MSEC
;
395 t
.tv_sec
= usec
/ 1000000;
396 t
.tv_usec
= usec
% 1000000;
397 tr_select( max_fd
+1, &r_fd_set
, &w_fd_set
, &c_fd_set
, &t
);
400 /* call curl_multi_perform() */
402 mcode
= curl_multi_perform( multi
, &unused
);
403 } while( mcode
== CURLM_CALL_MULTI_PERFORM
);
405 /* pump completed tasks from the multi */
406 while(( msg
= curl_multi_info_read( multi
, &unused
)))
408 if(( msg
->msg
== CURLMSG_DONE
) && ( msg
->easy_handle
!= NULL
))
411 struct tr_web_task
* task
;
413 CURL
* e
= msg
->easy_handle
;
414 curl_easy_getinfo( e
, CURLINFO_PRIVATE
, (void*)&task
);
415 curl_easy_getinfo( e
, CURLINFO_RESPONSE_CODE
, &task
->code
);
416 curl_easy_getinfo( e
, CURLINFO_REQUEST_SIZE
, &req_bytes_sent
);
417 curl_easy_getinfo( e
, CURLINFO_TOTAL_TIME
, &total_time
);
418 task
->did_connect
= task
->code
>0 || req_bytes_sent
>0;
419 task
->did_timeout
= !task
->code
&& ( total_time
>= task
->timeout_secs
);
420 curl_multi_remove_handle( multi
, e
);
421 curl_easy_cleanup( e
);
422 /*fprintf( stderr, "removing a completed task.. taskCount is now %d (response code: %d, response len: %d)\n", taskCount, (int)task->code, (int)evbuffer_get_length(task->response) );*/
423 tr_runInEventThread( task
->session
, task_finish_func
, task
);
429 /* Discard any remaining tasks.
430 * This is rare, but can happen on shutdown with unresponsive trackers. */
431 while( web
->tasks
!= NULL
) {
433 web
->tasks
= task
->next
;
434 dbgmsg( "Discarding task \"%s\"", task
->url
);
439 curl_multi_cleanup( multi
);
440 tr_lockFree( web
->taskLock
);
441 tr_free( web
->cookie_filename
);
447 tr_webInit( tr_session
* session
)
449 tr_threadNew( tr_webThreadFunc
, session
);
453 tr_webClose( tr_session
* session
, tr_web_close_mode close_mode
)
455 if( session
->web
!= NULL
)
457 session
->web
->close_mode
= close_mode
;
459 if( close_mode
== TR_WEB_CLOSE_NOW
)
460 while( session
->web
!= NULL
)
466 tr_webGetTaskInfo( struct tr_web_task
* task
, tr_web_task_info info
, void * dst
)
468 curl_easy_getinfo( task
->curl_easy
, (CURLINFO
) info
, dst
);
477 tr_webGetResponseStr( long code
)
481 case 0: return "No Response";
482 case 101: return "Switching Protocols";
483 case 200: return "OK";
484 case 201: return "Created";
485 case 202: return "Accepted";
486 case 203: return "Non-Authoritative Information";
487 case 204: return "No Content";
488 case 205: return "Reset Content";
489 case 206: return "Partial Content";
490 case 300: return "Multiple Choices";
491 case 301: return "Moved Permanently";
492 case 302: return "Found";
493 case 303: return "See Other";
494 case 304: return "Not Modified";
495 case 305: return "Use Proxy";
496 case 306: return "(Unused)";
497 case 307: return "Temporary Redirect";
498 case 400: return "Bad Request";
499 case 401: return "Unauthorized";
500 case 402: return "Payment Required";
501 case 403: return "Forbidden";
502 case 404: return "Not Found";
503 case 405: return "Method Not Allowed";
504 case 406: return "Not Acceptable";
505 case 407: return "Proxy Authentication Required";
506 case 408: return "Request Timeout";
507 case 409: return "Conflict";
508 case 410: return "Gone";
509 case 411: return "Length Required";
510 case 412: return "Precondition Failed";
511 case 413: return "Request Entity Too Large";
512 case 414: return "Request-URI Too Long";
513 case 415: return "Unsupported Media Type";
514 case 416: return "Requested Range Not Satisfiable";
515 case 417: return "Expectation Failed";
516 case 500: return "Internal Server Error";
517 case 501: return "Not Implemented";
518 case 502: return "Bad Gateway";
519 case 503: return "Service Unavailable";
520 case 504: return "Gateway Timeout";
521 case 505: return "HTTP Version Not Supported";
522 default: return "Unknown Error";
527 tr_http_escape( struct evbuffer
* out
,
528 const char * str
, int len
, bool escape_slashes
)
532 if( ( len
< 0 ) && ( str
!= NULL
) )
535 for( end
=str
+len
; str
&& str
!=end
; ++str
) {
539 || ( ( '0' <= *str
) && ( *str
<= '9' ) )
540 || ( ( 'A' <= *str
) && ( *str
<= 'Z' ) )
541 || ( ( 'a' <= *str
) && ( *str
<= 'z' ) )
542 || ( ( *str
== '/' ) && ( !escape_slashes
) ) )
543 evbuffer_add_printf( out
, "%c", *str
);
545 evbuffer_add_printf( out
, "%%%02X", (unsigned)(*str
&0xFF) );
550 tr_http_unescape( const char * str
, int len
)
552 char * tmp
= curl_unescape( str
, len
);
553 char * ret
= tr_strdup( tmp
);
559 is_rfc2396_alnum( uint8_t ch
)
561 return ( '0' <= ch
&& ch
<= '9' )
562 || ( 'A' <= ch
&& ch
<= 'Z' )
563 || ( 'a' <= ch
&& ch
<= 'z' )
571 tr_http_escape_sha1( char * out
, const uint8_t * sha1_digest
)
573 const uint8_t * in
= sha1_digest
;
574 const uint8_t * end
= in
+ SHA_DIGEST_LENGTH
;
577 if( is_rfc2396_alnum( *in
) )
578 *out
++ = (char) *in
++;
580 out
+= tr_snprintf( out
, 4, "%%%02x", (unsigned int)*in
++ );