1/* 2 * This file Copyright (C) Mnemosyne LLC 3 * 4 * This file is licensed by the GPL version 2. Works owned by the 5 * Transmission project are granted a special exemption to clause 2(b) 6 * so that the bulk of its code can remain under the MIT license. 7 * This exemption does not extend to derived works not owned by 8 * the Transmission project. 9 * 10 * $Id: magnet.c 13546 2012-10-05 16:10:12Z jordan $ 11 */ 12 13#include <assert.h> 14#include <string.h> /* strchr() */ 15#include <stdio.h> /* sscanf() */ 16 17#include "transmission.h" 18#include "bencode.h" 19#include "magnet.h" 20#include "utils.h" 21#include "web.h" 22 23/*** 24**** 25***/ 26 27/* this base32 code converted from code by Robert Kaye and Gordon Mohr 28 * and is public domain. see http://bitzi.com/publicdomain for more info */ 29 30static const int base32Lookup[] = 31{ 32 0xFF,0xFF,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F, /* '0', '1', '2', '3', '4', '5', '6', '7' */ 33 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, /* '8', '9', ':', ';', '<', '=', '>', '?' */ 34 0xFF,0x00,0x01,0x02,0x03,0x04,0x05,0x06, /* '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G' */ 35 0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E, /* 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O' */ 36 0x0F,0x10,0x11,0x12,0x13,0x14,0x15,0x16, /* 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W' */ 37 0x17,0x18,0x19,0xFF,0xFF,0xFF,0xFF,0xFF, /* 'X', 'Y', 'Z', '[', '\', ']', '^', '_' */ 38 0xFF,0x00,0x01,0x02,0x03,0x04,0x05,0x06, /* '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g' */ 39 0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E, /* 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o' */ 40 0x0F,0x10,0x11,0x12,0x13,0x14,0x15,0x16, /* 'p', 'q', 'r', 's', 't', 'u', 'v', 'w' */ 41 0x17,0x18,0x19,0xFF,0xFF,0xFF,0xFF,0xFF /* 'x', 'y', 'z', '{', '|', '}', '~', 'DEL' */ 42}; 43 44static const int base32LookupLen = sizeof( base32Lookup ) / sizeof( base32Lookup[0] ); 45 46static void 47base32_to_sha1( uint8_t * out, const char * in, const int inlen ) 48{ 49 const int outlen = 20; 50 int i, index, offset; 51 52 memset( out, 0, 20 ); 53 54 assert( inlen == 32 ); 55 56 for( i=0, index=0, offset=0; i<inlen; ++i ) 57 { 58 int digit; 59 int lookup = in[i] - '0'; 60 61 /* Skip chars outside the lookup table */ 62 if( lookup<0 || lookup>=base32LookupLen ) 63 continue; 64 65 /* If this digit is not in the table, ignore it */ 66 digit = base32Lookup[lookup]; 67 if( digit == 0xFF ) 68 continue; 69 70 if( index <= 3 ) { 71 index = (index + 5) % 8; 72 if( index == 0 ) { 73 out[offset] |= digit; 74 offset++; 75 if( offset >= outlen ) 76 break; 77 } else { 78 out[offset] |= digit << (8 - index); 79 } 80 } else { 81 index = (index + 5) % 8; 82 out[offset] |= (digit >> index); 83 offset++; 84 85 if( offset >= outlen ) 86 break; 87 out[offset] |= digit << (8 - index); 88 } 89 } 90} 91 92/*** 93**** 94***/ 95 96#define MAX_TRACKERS 64 97#define MAX_WEBSEEDS 64 98 99tr_magnet_info * 100tr_magnetParse( const char * uri ) 101{ 102 bool got_checksum = false; 103 int trCount = 0; 104 int wsCount = 0; 105 char * tr[MAX_TRACKERS]; 106 char * ws[MAX_WEBSEEDS]; 107 char * displayName = NULL; 108 uint8_t sha1[SHA_DIGEST_LENGTH]; 109 tr_magnet_info * info = NULL; 110 111 if( ( uri != NULL ) && !memcmp( uri, "magnet:?", 8 ) ) 112 { 113 const char * walk; 114 115 for( walk=uri+8; walk && *walk; ) 116 { 117 const char * key = walk; 118 const char * delim = strchr( key, '=' ); 119 const char * val = delim == NULL ? NULL : delim + 1; 120 const char * next = strchr( delim == NULL ? key : val, '&' ); 121 int keylen, vallen; 122 123 if( delim != NULL ) 124 keylen = delim - key; 125 else if( next != NULL ) 126 keylen = next - key; 127 else 128 keylen = strlen( key ); 129 130 if( val == NULL ) 131 vallen = 0; 132 else if( next != NULL ) 133 vallen = next - val; 134 else 135 vallen = strlen( val ); 136 137 if( ( keylen==2 ) && !memcmp( key, "xt", 2 ) && val && !memcmp( val, "urn:btih:", 9 ) ) 138 { 139 const char * hash = val + 9; 140 const int hashlen = vallen - 9; 141 142 if( hashlen == 40 ) { 143 tr_hex_to_sha1( sha1, hash ); 144 got_checksum = true; 145 } 146 else if( hashlen == 32 ) { 147 base32_to_sha1( sha1, hash, hashlen ); 148 got_checksum = true; 149 } 150 } 151 152 if( ( vallen > 0 ) && ( keylen==2 ) && !memcmp( key, "dn", 2 ) ) 153 displayName = tr_http_unescape( val, vallen ); 154 155 if( ( vallen > 0 ) && ( trCount < MAX_TRACKERS ) ) { 156 int i; 157 if( ( keylen==2 ) && !memcmp( key, "tr", 2 ) ) 158 tr[trCount++] = tr_http_unescape( val, vallen ); 159 else if( ( sscanf( key, "tr.%d=", &i ) == 1 ) && ( i > 0 ) ) /* ticket #3341 */ 160 tr[trCount++] = tr_http_unescape( val, vallen ); 161 } 162 163 if( ( vallen > 0 ) && ( keylen==2 ) && !memcmp( key, "ws", 2 ) && ( wsCount < MAX_WEBSEEDS ) ) 164 ws[wsCount++] = tr_http_unescape( val, vallen ); 165 166 walk = next != NULL ? next + 1 : NULL; 167 } 168 } 169 170 if( got_checksum ) 171 { 172 info = tr_new0( tr_magnet_info, 1 ); 173 info->displayName = displayName; 174 info->trackerCount = trCount; 175 info->trackers = tr_memdup( tr, sizeof(char*) * trCount ); 176 info->webseedCount = wsCount; 177 info->webseeds = tr_memdup( ws, sizeof(char*) * wsCount ); 178 memcpy( info->hash, sha1, sizeof(uint8_t) * SHA_DIGEST_LENGTH ); 179 } 180 181 return info; 182} 183 184void 185tr_magnetFree( tr_magnet_info * info ) 186{ 187 if( info != NULL ) 188 { 189 int i; 190 191 for( i=0; i<info->trackerCount; ++i ) 192 tr_free( info->trackers[i] ); 193 tr_free( info->trackers ); 194 195 for( i=0; i<info->webseedCount; ++i ) 196 tr_free( info->webseeds[i] ); 197 tr_free( info->webseeds ); 198 199 tr_free( info->displayName ); 200 tr_free( info ); 201 } 202} 203 204void 205tr_magnetCreateMetainfo( const tr_magnet_info * info, tr_benc * top ) 206{ 207 int i; 208 tr_benc * d; 209 tr_bencInitDict( top, 4 ); 210 211 /* announce list */ 212 if( info->trackerCount == 1 ) 213 tr_bencDictAddStr( top, "announce", info->trackers[0] ); 214 else { 215 tr_benc * trackers = tr_bencDictAddList( top, "announce-list", info->trackerCount ); 216 for( i=0; i<info->trackerCount; ++i ) 217 tr_bencListAddStr( tr_bencListAddList( trackers, 1 ), info->trackers[i] ); 218 } 219 220 /* webseeds */ 221 if( info->webseedCount > 0 ) { 222 tr_benc * urls = tr_bencDictAddList( top, "url-list", info->webseedCount ); 223 for( i=0; i<info->webseedCount; ++i ) 224 tr_bencListAddStr( urls, info->webseeds[i] ); 225 } 226 227 /* nonstandard keys */ 228 d = tr_bencDictAddDict( top, "magnet-info", 2 ); 229 tr_bencDictAddRaw( d, "info_hash", info->hash, 20 ); 230 if( info->displayName != NULL ) 231 tr_bencDictAddStr( d, "display-name", info->displayName ); 232} 233 234 235