From 9383a6abfd63d250466227dfbb5dfc0a669c1174 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Fri, 20 Nov 2009 04:38:19 +0000 Subject: [PATCH] (trunk libT) #2096: add code magnet URL parser and unit tests --- libtransmission/Makefile.am | 7 ++ libtransmission/magnet-test.c | 92 +++++++++++++++++ libtransmission/magnet.c | 184 ++++++++++++++++++++++++++++++++++ libtransmission/magnet.h | 31 ++++++ libtransmission/utils-test.c | 19 ++++ libtransmission/utils.c | 24 ++++- libtransmission/utils.h | 5 +- libtransmission/web.c | 9 ++ libtransmission/web.h | 2 + 9 files changed, 367 insertions(+), 6 deletions(-) create mode 100644 libtransmission/magnet-test.c create mode 100644 libtransmission/magnet.c create mode 100644 libtransmission/magnet.h diff --git a/libtransmission/Makefile.am b/libtransmission/Makefile.am index 0e3a206e1..6e3cd6b4f 100644 --- a/libtransmission/Makefile.am +++ b/libtransmission/Makefile.am @@ -27,6 +27,7 @@ libtransmission_a_SOURCES = \ json.c \ JSON_parser.c \ list.c \ + magnet.c \ makemeta.c \ metainfo.c \ natpmp.c \ @@ -73,6 +74,7 @@ noinst_HEADERS = \ json.h \ JSON_parser.h \ list.h \ + magnet.h \ makemeta.h \ metainfo.h \ natpmp.h \ @@ -108,6 +110,7 @@ TESTS = \ bencode-test \ clients-test \ json-test \ + magnet-test \ peer-msgs-test \ rpc-test \ test-peer-id \ @@ -146,6 +149,10 @@ json_test_SOURCES = json-test.c json_test_LDADD = ${apps_ldadd} json_test_LDFLAGS = ${apps_ldflags} +magnet_test_SOURCES = magnet-test.c +magnet_test_LDADD = ${apps_ldadd} +magnet_test_LDFLAGS = ${apps_ldflags} + rpc_test_SOURCES = rpc-test.c rpc_test_LDADD = ${apps_ldadd} rpc_test_LDFLAGS = ${apps_ldflags} diff --git a/libtransmission/magnet-test.c b/libtransmission/magnet-test.c new file mode 100644 index 000000000..0b754a747 --- /dev/null +++ b/libtransmission/magnet-test.c @@ -0,0 +1,92 @@ +#include +#include +#include "transmission.h" +#include "magnet.h" +#include "utils.h" + +/* #define VERBOSE */ +#undef VERBOSE + +static int test = 0; + +#ifdef VERBOSE + #define check( A ) \ + { \ + ++test; \ + if( A ){ \ + fprintf( stderr, "PASS test #%d (%s, %d)\n", test, __FILE__, __LINE__ ); \ + } else { \ + fprintf( stderr, "FAIL test #%d (%s, %d)\n", test, __FILE__, __LINE__ ); \ + return test; \ + } \ + } +#else + #define check( A ) \ + { \ + ++test; \ + if( !( A ) ){ \ + fprintf( stderr, "FAIL test #%d (%s, %d)\n", test, __FILE__, __LINE__ ); \ + return test; \ + } \ + } +#endif + +static int +test1( void ) +{ + int i; + const char * uri; + tr_magnet_info * info; + const int dec[] = { 210, 53, 64, 16, 163, 202, 74, 222, 91, 116, + 39, 187, 9, 58, 98, 163, 137, 159, 243, 129 }; + + uri = "magnet:?xt=urn:btih:" + "d2354010a3ca4ade5b7427bb093a62a3899ff381" + "&dn=Display%20Name" + "&tr=http%3A%2F%2Ftracker.openbittorrent.com%2Fannounce" + "&tr=http%3A%2F%2Ftracker.opentracker.org%2Fannounce"; + info = tr_magnetParse( uri ); + check( info != NULL ) + check( info->announceCount == 2 ); + check( !strcmp( info->announceURLs[0], "http://tracker.openbittorrent.com/announce" ) ) + check( !strcmp( info->announceURLs[1], "http://tracker.opentracker.org/announce" ) ) + check( !strcmp( info->displayName, "Display Name" ) ) + for( i=0; i<20; ++i ) + check( info->hash[i] == dec[i] ); + tr_magnetFree( info ); + info = NULL; + + /* same thing but in base32 encoding */ + uri = "magnet:?xt=urn:btih:" + "2I2UAEFDZJFN4W3UE65QSOTCUOEZ744B" + "&dn=Display%20Name" + "&tr=http%3A%2F%2Ftracker.openbittorrent.com%2Fannounce" + "&tr=http%3A%2F%2Ftracker.opentracker.org%2Fannounce"; + info = tr_magnetParse( uri ); + check( info != NULL ) + check( info->announceCount == 2 ); + check( !strcmp( info->announceURLs[0], "http://tracker.openbittorrent.com/announce" ) ) + check( !strcmp( info->announceURLs[1], "http://tracker.opentracker.org/announce" ) ) + check( !strcmp( info->displayName, "Display Name" ) ) + for( i=0; i<20; ++i ) + check( info->hash[i] == dec[i] ); + tr_magnetFree( info ); + info = NULL; + + return 0; +} + +int +main( void ) +{ + int i; + + if( ( i = test1( ) ) ) + return i; + +#ifdef VERBOSE + fprintf( stderr, "magnet-test passed\n" ); +#endif + return 0; +} + diff --git a/libtransmission/magnet.c b/libtransmission/magnet.c new file mode 100644 index 000000000..4ff0f69b6 --- /dev/null +++ b/libtransmission/magnet.c @@ -0,0 +1,184 @@ +/* + * This file Copyright (C) 2009 Charles Kerr + * + * This file is licensed by the GPL version 2. Works owned by the + * Transmission project are granted a special exemption to clause 2(b) + * so that the bulk of its code can remain under the MIT license. + * This exemption does not extend to derived works not owned by + * the Transmission project. + * + * $Id:$ + */ + +#include +#include /* tolower() */ +#include /* strchr() */ + +#include "transmission.h" +#include "magnet.h" +#include "utils.h" +#include "web.h" + +/*** +**** +***/ + +/* this base32 code converted from code by Robert Kaye and Gordon Mohr + * and is public domain. see http://bitzi.com/publicdomain for more info */ + +static const int base32Lookup[] = +{ + 0xFF,0xFF,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F, // '0', '1', '2', '3', '4', '5', '6', '7' + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, // '8', '9', ':', ';', '<', '=', '>', '?' + 0xFF,0x00,0x01,0x02,0x03,0x04,0x05,0x06, // '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G' + 0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E, // 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O' + 0x0F,0x10,0x11,0x12,0x13,0x14,0x15,0x16, // 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W' + 0x17,0x18,0x19,0xFF,0xFF,0xFF,0xFF,0xFF, // 'X', 'Y', 'Z', '[', '\', ']', '^', '_' + 0xFF,0x00,0x01,0x02,0x03,0x04,0x05,0x06, // '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g' + 0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E, // 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o' + 0x0F,0x10,0x11,0x12,0x13,0x14,0x15,0x16, // 'p', 'q', 'r', 's', 't', 'u', 'v', 'w' + 0x17,0x18,0x19,0xFF,0xFF,0xFF,0xFF,0xFF // 'x', 'y', 'z', '{', '|', '}', '~', 'DEL' +}; + +static const int base32LookupLen = sizeof( base32Lookup ) / sizeof( base32Lookup[0] ); + +static void +base32_to_sha1( uint8_t * out, const char * in, const int inlen ) +{ + const int outlen = 20; + int i, index, offset; + + memset( out, 0, 20 ); + + assert( inlen == 32 ); + + for( i=0, index=0, offset=0; i=base32LookupLen ) + continue; + + /* If this digit is not in the table, ignore it */ + digit = base32Lookup[lookup]; + if( digit == 0xFF ) + continue; + + if( index <= 3 ) { + index = (index + 5) % 8; + if( index == 0 ) { + out[offset] |= digit; + offset++; + if( offset >= outlen ) + break; + } else { + out[offset] |= digit << (8 - index); + } + } else { + index = (index + 5) % 8; + out[offset] |= (digit >> index); + offset++; + + if( offset >= outlen ) + break; + out[offset] |= digit << (8 - index); + } + } +} + +/*** +**** +***/ + +#define MAX_TRACKERS 64 + +tr_magnet_info * +tr_magnetParse( const char * uri ) +{ + tr_bool got_checksum = FALSE; + int announceCount = 0; + char * announceURLs[MAX_TRACKERS]; + char * displayName = NULL; + uint8_t sha1[SHA_DIGEST_LENGTH]; + tr_magnet_info * info = NULL; + + if( ( uri != NULL ) && !memcmp( uri, "magnet:?", 8 ) ) + { + const char * walk; + + for( walk=uri+8; walk && *walk; ) + { + const char * key = walk; + const char * delim = strchr( key, '=' ); + const char * val = delim == NULL ? NULL : delim + 1; + const char * next = strchr( delim == NULL ? key : val, '&' ); + int keylen, vallen; + + if( delim != NULL ) + keylen = delim - key; + else if( next != NULL ) + keylen = next - key; + else + keylen = strlen( key ); + + if( val == NULL ) + vallen = 0; + else if( next != NULL ) + vallen = next - val; + else + vallen = strlen( val ); + + if( ( keylen==2 ) && !memcmp( key, "xt", 2 ) && !memcmp( val, "urn:btih:", 9 ) ) + { + const char * hash = val + 9; + const int hashlen = vallen - 9; + + if( hashlen == 40 ) { + tr_hex_to_sha1( sha1, hash ); + got_checksum = TRUE; + } + else if( hashlen == 32 ) { + base32_to_sha1( sha1, hash, hashlen ); + got_checksum = TRUE; + } + } + + if( ( keylen==2 ) && !memcmp( key, "dn", 2 ) ) + displayName = tr_http_unescape( val, vallen ); + + if( ( keylen==2 ) && !memcmp( key, "tr", 2 ) ) + announceURLs[announceCount++] = tr_http_unescape( val, vallen ); + + walk = next != NULL ? next + 1 : NULL; + } + } + + if( got_checksum ) + { + info = tr_new0( tr_magnet_info, 1 ); + info->displayName = displayName; + info->announceCount = announceCount; + info->announceURLs = tr_memdup( announceURLs, sizeof(char*) * announceCount ); + memcpy( info->hash, sha1, sizeof(uint8_t) * SHA_DIGEST_LENGTH ); + } + + return info; +} + +void +tr_magnetFree( tr_magnet_info * info ) +{ + if( info != NULL ) + { + int i; + + for( i=0; iannounceCount; ++i ) + tr_free( info->announceURLs[i] ); + + tr_free( info->announceURLs ); + tr_free( info->displayName ); + tr_free( info ); + } +} diff --git a/libtransmission/magnet.h b/libtransmission/magnet.h new file mode 100644 index 000000000..3ab92b715 --- /dev/null +++ b/libtransmission/magnet.h @@ -0,0 +1,31 @@ +/* + * This file Copyright (C) 2009 Charles Kerr + * + * This file is licensed by the GPL version 2. Works owned by the + * Transmission project are granted a special exemption to clause 2(b) + * so that the bulk of its code can remain under the MIT license. + * This exemption does not extend to derived works not owned by + * the Transmission project. + * + * $Id:$ + */ + +#ifndef TR_MAGNET_H +#define TR_MAGNET_H 1 + +#include "transmission.h" + +typedef struct +{ + uint8_t hash[20]; + char * displayName; + char ** announceURLs; + int announceCount; +} +tr_magnet_info; + +tr_magnet_info * tr_magnetParse( const char * uri ); + +void tr_magnetFree( tr_magnet_info * info ); + +#endif diff --git a/libtransmission/utils-test.c b/libtransmission/utils-test.c index 97bdf7a5e..a087e4d6a 100644 --- a/libtransmission/utils-test.c +++ b/libtransmission/utils-test.c @@ -8,6 +8,7 @@ #include "utils.h" #include "crypto.h" +/* #define VERBOSE */ #undef VERBOSE #define NUM_LOOPS 1 #define SPEED_TEST 0 @@ -281,6 +282,22 @@ test_memmem( void ) return 0; } +static int +test_hex( void ) +{ + char hex1[41]; + char hex2[41]; + uint8_t sha1[20]; + /*uint8_t sha2[20];*/ + + memcpy( hex1, "fb5ef5507427b17e04b69cef31fa3379b456735a", 41 ); + tr_hex_to_sha1( sha1, hex1 ); + tr_sha1_to_hex( hex2, sha1 ); + check( !strcmp( hex1, hex2 ) ) + + return 0; +} + int main( void ) { @@ -308,6 +325,8 @@ main( void ) tr_free( in ); tr_free( out ); + if( ( i = test_hex( ) ) ) + return i; if( ( i = test_lowerbound( ) ) ) return i; if( ( i = test_strstrip( ) ) ) diff --git a/libtransmission/utils.c b/libtransmission/utils.c index 0e40e1c5e..d063fd3a3 100644 --- a/libtransmission/utils.c +++ b/libtransmission/utils.c @@ -902,21 +902,35 @@ tr_getRatio( double numerator, } void -tr_sha1_to_hex( char * out, - const uint8_t * sha1 ) +tr_sha1_to_hex( char * out, const uint8_t * sha1 ) { + int i; static const char hex[] = "0123456789abcdef"; - int i; - for( i = 0; i < 20; i++ ) + for( i=0; i<20; ++i ) { - unsigned int val = *sha1++; + const unsigned int val = *sha1++; *out++ = hex[val >> 4]; *out++ = hex[val & 0xf]; } + *out = '\0'; } +void +tr_hex_to_sha1( uint8_t * out, const char * in ) +{ + int i; + static const char hex[] = "0123456789abcdef"; + + for( i=0; i<20; ++i ) + { + const int hi = strchr( hex, *in++ ) - hex; + const int lo = strchr( hex, *in++ ) - hex; + *out++ = (uint8_t)( (hi<<4) | lo ); + } +} + /*** **** ***/ diff --git a/libtransmission/utils.h b/libtransmission/utils.h index b95339025..f1de35133 100644 --- a/libtransmission/utils.h +++ b/libtransmission/utils.h @@ -377,9 +377,12 @@ void tr_set_compare( const void * a, size_t aCount, tr_set_func in_both_cb, void * userData ); -void tr_sha1_to_hex( char * out, +void tr_sha1_to_hex( char * out, const uint8_t * sha1 ) TR_GNUC_NONNULL(1,2); +void tr_hex_to_sha1( uint8_t * out, + const char * hex ) TR_GNUC_NONNULL(1,2); + tr_bool tr_httpIsValidURL( const char * url ) TR_GNUC_NONNULL(1); diff --git a/libtransmission/web.c b/libtransmission/web.c index a71ab72ff..4817a03e8 100644 --- a/libtransmission/web.c +++ b/libtransmission/web.c @@ -684,3 +684,12 @@ tr_http_escape( struct evbuffer *out, const char *str, int len, int keep_slashes } } } + +char* +tr_http_unescape( const char * str, int len ) +{ + char * tmp = curl_unescape( str, len ); + char * ret = tr_strdup( tmp ); + curl_free( tmp ); + return ret; +} diff --git a/libtransmission/web.h b/libtransmission/web.h index acd0aa0d0..0d385fe95 100644 --- a/libtransmission/web.h +++ b/libtransmission/web.h @@ -40,4 +40,6 @@ struct evbuffer; void tr_http_escape( struct evbuffer *out, const char *str, int len, int noslashes ); +char* tr_http_unescape( const char * str, int len ); + #endif