2008-04-25 18:35:48 +00:00
/*
2010-01-04 21:00:47 +00:00
* This file Copyright ( C ) 2008 - 2010 Mnemosyne LLC
2008-04-24 01:42:53 +00:00
*
* This file is licensed by the GPL version 2. Works owned by the
* Transmission project are granted a special exemption to clause 2 ( b )
2009-08-10 20:04:08 +00:00
* so that the bulk of its code can remain under the MIT license .
2008-04-24 01:42:53 +00:00
* This exemption does not extend to derived works not owned by
* the Transmission project .
*
2008-04-25 19:46:36 +00:00
* $ Id $
2008-04-24 01:42:53 +00:00
*/
2010-03-06 15:05:05 +00:00
# include <sys/select.h>
2010-01-04 20:06:39 +00:00
2008-04-24 01:42:53 +00:00
# include <curl/curl.h>
2009-12-13 17:54:01 +00:00
# include <event.h>
2008-04-24 01:42:53 +00:00
# include "transmission.h"
2010-03-06 15:05:05 +00:00
# include "list.h"
# include "net.h" /* tr_address */
# include "platform.h" /* mutex */
2009-07-01 14:58:57 +00:00
# include "session.h"
2010-03-06 15:05:05 +00:00
# include "trevent.h" /* tr_runInEventThread() */
2008-04-24 01:42:53 +00:00
# include "utils.h"
2010-03-06 15:05:05 +00:00
# include "version.h" /* User-Agent */
2008-04-24 01:42:53 +00:00
# include "web.h"
2009-01-09 19:24:40 +00:00
enum
2008-12-26 20:14:47 +00:00
{
2010-03-06 15:05:05 +00:00
THREADFUNC_MAX_SLEEP_MSEC = 1000 ,
2008-12-26 20:14:47 +00:00
} ;
2008-10-17 20:57:54 +00:00
2008-10-27 18:00:03 +00:00
#if 0
# define dbgmsg(...) \
do { \
fprintf ( stderr , __VA_ARGS__ ) ; \
fprintf ( stderr , " \n " ) ; \
} while ( 0 )
# else
2008-10-26 15:39:04 +00:00
# define dbgmsg( ... ) \
do { \
if ( tr_deepLoggingIsActive ( ) ) \
tr_deepLog ( __FILE__ , __LINE__ , " web " , __VA_ARGS__ ) ; \
} while ( 0 )
2008-10-27 18:00:03 +00:00
# endif
2008-04-25 02:57:33 +00:00
2010-01-04 20:06:39 +00:00
/***
* * * *
* * */
2008-04-24 01:42:53 +00:00
struct tr_web
{
2010-03-06 15:05:05 +00:00
int close_mode ;
2009-12-14 14:25:22 +00:00
tr_bool haveAddr ;
2010-03-06 15:05:05 +00:00
tr_list * tasks ;
tr_lock * taskLock ;
2009-10-23 05:48:56 +00:00
tr_address addr ;
2008-04-24 01:42:53 +00:00
} ;
2009-12-14 12:54:30 +00:00
2008-10-15 16:43:51 +00:00
/***
* * * *
* * */
2008-04-24 01:42:53 +00:00
struct tr_web_task
{
2010-03-06 15:05:05 +00:00
long code ;
2008-10-15 16:43:51 +00:00
struct evbuffer * response ;
char * url ;
char * range ;
tr_session * session ;
tr_web_done_func * done_func ;
void * done_func_user_data ;
2008-04-24 01:42:53 +00:00
} ;
2009-12-14 12:54:30 +00:00
static void
task_free ( struct tr_web_task * task )
{
evbuffer_free ( task - > response ) ;
tr_free ( task - > range ) ;
tr_free ( task - > url ) ;
tr_free ( task ) ;
}
/***
* * * *
* * */
2008-04-25 18:35:48 +00:00
static size_t
2009-09-25 21:05:59 +00:00
writeFunc ( void * ptr , size_t size , size_t nmemb , void * vtask )
2008-04-24 01:42:53 +00:00
{
2008-04-25 18:35:48 +00:00
const size_t byteCount = size * nmemb ;
2009-09-25 21:05:59 +00:00
struct tr_web_task * task = vtask ;
evbuffer_add ( task - > response , ptr , byteCount ) ;
2008-10-16 05:24:57 +00:00
dbgmsg ( " wrote %zu bytes to task %p's buffer " , byteCount , task ) ;
2008-04-25 18:35:48 +00:00
return byteCount ;
}
2008-04-24 01:42:53 +00:00
2009-12-28 23:25:50 +00:00
static int
2009-12-02 05:30:46 +00:00
sockoptfunction ( void * vtask , curl_socket_t fd , curlsocktype purpose UNUSED )
{
struct tr_web_task * task = vtask ;
const tr_bool isScrape = strstr ( task - > url , " scrape " ) ! = NULL ;
const tr_bool isAnnounce = strstr ( task - > url , " announce " ) ! = NULL ;
2010-01-04 20:06:39 +00:00
/* announce and scrape requests have tiny payloads. */
2009-12-02 05:30:46 +00:00
if ( isScrape | | isAnnounce )
{
2009-12-13 19:33:02 +00:00
const int sndbuf = 1024 ;
const int rcvbuf = isScrape ? 2048 : 3072 ;
setsockopt ( fd , SOL_SOCKET , SO_SNDBUF , & sndbuf , sizeof ( sndbuf ) ) ;
setsockopt ( fd , SOL_SOCKET , SO_RCVBUF , & rcvbuf , sizeof ( rcvbuf ) ) ;
2009-12-02 05:30:46 +00:00
}
2009-12-28 23:25:50 +00:00
/* return nonzero if this function encountered an error */
return 0 ;
2009-12-02 05:30:46 +00:00
}
2009-12-14 05:11:33 +00:00
static int
getCurlProxyType ( tr_proxy_type t )
{
if ( t = = TR_PROXY_SOCKS4 ) return CURLPROXY_SOCKS4 ;
if ( t = = TR_PROXY_SOCKS5 ) return CURLPROXY_SOCKS5 ;
return CURLPROXY_HTTP ;
}
2010-03-06 15:05:05 +00:00
static long
2009-12-13 17:54:01 +00:00
getTimeoutFromURL ( const char * url )
{
2010-03-06 15:05:05 +00:00
if ( strstr ( url , " scrape " ) ! = NULL ) return 30L ;
if ( strstr ( url , " announce " ) ! = NULL ) return 90L ;
return 240L ;
2009-12-13 17:54:01 +00:00
}
2010-03-06 15:05:05 +00:00
static CURL *
createEasy ( tr_session * s , struct tr_web * w , struct tr_web_task * task )
2008-04-25 18:35:48 +00:00
{
2010-03-06 15:05:05 +00:00
CURL * e = curl_easy_init ( ) ;
const long verbose = getenv ( " TR_CURL_VERBOSE " ) ! = NULL ;
2010-01-26 07:22:50 +00:00
2010-03-06 15:05:05 +00:00
if ( ! task - > range & & s - > isProxyEnabled ) {
const long proxyType = getCurlProxyType ( s - > proxyType ) ;
curl_easy_setopt ( e , CURLOPT_PROXY , s - > proxy ) ;
curl_easy_setopt ( e , CURLOPT_PROXYAUTH , CURLAUTH_ANY ) ;
curl_easy_setopt ( e , CURLOPT_PROXYPORT , s - > proxyPort ) ;
curl_easy_setopt ( e , CURLOPT_PROXYTYPE , proxyType ) ;
2010-01-26 07:22:50 +00:00
}
2008-08-06 23:33:29 +00:00
2010-03-06 15:05:05 +00:00
if ( ! task - > range & & s - > isProxyAuthEnabled ) {
char * str = tr_strdup_printf ( " %s:%s " , s - > proxyUsername ,
s - > proxyPassword ) ;
curl_easy_setopt ( e , CURLOPT_PROXYUSERPWD , str ) ;
tr_free ( str ) ;
2010-01-22 02:40:11 +00:00
}
2010-03-06 15:05:05 +00:00
curl_easy_setopt ( e , CURLOPT_AUTOREFERER , 1L ) ;
curl_easy_setopt ( e , CURLOPT_ENCODING , " gzip;q=1.0, deflate, identity " ) ;
curl_easy_setopt ( e , CURLOPT_FOLLOWLOCATION , 1L ) ;
curl_easy_setopt ( e , CURLOPT_MAXREDIRS , - 1L ) ;
curl_easy_setopt ( e , CURLOPT_NOSIGNAL , 1L ) ;
curl_easy_setopt ( e , CURLOPT_PRIVATE , task ) ;
curl_easy_setopt ( e , CURLOPT_SOCKOPTFUNCTION , sockoptfunction ) ;
curl_easy_setopt ( e , CURLOPT_SOCKOPTDATA , task ) ;
curl_easy_setopt ( e , CURLOPT_SSL_VERIFYHOST , 0L ) ;
curl_easy_setopt ( e , CURLOPT_SSL_VERIFYPEER , 0L ) ;
curl_easy_setopt ( e , CURLOPT_TIMEOUT , getTimeoutFromURL ( task - > url ) ) ;
curl_easy_setopt ( e , CURLOPT_URL , task - > url ) ;
curl_easy_setopt ( e , CURLOPT_USERAGENT , TR_NAME " / " SHORT_VERSION_STRING ) ;
curl_easy_setopt ( e , CURLOPT_VERBOSE , verbose ) ;
curl_easy_setopt ( e , CURLOPT_WRITEDATA , task ) ;
curl_easy_setopt ( e , CURLOPT_WRITEFUNCTION , writeFunc ) ;
if ( w - > haveAddr )
curl_easy_setopt ( e , CURLOPT_INTERFACE , tr_ntop_non_ts ( & w - > addr ) ) ;
if ( task - > range )
curl_easy_setopt ( e , CURLOPT_RANGE , task - > range ) ;
return e ;
2008-04-24 01:42:53 +00:00
}
2008-10-15 16:43:51 +00:00
/***
* * * *
* * */
2008-04-27 18:27:32 +00:00
2008-10-18 15:45:12 +00:00
static void
2010-03-06 15:05:05 +00:00
task_finish_func ( void * vtask )
2008-10-15 16:43:51 +00:00
{
2010-03-06 15:05:05 +00:00
struct tr_web_task * task = vtask ;
dbgmsg ( " finished web task %p; got %ld " , task , task - > code ) ;
2009-09-25 21:05:59 +00:00
if ( task - > done_func ! = NULL )
task - > done_func ( task - > session ,
2010-03-06 15:05:05 +00:00
task - > code ,
2009-09-25 21:05:59 +00:00
EVBUFFER_DATA ( task - > response ) ,
EVBUFFER_LENGTH ( task - > response ) ,
task - > done_func_user_data ) ;
2008-10-15 16:43:51 +00:00
2010-03-06 15:05:05 +00:00
task_free ( task ) ;
2008-10-15 16:43:51 +00:00
}
/****
* * * * *
* * * */
void
tr_webRun ( tr_session * session ,
const char * url ,
const char * range ,
tr_web_done_func done_func ,
void * done_func_user_data )
{
2010-03-06 15:05:05 +00:00
struct tr_web * web = session - > web ;
if ( web ! = NULL )
2008-09-23 19:11:04 +00:00
{
2009-12-14 05:11:33 +00:00
struct tr_web_task * task = tr_new0 ( struct tr_web_task , 1 ) ;
2010-03-06 15:05:05 +00:00
2008-10-15 16:43:51 +00:00
task - > session = session ;
task - > url = tr_strdup ( url ) ;
task - > range = tr_strdup ( range ) ;
task - > done_func = done_func ;
task - > done_func_user_data = done_func_user_data ;
task - > response = evbuffer_new ( ) ;
2010-03-06 15:05:05 +00:00
tr_lockLock ( web - > taskLock ) ;
tr_list_append ( & web - > tasks , task ) ;
tr_lockUnlock ( web - > taskLock ) ;
2008-08-06 23:33:29 +00:00
}
2008-05-06 15:52:57 +00:00
}
2008-04-24 19:38:59 +00:00
2009-10-23 05:48:56 +00:00
void
2010-03-06 15:05:05 +00:00
tr_webSetInterface ( tr_session * session , const tr_address * addr )
2009-10-23 05:48:56 +00:00
{
2010-03-06 15:05:05 +00:00
struct tr_web * web = session - > web ;
if ( web ! = NULL )
if ( ( web - > haveAddr = ( addr ! = NULL ) ) )
web - > addr = * addr ;
2009-10-23 05:48:56 +00:00
}
2010-03-06 15:05:05 +00:00
static void
tr_webThreadFunc ( void * vsession )
2008-04-24 01:42:53 +00:00
{
2010-03-06 15:05:05 +00:00
int unused ;
CURLM * multi ;
struct tr_web * web ;
int taskCount = 0 ;
tr_session * session = vsession ;
2008-04-24 01:42:53 +00:00
2009-12-14 12:54:30 +00:00
/* try to enable ssl for https support; but if that fails,
2009-08-10 20:04:08 +00:00
* try a plain vanilla init */
2009-12-14 12:54:30 +00:00
if ( curl_global_init ( CURL_GLOBAL_SSL ) )
curl_global_init ( 0 ) ;
2009-08-10 20:04:08 +00:00
2008-04-24 01:42:53 +00:00
web = tr_new0 ( struct tr_web , 1 ) ;
2010-03-06 15:05:05 +00:00
web - > close_mode = ~ 0 ;
web - > taskLock = tr_lockNew ( ) ;
web - > tasks = NULL ;
multi = curl_multi_init ( ) ;
session - > web = web ;
2009-12-11 15:41:34 +00:00
2010-03-06 15:05:05 +00:00
for ( ; ; )
{
long msec ;
CURLMsg * msg ;
CURLMcode mcode ;
struct tr_web_task * task ;
if ( web - > close_mode = = TR_WEB_CLOSE_NOW )
break ;
if ( ( web - > close_mode = = TR_WEB_CLOSE_WHEN_IDLE ) & & ! taskCount )
break ;
/* add tasks from the queue */
tr_lockLock ( web - > taskLock ) ;
while ( ( task = tr_list_pop_front ( & web - > tasks ) ) )
{
curl_multi_add_handle ( multi , createEasy ( session , web , task ) ) ;
fprintf ( stderr , " adding a task.. taskCount is now %d \n " , taskCount ) ;
+ + taskCount ;
}
tr_lockUnlock ( web - > taskLock ) ;
/* maybe wait a little while before calling curl_multi_perform() */
msec = 0 ;
curl_multi_timeout ( multi , & msec ) ;
if ( msec < 0 )
msec = THREADFUNC_MAX_SLEEP_MSEC ;
if ( msec > 0 )
{
int max_fd ;
struct timeval t ;
fd_set r_fd_set , w_fd_set , c_fd_set ;
max_fd = 0 ;
FD_ZERO ( & r_fd_set ) ;
FD_ZERO ( & w_fd_set ) ;
FD_ZERO ( & c_fd_set ) ;
curl_multi_fdset ( multi , & r_fd_set , & w_fd_set , & c_fd_set , & max_fd ) ;
if ( msec > THREADFUNC_MAX_SLEEP_MSEC )
msec = THREADFUNC_MAX_SLEEP_MSEC ;
t . tv_sec = 0 ;
t . tv_usec = msec * 1000 ;
select ( max_fd + 1 , & r_fd_set , & w_fd_set , & c_fd_set , & t ) ;
}
2010-01-22 03:39:21 +00:00
2010-03-06 15:05:05 +00:00
/* call curl_multi_perform() */
do {
mcode = curl_multi_perform ( multi , & unused ) ;
} while ( mcode = = CURLM_CALL_MULTI_PERFORM ) ;
2010-01-22 03:39:21 +00:00
2010-03-06 15:05:05 +00:00
/* pump completed tasks from the multi */
while ( ( msg = curl_multi_info_read ( multi , & unused ) ) )
{
if ( ( msg - > msg = = CURLMSG_DONE ) & & ( msg - > easy_handle ! = NULL ) )
{
struct tr_web_task * task ;
CURL * e = msg - > easy_handle ;
curl_easy_getinfo ( e , CURLINFO_PRIVATE , ( void * ) & task ) ;
curl_easy_getinfo ( e , CURLINFO_RESPONSE_CODE , & task - > code ) ;
curl_multi_remove_handle ( multi , e ) ;
curl_easy_cleanup ( e ) ;
fprintf ( stderr , " removing a completed task.. taskCount is now %d (response code: %d, response len: %d) \n " , taskCount , ( int ) task - > code , EVBUFFER_LENGTH ( task - > response ) ) ;
tr_runInEventThread ( task - > session , task_finish_func , task ) ;
- - taskCount ;
}
}
}
2008-04-24 01:42:53 +00:00
2010-03-06 15:05:05 +00:00
/* cleanup */
curl_multi_cleanup ( multi ) ;
tr_lockFree ( web - > taskLock ) ;
tr_free ( web ) ;
session - > web = NULL ;
}
void
tr_webInit ( tr_session * session )
{
tr_threadNew ( tr_webThreadFunc , session ) ;
2008-04-24 01:42:53 +00:00
}
2008-04-25 04:26:04 +00:00
2008-04-25 19:46:36 +00:00
void
2010-03-06 15:05:05 +00:00
tr_webClose ( tr_session * session , tr_web_close_mode close_mode )
2008-04-25 19:46:36 +00:00
{
2010-03-06 15:05:05 +00:00
if ( session - > web ! = NULL )
{
session - > web - > close_mode = close_mode ;
if ( close_mode = = TR_WEB_CLOSE_NOW )
while ( session - > web ! = NULL )
tr_wait_msec ( 100 ) ;
}
2008-04-25 19:46:36 +00:00
}
2008-10-15 16:43:51 +00:00
/*****
* * * * * *
* * * * * *
* * * * */
2008-04-25 04:26:04 +00:00
const char *
tr_webGetResponseStr ( long code )
{
2009-12-14 12:54:30 +00:00
switch ( code )
{
case 0 : return " No Response " ;
case 101 : return " Switching Protocols " ;
case 200 : return " OK " ;
case 201 : return " Created " ;
case 202 : return " Accepted " ;
case 203 : return " Non-Authoritative Information " ;
case 204 : return " No Content " ;
case 205 : return " Reset Content " ;
case 206 : return " Partial Content " ;
case 300 : return " Multiple Choices " ;
case 301 : return " Moved Permanently " ;
case 302 : return " Found " ;
case 303 : return " See Other " ;
case 304 : return " Not Modified " ;
case 305 : return " Use Proxy " ;
case 306 : return " (Unused) " ;
case 307 : return " Temporary Redirect " ;
case 400 : return " Bad Request " ;
case 401 : return " Unauthorized " ;
case 402 : return " Payment Required " ;
case 403 : return " Forbidden " ;
case 404 : return " Not Found " ;
case 405 : return " Method Not Allowed " ;
case 406 : return " Not Acceptable " ;
case 407 : return " Proxy Authentication Required " ;
case 408 : return " Request Timeout " ;
case 409 : return " Conflict " ;
case 410 : return " Gone " ;
case 411 : return " Length Required " ;
case 412 : return " Precondition Failed " ;
case 413 : return " Request Entity Too Large " ;
case 414 : return " Request-URI Too Long " ;
case 415 : return " Unsupported Media Type " ;
case 416 : return " Requested Range Not Satisfiable " ;
case 417 : return " Expectation Failed " ;
case 500 : return " Internal Server Error " ;
case 501 : return " Not Implemented " ;
case 502 : return " Bad Gateway " ;
case 503 : return " Service Unavailable " ;
case 504 : return " Gateway Timeout " ;
case 505 : return " HTTP Version Not Supported " ;
default : return " Unknown Error " ;
}
2008-04-25 04:26:04 +00:00
}
2009-11-10 17:03:23 +00:00
void
2009-12-14 14:25:22 +00:00
tr_http_escape ( struct evbuffer * out ,
const char * str , int len , tr_bool escape_slashes )
2009-11-10 17:03:23 +00:00
{
2010-03-06 15:05:05 +00:00
const char * end ;
2009-11-10 17:03:23 +00:00
2009-11-29 08:05:47 +00:00
if ( ( len < 0 ) & & ( str ! = NULL ) )
len = strlen ( str ) ;
2010-03-06 15:05:05 +00:00
for ( end = str + len ; str ! = end ; + + str ) {
if ( ( * str = = ' , ' )
| | ( * str = = ' - ' )
| | ( * str = = ' . ' )
| | ( ( ' 0 ' < = * str ) & & ( * str < = ' 9 ' ) )
| | ( ( ' A ' < = * str ) & & ( * str < = ' Z ' ) )
| | ( ( ' a ' < = * str ) & & ( * str < = ' z ' ) )
| | ( ( * str = = ' / ' ) & & ( ! escape_slashes ) ) )
evbuffer_add ( out , str , 1 ) ;
2010-01-10 14:56:04 +00:00
else
2010-03-06 15:05:05 +00:00
evbuffer_add_printf ( out , " %%%02X " , ( unsigned ) ( * str & 0xFF ) ) ;
2009-11-10 17:03:23 +00:00
}
}
2009-11-20 04:38:19 +00:00
2009-12-14 05:11:33 +00:00
char *
2009-11-20 04:38:19 +00:00
tr_http_unescape ( const char * str , int len )
{
char * tmp = curl_unescape ( str , len ) ;
char * ret = tr_strdup ( tmp ) ;
curl_free ( tmp ) ;
return ret ;
}