From 7beacb3032fa66d317a8c66494249ba9be736495 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Tue, 20 May 2008 17:33:54 +0000 Subject: [PATCH] (1) RPC "add-torrent" now lets clients embed base64-encoded metainfo directly into the request (2) remove the RISON code; it didn't make the final cut (3) add base64 encode/decode utilities and unit tests --- daemon/remote.c | 16 ++++- doc/rpc-spec.txt | 4 +- libtransmission/JSON_checker.c | 1 + libtransmission/Makefile.am | 13 ++-- libtransmission/bencode-test.c | 36 ---------- libtransmission/bencode.h | 1 - libtransmission/json.c | 119 --------------------------------- libtransmission/json.h | 3 - libtransmission/rpc-server.c | 2 +- libtransmission/rpc.c | 23 +++++-- libtransmission/utils-test.c | 43 ++++++++++++ libtransmission/utils.c | 56 ++++++++++++++++ libtransmission/utils.h | 3 + 13 files changed, 148 insertions(+), 172 deletions(-) create mode 100644 libtransmission/utils-test.c diff --git a/daemon/remote.c b/daemon/remote.c index e74f3502b..d678c85e4 100644 --- a/daemon/remote.c +++ b/daemon/remote.c @@ -100,6 +100,16 @@ absolutify( char * buf, size_t len, const char * path ) return buf; } +static char* +getEncodedMetainfo( const char * filename ) +{ + size_t len; + uint8_t * buf = tr_loadFile( filename, &len ); + char * b64 = tr_base64_encode( buf, len, NULL ); + tr_free( buf ); + return b64; +} + static void readargs( int argc, char ** argv ) { @@ -132,17 +142,19 @@ readargs( int argc, char ** argv ) while((( opt = getopt_long( argc, argv, optstr, longopts, NULL ))) != -1 ) { + char * tmp; char buf[MAX_PATH_LENGTH]; tr_benc top, *args; tr_bencInitDict( &top, 3 ); - args = tr_bencDictAddDict( &top, "args", 0 ); + args = tr_bencDictAddDict( &top, "arguments", 0 ); switch( opt ) { case 'g': debug = 1; break; case 'h': showUsage( ); break; case 'a': tr_bencDictAddStr( &top, "method", "torrent-add" ); - tr_bencDictAddStr( args, "filename", optarg ); + tr_bencDictAddStr( args, "metainfo", ((tmp=getEncodedMetainfo(optarg))) ); + tr_free( tmp ); break; case 'c': tr_bencDictAddStr( &top, "method", "session-set" ); tr_bencDictAddStr( args, "encryption", optarg ); diff --git a/doc/rpc-spec.txt b/doc/rpc-spec.txt index 37dde920c..e2027dd9d 100644 --- a/doc/rpc-spec.txt +++ b/doc/rpc-spec.txt @@ -158,10 +158,12 @@ -------------------+------------------------------------------------- "download-dir" | string path to download the torrent to "filename" | string location of the .torrent file + "metainfo" | string base64-encoded .torrent content "paused" | boolean if true, don't start the torrent "peer-limit" | int maximum number of peers - The "filename" argument is required; all others are optional. + Either "filename" OR "metainfo" must be included. + All other arguments are optional. Response arguments: on success, a "torrent-added" object in the form of one of 3.3's tr_info objects. diff --git a/libtransmission/JSON_checker.c b/libtransmission/JSON_checker.c index 933e6b660..12d78df3b 100644 --- a/libtransmission/JSON_checker.c +++ b/libtransmission/JSON_checker.c @@ -385,6 +385,7 @@ static void parse_buffer_push_back_char(JSON_checker jc, char c) jc->parse_buffer_capacity *= 2; if (jc->parse_buffer == &jc->static_parse_buffer[0]) { jc->parse_buffer = (char*)malloc(jc->parse_buffer_capacity * sizeof(jc->parse_buffer[0])); + memcpy( jc->parse_buffer, jc->static_parse_buffer, jc->parse_buffer_count ); } else { jc->parse_buffer = (char*)realloc(jc->parse_buffer, jc->parse_buffer_capacity * sizeof(jc->parse_buffer[0])); } diff --git a/libtransmission/Makefile.am b/libtransmission/Makefile.am index 8670aa540..9b97fbe78 100644 --- a/libtransmission/Makefile.am +++ b/libtransmission/Makefile.am @@ -4,12 +4,11 @@ AM_CFLAGS = $(OPENSSL_CFLAGS) $(LIBCURL_CFLAGS) $(PTHREAD_CFLAGS) noinst_LIBRARIES = libtransmission.a libtransmission_a_SOURCES = \ - ConvertUTF.c \ - JSON_checker.c \ bencode.c \ blocklist.c \ clients.c \ completion.c \ + ConvertUTF.c \ crypto.c \ fastresume.c \ fdlimit.c \ @@ -17,6 +16,7 @@ libtransmission_a_SOURCES = \ handshake.c \ inout.c \ json.c \ + JSON_checker.c \ list.c \ makemeta.c \ metainfo.c \ @@ -45,11 +45,10 @@ libtransmission_a_SOURCES = \ web.c noinst_HEADERS = \ - ConvertUTF.h \ - JSON_checker.h \ bencode.h \ blocklist.h \ clients.h \ + ConvertUTF.h \ crypto.h \ completion.h \ fastresume.h \ @@ -58,6 +57,7 @@ noinst_HEADERS = \ handshake.h \ inout.h \ json.h \ + JSON_checker.h \ list.h \ makemeta.h \ metainfo.h \ @@ -92,7 +92,8 @@ TESTS = \ clients-test \ json-test \ test-fastset \ - test-peer-id + test-peer-id \ + utils-test noinst_PROGRAMS = $(TESTS) @@ -120,6 +121,8 @@ test_fastset_SOURCES = test-fastset.c test_fastset_LDADD = $(APPS_LDADD) test_peer_id_SOURCES = test-peer-id.c test_peer_id_LDADD = $(APPS_LDADD) +utils_test_SOURCES = utils-test.c +utils_test_LDADD = $(APPS_LDADD) clean-local: diff --git a/libtransmission/bencode-test.c b/libtransmission/bencode-test.c index ac71682b0..f1615d0bb 100644 --- a/libtransmission/bencode-test.c +++ b/libtransmission/bencode-test.c @@ -271,39 +271,6 @@ testParse( void ) return 0; } -static int -testRISONSnippet( const char * rison_str, const char * expected ) -{ - char * serialized = tr_rison2json( rison_str, -1 ); -#if 0 -fprintf( stderr, " expected: [%s]\n", expected ); -fprintf( stderr, " got: [%s]\n", serialized ); -#endif - check( !strcmp( serialized, expected ) ); - tr_free( serialized ); - return 0; -} - -static int -testRISON( void ) -{ - int val; - const char * rison; - const char * expected; - - rison = "(a:0,b:foo,c:'23skidoo')"; - expected = "{ \"a\": 0, \"b\": \"foo\", \"c\": \"23skidoo\" }"; - if(( val = testRISONSnippet( rison, expected ))) - return val; - - rison = "(method:torrent-info)"; - expected = "{ \"method\": \"torrent-info\" }"; - if(( val = testRISONSnippet( rison, expected ))) - return val; - - return 0; -} - static int testJSONSnippet( const char * benc_str, const char * expected ) { @@ -405,9 +372,6 @@ main( void ) if(( i = testJSON( ))) return i; - if(( i = testRISON( ))) - return i; - if(( i = testStackSmash( ))) return i; diff --git a/libtransmission/bencode.h b/libtransmission/bencode.h index 7fbf84e0f..778f3260c 100644 --- a/libtransmission/bencode.h +++ b/libtransmission/bencode.h @@ -90,7 +90,6 @@ static inline void tr_bencInit( tr_benc * val, int type ) void _tr_bencInitStr( tr_benc * val, char * str, int len, int nofree ); int tr_bencInitStrDup( tr_benc * val, const char * str ); void tr_bencInitRaw( tr_benc * val, const void * src, size_t byteCount ); -int tr_bencInitStrDupLen( tr_benc * val, const char * str, int len ); void tr_bencInitInt( tr_benc * val, int64_t num ); int tr_bencInitDict( tr_benc * val, int reserveCount ); int tr_bencInitList( tr_benc * val, int reserveCount ); diff --git a/libtransmission/json.c b/libtransmission/json.c index 829f773c3..604fa4401 100644 --- a/libtransmission/json.c +++ b/libtransmission/json.c @@ -147,122 +147,3 @@ tr_jsonParse( const void * vbuf, tr_ptrArrayFree( data.stack, NULL ); return err; } - -/*** -**** RISON-to-JSON converter -***/ - -enum { ESCAPE, - STRING_BEGIN, - STRING, ESCAPE_STRING, - UNQUOTED_STRING, ESCAPE_UNQUOTED_STRING, - VAL_BEGIN, - OTHER }; - -char* -tr_rison2json( const char * str, int rison_len ) -{ - struct evbuffer * out = evbuffer_new( ); - int stack[1000], *parents=stack; - int mode = OTHER; - char * ret; - const char * end; - - if( rison_len < 0 ) - end = str + strlen( str ); - else - end = str + rison_len; - -#define IN_OBJECT ((parents!=stack) && (parents[-1]=='}')) - - for( ; str!=end; ++str ) - { - if( mode == ESCAPE ) - { - switch( *str ) - { - case '(': evbuffer_add_printf( out, "[ " ); *parents++ = ']'; break; - case 't': evbuffer_add_printf( out, " true" ); break; - case 'f': evbuffer_add_printf( out, " false" ); break; - case 'n': evbuffer_add_printf( out, " null" ); break; - default: fprintf( stderr, "invalid escape sequence!\n" ); break; - } - mode = OTHER; - } - else if( mode == STRING_BEGIN ) - { - switch( *str ) - { - case '\'': evbuffer_add_printf( out, "\"" ); mode = STRING; break; - case ')': evbuffer_add_printf( out, " %c", *--parents ); mode = OTHER; break; - default: evbuffer_add_printf( out, "\"%c", *str ); mode = UNQUOTED_STRING; break; - } - } - else if( mode == UNQUOTED_STRING ) - { - switch( *str ) - { - case '\'': evbuffer_add_printf( out, "\"" ); mode = OTHER; break; - case ':': evbuffer_add_printf( out, "\": "); mode = VAL_BEGIN; break; - case '!': mode = ESCAPE_UNQUOTED_STRING; break; - case ')': if( IN_OBJECT ) { evbuffer_add_printf( out, "\" }"); mode = OTHER; break; } - case ',': if( IN_OBJECT ) { evbuffer_add_printf( out, "\", "); mode = STRING_BEGIN; break; } - /* fallthrough */ - default: evbuffer_add_printf( out, "%c", *str ); break; - } - } - else if( mode == VAL_BEGIN ) - { - if( *str == '\'' ) { evbuffer_add_printf( out, "\"" ); mode = STRING; } - else if( isdigit( *str ) ) { evbuffer_add_printf( out, "%c", *str ); mode = OTHER; } - else { evbuffer_add_printf( out, "\"%c", *str ); mode = UNQUOTED_STRING; } - } - else if( mode == STRING ) - { - switch( *str ) - { - case '\'': evbuffer_add_printf( out, "\"" ); mode = OTHER; break; - case '!': mode = ESCAPE_STRING; break; - default: evbuffer_add_printf( out, "%c", *str ); break; - } - } - else if( mode == ESCAPE_STRING || mode == ESCAPE_UNQUOTED_STRING ) - { - switch( *str ) - { - case '!': evbuffer_add_printf( out, "!" ); break; - case '\'': evbuffer_add_printf( out, "'" ); break; - default: fprintf( stderr, "invalid string escape sequence\n" ); break; - } - if( mode == ESCAPE_UNQUOTED_STRING ) mode = UNQUOTED_STRING; - if( mode == ESCAPE_STRING ) mode = STRING; - } - else - { - switch( *str ) - { - case '(': evbuffer_add_printf( out, "{ " ); mode=STRING_BEGIN; *parents++ = '}'; break; - case '!': mode = ESCAPE; break; - case ')': evbuffer_add_printf( out, " %c", *--parents ); break; - case '\'': evbuffer_add_printf( out, "\"" ); mode = STRING; break; - case ':': if( IN_OBJECT ) { - evbuffer_add_printf( out, ": " ); mode = VAL_BEGIN; - } else { - evbuffer_add_printf( out, "%c", *str ); - } - break; - case ',': if( IN_OBJECT ) { - evbuffer_add_printf( out, ", " ); mode = STRING_BEGIN; - } else { - evbuffer_add_printf( out, "%c", *str ); - } - break; - default: evbuffer_add_printf( out, "%c", *str ); break; - } - } - } - - ret = tr_strdup( (char*) EVBUFFER_DATA( out ) ); - evbuffer_free( out ); - return ret; -} diff --git a/libtransmission/json.h b/libtransmission/json.h index 83fcf77f6..7b6672f47 100644 --- a/libtransmission/json.h +++ b/libtransmission/json.h @@ -17,7 +17,4 @@ int tr_jsonParse( const void * vbuf, struct tr_benc * setme_benc, const uint8_t ** setme_end ); -char* tr_rison2json( const char * rison, - int rison_len ); - #endif diff --git a/libtransmission/rpc-server.c b/libtransmission/rpc-server.c index 9f1cca5a9..18f570754 100644 --- a/libtransmission/rpc-server.c +++ b/libtransmission/rpc-server.c @@ -66,7 +66,7 @@ handle_rpc( struct shttpd_arg * arg ) } evbuffer_add_printf( s->out, "HTTP/1.1 200 OK\r\n" - "Content-Type: text/x-json\r\n" + "Content-Type: application/json\r\n" "Content-Length: %d\r\n" "\r\n" "%*.*s\r\n", len, len, len, response ); diff --git a/libtransmission/rpc.c b/libtransmission/rpc.c index 1a97b6750..2c68c6030 100644 --- a/libtransmission/rpc.c +++ b/libtransmission/rpc.c @@ -527,9 +527,12 @@ torrentSetPriorities( tr_handle * h, static const char* torrentAdd( tr_handle * h, tr_benc * args_in, tr_benc * args_out ) { - const char * filename; - if( !tr_bencDictFindStr( args_in, "filename", &filename ) ) - return "no filename specified"; + const char * filename = NULL; + const char * metainfo_base64 = NULL; + tr_bencDictFindStr( args_in, "filename", &filename ); + tr_bencDictFindStr( args_in, "metainfo", &metainfo_base64 ); + if( !filename && !metainfo_base64 ) + return "no filename or metainfo specified"; else { int64_t i; @@ -539,13 +542,25 @@ torrentAdd( tr_handle * h, tr_benc * args_in, tr_benc * args_out ) tr_torrent * tor; ctor = tr_ctorNew( h ); - tr_ctorSetMetainfoFromFile( ctor, filename ); + + /* 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 ); + } + + /* set the optional arguments */ if( tr_bencDictFindStr( args_in, "download-dir", &str ) ) tr_ctorSetDownloadDir( ctor, TR_FORCE, str ); if( tr_bencDictFindInt( args_in, "paused", &i ) ) tr_ctorSetPaused( ctor, TR_FORCE, i ); if( tr_bencDictFindInt( args_in, "peer-limit", &i ) ) tr_ctorSetPeerLimit( ctor, TR_FORCE, i ); + tor = tr_torrentNew( h, ctor, &err ); tr_ctorFree( ctor ); diff --git a/libtransmission/utils-test.c b/libtransmission/utils-test.c new file mode 100644 index 000000000..95430b784 --- /dev/null +++ b/libtransmission/utils-test.c @@ -0,0 +1,43 @@ +#include /* fprintf */ +#include /* strcmp */ +#include "transmission.h" +#include "utils.h" + +#define VERBOSE 0 + +int test = 0; + +#define check(A) { \ + ++test; \ + if (A) { \ + if( VERBOSE ) \ + fprintf( stderr, "PASS test #%d (%s, %d)\n", test, __FILE__, __LINE__ ); \ + } else { \ + if( VERBOSE ) \ + fprintf( stderr, "FAIL test #%d (%s, %d)\n", test, __FILE__, __LINE__ ); \ + return test; \ + } \ +} + + +int +main( void ) +{ + char *in, *out; + int len; + + /* base64 */ + in = "YOYO!"; + out = tr_base64_encode( in, -1, &len ); + check( out ); + check( !strcmp( out, "WU9ZTyE=\n" ) ); + check( len == 9 ); + in = tr_base64_decode( out, -1, &len ); + check( in ); + check( !strcmp( in, "YOYO!" ) ); + check( len == 5 ); + tr_free( in ); + tr_free( out ); + + return 0; +} diff --git a/libtransmission/utils.c b/libtransmission/utils.c index f5bfa2f3a..ff0d94637 100644 --- a/libtransmission/utils.c +++ b/libtransmission/utils.c @@ -601,6 +601,7 @@ tr_strndup( const char * in, int len ) memcpy( out, in, len ); out[len] = '\0'; } + return out; } @@ -1014,3 +1015,58 @@ tr_httpParseURL( const char * url_in, int len, tr_free( tmp ); return err; } + +#include +#include +#include +#include +#include +#include + +char * +tr_base64_encode( const void * input, int length, int * setme_len ) +{ + char * ret; + BIO * b64; + BIO * bmem; + BUF_MEM * bptr; + + if( length < 1 ) + length = strlen( input ); + + bmem = BIO_new( BIO_s_mem( ) ); + b64 = BIO_new( BIO_f_base64( ) ); + b64 = BIO_push( b64, bmem ); + BIO_write( b64, input, length ); + (void) BIO_flush( b64 ); + BIO_get_mem_ptr( b64, &bptr ); + ret = tr_strndup( bptr->data, bptr->length ); + if( setme_len ) + *setme_len = bptr->length; + + BIO_free_all( b64 ); + return ret; +} + +char * +tr_base64_decode( const void * input, int length, int * setme_len ) +{ + char * ret; + BIO * b64; + BIO * bmem; + int retlen; + + if( length < 1 ) + length = strlen( input ); + + ret = tr_new0( char, length ); + b64 = BIO_new( BIO_f_base64( ) ); + bmem = BIO_new_mem_buf( (unsigned char*)input, length ); + bmem = BIO_push( b64, bmem ); + retlen = BIO_read( bmem, ret, length ); + if( setme_len ) + *setme_len = retlen; + + BIO_free_all( bmem ); + return ret; +} diff --git a/libtransmission/utils.h b/libtransmission/utils.h index 2418656d1..d39888013 100644 --- a/libtransmission/utils.h +++ b/libtransmission/utils.h @@ -191,6 +191,9 @@ void tr_free ( void* ); char* tr_strdup( const char * str ) TR_GNUC_MALLOC; char* tr_strndup( const char * str, int len ) TR_GNUC_MALLOC; char* tr_strdup_printf( const char * fmt, ... ) TR_GNUC_PRINTF( 1, 2 ) TR_GNUC_MALLOC; +char* tr_base64_encode( const void * input, int inlen, int *outlen ) TR_GNUC_MALLOC; +char* tr_base64_decode( const void * input, int inlen, int *outlen ) TR_GNUC_MALLOC; + size_t tr_strlcpy( char * dst, const char * src, size_t siz );