diff --git a/daemon/remote.c b/daemon/remote.c index 1fb56de51..8b8d9c131 100644 --- a/daemon/remote.c +++ b/daemon/remote.c @@ -300,8 +300,8 @@ readargs( int argc, } else { - fprintf( stderr, "Couldn't add file: %s\n", optarg ); - addArg = FALSE; + tr_bencDictAddStr( &top, "method", "torrent-add" ); + tr_bencDictAddStr( args, "filename", optarg ); } } else diff --git a/doc/rpc-spec.txt b/doc/rpc-spec.txt index e02df64a2..b00395773 100644 --- a/doc/rpc-spec.txt +++ b/doc/rpc-spec.txt @@ -281,7 +281,7 @@ key | value type & description -------------------+------------------------------------------------- "download-dir" | string path to download the torrent to - "filename" | string location of the .torrent file + "filename" | string filename or URL of the .torrent file "metainfo" | string base64-encoded .torrent content "paused" | 'boolean' if true, don't start the torrent "peer-limit" | number maximum number of peers diff --git a/libtransmission/rpc-server.c b/libtransmission/rpc-server.c index 2de48e636..6cfff9e4b 100644 --- a/libtransmission/rpc-server.c +++ b/libtransmission/rpc-server.c @@ -176,7 +176,7 @@ handle_upload( struct evhttp_request * req, tr_rpc_request_exec_json( server->session, EVBUFFER_DATA( json ), EVBUFFER_LENGTH( json ), - NULL ); + NULL, NULL ); tr_releaseBuffer( json ); tr_free( b64 ); @@ -374,39 +374,54 @@ handle_clutch( struct evhttp_request * req, } } +struct rpc_response_data +{ + struct evhttp_request * req; + struct tr_rpc_server * server; +}; + +static void +rpc_response_func( tr_session * session UNUSED, + const char * response, + size_t response_len, + void * user_data ) +{ + struct rpc_response_data * data = user_data; + struct evbuffer * buf = tr_getBuffer( ); + + add_response( data->req, data->server, buf, response, response_len ); + evhttp_add_header( data->req->output_headers, + "Content-Type", "application/json; charset=UTF-8" ); + evhttp_send_reply( data->req, HTTP_OK, "OK", buf ); + + tr_releaseBuffer( buf ); + tr_free( data ); +} + + static void handle_rpc( struct evhttp_request * req, struct tr_rpc_server * server ) { - struct evbuffer * response = tr_getBuffer( ); + struct rpc_response_data * data = tr_new0( struct rpc_response_data, 1 ); + data->req = req; + data->server = server; + if( req->type == EVHTTP_REQ_GET ) { const char * q; if( ( q = strchr( req->uri, '?' ) ) ) - tr_rpc_request_exec_uri( server->session, q + 1, strlen( q + 1 ), response ); + tr_rpc_request_exec_uri( server->session, q+1, -1, rpc_response_func, data ); } else if( req->type == EVHTTP_REQ_POST ) { tr_rpc_request_exec_json( server->session, EVBUFFER_DATA( req->input_buffer ), EVBUFFER_LENGTH( req->input_buffer ), - response ); + rpc_response_func, data ); } - { - struct evbuffer * buf = tr_getBuffer( ); - add_response( req, server, buf, - EVBUFFER_DATA( response ), - EVBUFFER_LENGTH( response ) ); - evhttp_add_header( req->output_headers, "Content-Type", - "application/json; charset=UTF-8" ); - evhttp_send_reply( req, HTTP_OK, "OK", buf ); - tr_releaseBuffer( buf ); - } - - /* cleanup */ - tr_releaseBuffer( response ); } static tr_bool diff --git a/libtransmission/rpcimpl.c b/libtransmission/rpcimpl.c index e7fa50ac6..85f1ed069 100644 --- a/libtransmission/rpcimpl.c +++ b/libtransmission/rpcimpl.c @@ -15,6 +15,8 @@ #include /* strtol */ #include /* strcmp */ +#include /* evbuffer */ + #include "transmission.h" #include "bencode.h" #include "rpcimpl.h" @@ -22,9 +24,17 @@ #include "session.h" #include "torrent.h" #include "utils.h" +#include "web.h" #define TR_N_ELEMENTS( ary ) ( sizeof( ary ) / sizeof( *ary ) ) +#define dbgmsg( ... ) \ + do { \ + if( tr_deepLoggingIsActive( ) ) \ + tr_deepLog( __FILE__, __LINE__, "RPC", __VA_ARGS__ ); \ + } while( 0 ) + + /*** **** ***/ @@ -47,6 +57,41 @@ notify( tr_session * session, **** ***/ +/* For functions that can't be immediately executed, like torrentAdd, + * this is the callback data used to pass a response to the caller + * when the task is complete */ +struct tr_rpc_idle_data +{ + tr_session * session; + tr_benc * response; + tr_benc * args_out; + tr_rpc_response_func * callback; + void * callback_user_data; +}; + +static void +function_done( struct tr_rpc_idle_data * data, const char * result ) +{ + struct evbuffer * buf = tr_getBuffer( ); + + if( result == NULL ) + result = "success"; + tr_bencDictAddStr( data->response, "result", result ); + + tr_bencSaveAsJSON( data->response, buf ); + data->callback( data->session, (const char*)EVBUFFER_DATA(buf), + EVBUFFER_LENGTH(buf), data->callback_user_data ); + + tr_releaseBuffer( buf ); + tr_bencFree( data->response ); + tr_free( data->response ); + tr_free( data ); +} + +/*** +**** +***/ + static tr_torrent ** getTorrents( tr_session * session, tr_benc * args, @@ -99,13 +144,16 @@ getTorrents( tr_session * session, } static const char* -torrentStart( tr_session * session, - tr_benc * args_in, - tr_benc * args_out UNUSED ) +torrentStart( tr_session * session, + tr_benc * args_in, + tr_benc * args_out UNUSED, + struct tr_rpc_idle_data * idle_data ) { int i, torrentCount; tr_torrent ** torrents = getTorrents( session, args_in, &torrentCount ); + assert( idle_data == NULL ); + for( i = 0; i < torrentCount; ++i ) { tr_torrent * tor = torrents[i]; @@ -117,13 +165,16 @@ torrentStart( tr_session * session, } static const char* -torrentStop( tr_session * session, - tr_benc * args_in, - tr_benc * args_out UNUSED ) +torrentStop( tr_session * session, + tr_benc * args_in, + tr_benc * args_out UNUSED, + struct tr_rpc_idle_data * idle_data ) { int i, torrentCount; tr_torrent ** torrents = getTorrents( session, args_in, &torrentCount ); + assert( idle_data == NULL ); + for( i = 0; i < torrentCount; ++i ) { tr_torrent * tor = torrents[i]; @@ -135,14 +186,17 @@ torrentStop( tr_session * session, } static const char* -torrentRemove( tr_session * session, - tr_benc * args_in, - tr_benc * args_out UNUSED ) +torrentRemove( tr_session * session, + tr_benc * args_in, + tr_benc * args_out UNUSED, + struct tr_rpc_idle_data * idle_data ) { int i; int torrentCount; tr_torrent ** torrents = getTorrents( session, args_in, &torrentCount ); + assert( idle_data == NULL ); + for( i=0; isession, ctor, &err ); + + tr_ctorFree( ctor ); + + if( tor ) + { + tr_benc fields; + tr_bencInitList( &fields, 3 ); + tr_bencListAddStr( &fields, "id" ); + tr_bencListAddStr( &fields, "name" ); + tr_bencListAddStr( &fields, "hashString" ); + addInfo( tor, tr_bencDictAdd( data->args_out, "torrent-added" ), &fields ); + notify( data->session, TR_RPC_TORRENT_ADDED, tor ); + tr_bencFree( &fields ); + } + else if( err == TR_EDUPLICATE ) + { + result = "duplicate torrent"; + } + else if( err == TR_EINVALID ) + { + result = "invalid or corrupt torrent file"; + } + + function_done( data, result ); +} + + +struct add_torrent_idle_data +{ + struct tr_rpc_idle_data * data; + tr_ctor * ctor; +}; + +static void +gotMetadataFromURL( tr_session * session UNUSED, + long response_code, + const void * response, + size_t response_byte_count, + void * user_data ) +{ + struct add_torrent_idle_data * data = user_data; + + dbgmsg( "torrentAdd: HTTP response code was %ld (%s); response length was %zu bytes", + response_code, tr_webGetResponseStr( response_code ), response_byte_count ); + + if( response_code == 200 ) + { + tr_ctorSetMetainfo( data->ctor, response, response_byte_count ); + addTorrentImpl( data->data, data->ctor ); + } + else + { + char result[1024]; + tr_snprintf( result, sizeof( result ), "http error %ld: %s", + response_code, tr_webGetResponseStr( response_code ) ); + function_done( data->data, result ); + } + + tr_free( data ); +} + +static tr_bool +isCurlURL( const char * filename ) +{ + if( filename == NULL ) + return FALSE; + + return ( strstr( filename, "ftp://" ) != NULL ) + || ( strstr( filename, "http://" ) != NULL ) + || ( strstr( filename, "https://" ) != NULL ); +} + static const char* -torrentAdd( tr_session * session, - tr_benc * args_in, - tr_benc * args_out ) +torrentAdd( tr_session * session, + tr_benc * args_in, + tr_benc * args_out UNUSED, + struct tr_rpc_idle_data * idle_data ) { const char * filename = NULL; const char * metainfo_base64 = NULL; + assert( idle_data != NULL ); + tr_bencDictFindStr( args_in, "filename", &filename ); tr_bencDictFindStr( args_in, "metainfo", &metainfo_base64 ); if( !filename && !metainfo_base64 ) @@ -585,23 +728,8 @@ torrentAdd( tr_session * session, else { int64_t i; - int err = 0; const char * str; - tr_ctor * ctor; - tr_torrent * tor; - - ctor = tr_ctorNew( session ); - - /* set the metainfo */ - if( filename ) - tr_ctorSetMetainfoFromFile( ctor, filename ); - else - { - int len; - char * metainfo = tr_base64_decode( metainfo_base64, -1, &len ); - tr_ctorSetMetainfo( ctor, (uint8_t*)metainfo, len ); - tr_free( metainfo ); - } + tr_ctor * ctor = tr_ctorNew( session ); /* set the optional arguments */ if( tr_bencDictFindStr( args_in, "download-dir", &str ) ) @@ -611,29 +739,28 @@ torrentAdd( tr_session * session, if( tr_bencDictFindInt( args_in, "peer-limit", &i ) ) tr_ctorSetPeerLimit( ctor, TR_FORCE, i ); - tor = tr_torrentNew( session, ctor, &err ); - tr_ctorFree( ctor ); + dbgmsg( "torrentAdd: filename is \"%s\"", filename ); - if( tor ) + if( isCurlURL( filename ) ) { - tr_benc fields; - tr_bencInitList( &fields, 3 ); - tr_bencListAddStr( &fields, "id" ); - tr_bencListAddStr( &fields, "name" ); - tr_bencListAddStr( &fields, "hashString" ); - addInfo( tor, tr_bencDictAdd( args_out, - "torrent-added" ), &fields ); - notify( session, TR_RPC_TORRENT_ADDED, tor ); - tr_bencFree( &fields ); + struct add_torrent_idle_data * d = tr_new0( struct add_torrent_idle_data, 1 ); + d->data = idle_data; + d->ctor = ctor; + tr_webRun( session, filename, NULL, gotMetadataFromURL, d ); } - else if( err == TR_EDUPLICATE ) + else { - return "duplicate torrent"; - } - else if( err == TR_EINVALID ) - { - return "invalid or corrupt torrent file"; + if( filename != NULL ) + tr_ctorSetMetainfoFromFile( ctor, filename ); + else { + int len; + char * metainfo = tr_base64_decode( metainfo_base64, -1, &len ); + tr_ctorSetMetainfo( ctor, (uint8_t*)metainfo, len ); + tr_free( metainfo ); + } + addTorrentImpl( idle_data, ctor ); } + } return NULL; @@ -644,13 +771,16 @@ torrentAdd( tr_session * session, ***/ static const char* -sessionSet( tr_session * session, - tr_benc * args_in, - tr_benc * args_out UNUSED ) +sessionSet( tr_session * session, + tr_benc * args_in, + tr_benc * args_out UNUSED, + struct tr_rpc_idle_data * idle_data ) { int64_t i; const char * str; + assert( idle_data == NULL ); + if( tr_bencDictFindStr( args_in, "download-dir", &str ) ) tr_sessionSetDownloadDir( session, str ); if( tr_bencDictFindInt( args_in, "peer-limit", &i ) ) @@ -685,14 +815,17 @@ sessionSet( tr_session * session, } static const char* -sessionStats( tr_session * session, - tr_benc * args_in UNUSED, - tr_benc * args_out ) +sessionStats( tr_session * session, + tr_benc * args_in UNUSED, + tr_benc * args_out, + struct tr_rpc_idle_data * idle_data ) { int running = 0; int total = 0; tr_torrent * tor = NULL; + assert( idle_data == NULL ); + while(( tor = tr_torrentNext( session, tor ))) { ++total; if( tor->isRunning ) @@ -708,13 +841,16 @@ sessionStats( tr_session * session, } static const char* -sessionGet( tr_session * session, - tr_benc * args_in UNUSED, - tr_benc * args_out ) +sessionGet( tr_session * session, + tr_benc * args_in UNUSED, + tr_benc * args_out, + struct tr_rpc_idle_data * idle_data ) { const char * str; tr_benc * d = args_out; + assert( idle_data == NULL ); + tr_bencDictAddStr( d, "download-dir", tr_sessionGetDownloadDir( session ) ); tr_bencDictAddInt( d, "peer-limit", tr_sessionGetPeerLimit( session ) ); tr_bencDictAddInt( d, "pex-allowed", tr_sessionIsPexEnabled( session ) ); @@ -741,71 +877,112 @@ sessionGet( tr_session * session, **** ***/ -typedef const char* ( handler )( tr_session*, tr_benc*, tr_benc* ); +typedef const char* ( handler )( tr_session*, tr_benc*, tr_benc*, struct tr_rpc_idle_data * ); static struct method { const char * name; - handler * func; -} methods[] = { - { "session-get", sessionGet }, - { "session-set", sessionSet }, - { "session-stats", sessionStats }, - { "torrent-add", torrentAdd }, - { "torrent-get", torrentGet }, - { "torrent-remove", torrentRemove }, - { "torrent-set", torrentSet }, - { "torrent-start", torrentStart }, - { "torrent-stop", torrentStop }, - { "torrent-verify", torrentVerify } + tr_bool immediate; + handler * func; +} +methods[] = +{ + { "session-get", TRUE, sessionGet }, + { "session-set", TRUE, sessionSet }, + { "session-stats", TRUE, sessionStats }, + { "torrent-add", FALSE, torrentAdd }, + { "torrent-get", TRUE, torrentGet }, + { "torrent-remove", TRUE, torrentRemove }, + { "torrent-set", TRUE, torrentSet }, + { "torrent-start", TRUE, torrentStart }, + { "torrent-stop", TRUE, torrentStop }, + { "torrent-verify", TRUE, torrentVerify } }; static void -request_exec( tr_session * session, - tr_benc * request, - struct evbuffer * response_buf ) +request_exec( tr_session * session, + tr_benc * request, + tr_rpc_response_func callback, + void * callback_user_data ) { int64_t i; const char * str; - tr_benc response; tr_benc * args_in = tr_bencDictFind( request, "arguments" ); - tr_benc * args_out = NULL; const char * result = NULL; - /* build the response skeleton */ - tr_bencInitDict( &response, 3 ); - args_out = tr_bencDictAddDict( &response, "arguments", 0 ); - /* parse the request */ if( !tr_bencDictFindStr( request, "method", &str ) ) result = "no method name"; - else - { + else { const int n = TR_N_ELEMENTS( methods ); for( i = 0; i < n; ++i ) if( !strcmp( str, methods[i].name ) ) break; - result = i == n - ? "method name not recognized" - : ( *methods[i].func )( session, args_in, args_out ); + if( i ==n ) + result = "method name not recognized"; } - /* serialize & return the response */ - if( !result ) - result = "success"; - tr_bencDictAddStr( &response, "result", result ); - if( tr_bencDictFindInt( request, "tag", &i ) ) - tr_bencDictAddInt( &response, "tag", i ); - if( response_buf != NULL ) - tr_bencSaveAsJSON( &response, response_buf ); - tr_bencFree( &response ); + /* if we couldn't figure out which method to use, return an error */ + if( result != NULL ) + { + tr_benc response; + struct evbuffer * buf = tr_getBuffer( ); + + tr_bencInitDict( &response, 3 ); + tr_bencDictAddDict( &response, "arguments", 0 ); + tr_bencDictAddStr( &response, "result", result ); + if( tr_bencDictFindInt( request, "tag", &i ) ) + tr_bencDictAddInt( &response, "tag", i ); + tr_bencSaveAsJSON( &response, buf ); + callback( session, (const char*)EVBUFFER_DATA(buf), + EVBUFFER_LENGTH( buf ), callback_user_data ); + + tr_releaseBuffer( buf ); + tr_bencFree( &response ); + } + + if( methods[i].immediate ) + { + tr_benc response; + tr_benc * args_out; + struct evbuffer * buf = tr_getBuffer( ); + + tr_bencInitDict( &response, 3 ); + args_out = tr_bencDictAddDict( &response, "arguments", 0 ); + result = (*methods[i].func)( session, args_in, args_out, NULL ); + if( result == NULL ) + result = "success"; + tr_bencDictAddStr( &response, "result", result ); + if( tr_bencDictFindInt( request, "tag", &i ) ) + tr_bencDictAddInt( &response, "tag", i ); + tr_bencSaveAsJSON( &response, buf ); + callback( session, (const char*)EVBUFFER_DATA(buf), + EVBUFFER_LENGTH(buf), callback_user_data ); + + tr_releaseBuffer( buf ); + tr_bencFree( &response ); + } + else + { + struct tr_rpc_idle_data * data = tr_new0( struct tr_rpc_idle_data, 1 ); + data->session = session; + data->response = tr_new0( tr_benc, 1 ); + if( tr_bencDictFindInt( request, "tag", &i ) ) + tr_bencDictAddInt( data->response, "tag", i ); + tr_bencInitDict( data->response, 3 ); + data->args_out = tr_bencDictAddDict( data->response, "arguments", 0 ); + data->callback = callback; + data->callback_user_data = callback_user_data; + (*methods[i].func)( session, args_in, data->args_out, data ); + } } void -tr_rpc_request_exec_json( tr_session * session, - const void * request_json, - int request_len, - struct evbuffer * response ) +tr_rpc_request_exec_json( tr_session * session, + const void * request_json, + ssize_t request_len, + tr_rpc_response_func callback, + void * callback_user_data ) { tr_benc top; int have_content; @@ -814,7 +991,7 @@ tr_rpc_request_exec_json( tr_session * session, request_len = strlen( request_json ); have_content = !tr_jsonParse( request_json, request_len, &top, NULL ); - request_exec( session, have_content ? &top : NULL, response ); + request_exec( session, have_content ? &top : NULL, callback, callback_user_data ); if( have_content ) tr_bencFree( &top ); @@ -853,9 +1030,9 @@ addToken( tr_benc * list, * - all other values are strings */ void -tr_rpc_parse_list_str( tr_benc * setme, - const char * str_in, - size_t len ) +tr_rpc_parse_list_str( tr_benc * setme, + const char * str_in, + ssize_t len ) { char * str = tr_strndup( str_in, len ); @@ -900,10 +1077,11 @@ tr_rpc_parse_list_str( tr_benc * setme, } void -tr_rpc_request_exec_uri( tr_session * session, - const void * request_uri, - int request_len, - struct evbuffer * response ) +tr_rpc_request_exec_uri( tr_session * session, + const void * request_uri, + ssize_t request_len, + tr_rpc_response_func callback, + void * callback_user_data ) { tr_benc top, * args; char * request = tr_strndup( request_uri, request_len ); @@ -933,7 +1111,7 @@ tr_rpc_request_exec_uri( tr_session * session, pch = next ? next + 1 : NULL; } - request_exec( session, &top, response ); + request_exec( session, &top, callback, callback_user_data ); /* cleanup */ tr_bencFree( &top ); diff --git a/libtransmission/rpcimpl.h b/libtransmission/rpcimpl.h index 217f6ca6d..1b67c92ac 100644 --- a/libtransmission/rpcimpl.h +++ b/libtransmission/rpcimpl.h @@ -13,28 +13,35 @@ #ifndef TR_RPC_H #define TR_RPC_H +#include /* ssize_t */ + /*** **** RPC processing ***/ -struct evbuffer; struct tr_benc; +typedef void( tr_rpc_response_func )( tr_session * session, + const char * response, + size_t response_len, + void * user_data ); /* http://www.json.org/ */ -void tr_rpc_request_exec_json( tr_session * session, - const void * request_json, - int request_len, - struct evbuffer * setme_response ); +void tr_rpc_request_exec_json( tr_session * session, + const void * request_json, + ssize_t request_len, + tr_rpc_response_func callback, + void * callback_user_data ); /* see the RPC spec's "Request URI Notation" section */ -void tr_rpc_request_exec_uri( tr_session * session, - const void * request_uri, - int request_len, - struct evbuffer * setme_response ); +void tr_rpc_request_exec_uri( tr_session * session, + const void * request_uri, + ssize_t request_len, + tr_rpc_response_func callback, + void * callback_user_data ); void tr_rpc_parse_list_str( struct tr_benc * setme, const char * list_str, - size_t list_str_len ); + ssize_t list_str_len ); #endif