mirror of
https://github.com/transmission/transmission
synced 2024-12-24 08:43:27 +00:00
(trunk libT) #2096: add code magnet URL parser and unit tests
This commit is contained in:
parent
4b563b7bde
commit
9383a6abfd
9 changed files with 367 additions and 6 deletions
|
@ -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}
|
||||
|
|
92
libtransmission/magnet-test.c
Normal file
92
libtransmission/magnet-test.c
Normal file
|
@ -0,0 +1,92 @@
|
|||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#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;
|
||||
}
|
||||
|
184
libtransmission/magnet.c
Normal file
184
libtransmission/magnet.c
Normal file
|
@ -0,0 +1,184 @@
|
|||
/*
|
||||
* This file Copyright (C) 2009 Charles Kerr <charles@transmissionbt.com>
|
||||
*
|
||||
* 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 <assert.h>
|
||||
#include <ctype.h> /* tolower() */
|
||||
#include <string.h> /* 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<inlen; ++i )
|
||||
{
|
||||
int digit;
|
||||
int lookup = in[i] - '0';
|
||||
|
||||
/* Skip chars outside the lookup table */
|
||||
if( lookup<0 || lookup>=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; i<info->announceCount; ++i )
|
||||
tr_free( info->announceURLs[i] );
|
||||
|
||||
tr_free( info->announceURLs );
|
||||
tr_free( info->displayName );
|
||||
tr_free( info );
|
||||
}
|
||||
}
|
31
libtransmission/magnet.h
Normal file
31
libtransmission/magnet.h
Normal file
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* This file Copyright (C) 2009 Charles Kerr <charles@transmissionbt.com>
|
||||
*
|
||||
* 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
|
|
@ -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( ) ) )
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
|
||||
/***
|
||||
****
|
||||
***/
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue