Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Convert Torrent info_hash from bencoded to URLEncoded data

I am creating torrent scraper in objective-c and I am using AFNetworking for the HTTP requests. I need to send a sha1 hash of the part of the meta info for a tracker request. I have successfully created the hash and verified that it is correct. I can not put the hash in an NSString since it does not encode binary data so I have put it in an NSData object and then in the parameters to send. This is what I have right now, but I always get an error back, and I would assume it is with the methods I am using to send the hash. I have also tried url encoding the hash and then putting it in an NSString with no avail

 NSMutableDictionary *parameters = [NSMutableDictionary dictionary];

 unsigned char infoHash[20];
 [self.tracker generateTorrentInfoHash:infoHash];

 const char peer_id[20] = "randomstringfornow";

[parameters setObject:[NSData dataWithBytes:&infoHash length:20] forKey:@"info_hash"];    
[parameters setObject:[NSData dataWithBytes:&peer_id length:20] forKey:@"peer_id"];
[parameters setObject:@(8080) forKey:@"port"];
[parameters setObject:@(0) forKey:@"uploaded"];
[parameters setObject:@(self.tracker.metaInfo.totalFileSize) forKey:@"left"];
[parameters setObject:@(0) forKey:@"downloaded"];
[parameters setObject:@(0) forKey:@"compact"];
[parameters setObject:@"stopped" forKey:@"event"];


[self getPath:@"" parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
    NSLog(@"%@",operation.responseString);


   } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
    NSLog(@"%@",operation.responseString);
}];

Does anyone know if there is a possible way of doing this with AFNetworking or maybe I am hopefully missing something simple.

like image 300
Otium Avatar asked Aug 10 '12 03:08

Otium


1 Answers

Alright, I'm not an expert at torrents, but I think the challenge you're having is that the bencoded data representation you're pulling from encodes the SHA1 hash as 20 raw bytes of data.

You can't send raw data like that in the GET portion of an HTTP request, so you're going to need to encode it. Based on the specs, it appears that urlencoding the raw data is the official way to do so. (Other options would be to convert it to a human-readable hex string, for example).

From this Alternative Bittorrent Specification:

The tracker is an HTTP/HTTPS service which responds to HTTP GET requests. The requests include metrics from clients that help the tracker keep overall statistics about the torrent. The response includes a peer list that helps the client participate in the torrent. The base URL consists of the "announce URL" as defined in the metainfo (.torrent) file. The parameters are then added to this URL, using standard CGI methods (i.e. a '?' after the announce URL, followed by 'param=value' sequences separated by '&').

Note that all binary data in the URL (particularly info_hash and peer_id) must be properly escaped. This means any byte not in the set 0-9, a-z, A-Z, '.', '-', '_' and '~', must be encoded using the "%nn" format, where nn is the hexadecimal value of the byte. (See RFC1738 for details.)

For a 20-byte hash of \x12\x34\x56\x78\x9a\xbc\xde\xf1\x23\x45\x67\x89\xab\xcd\xef\x12\x34\x56\x78\x9a

The right encoded form is %124Vx%9A%BC%DE%F1%23Eg%89%AB%CD%EF%124Vx%9A

Starting with your raw 20 char C array, you need to get to a URLEncoded NSString.

Unfortunately, especially with this type of data, the simple method - using stringByAddingPercentEscapesUsingEncoding in a loop - won't work because it does not end up with a safe encoding to send as a GET parameter.

With a bit of S.O. help ( How do I URL encode a string ), here is a bit of code that you can mold into the final product, but meets the spec doc for correct output:

const char source[20] = {0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf1, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x12, 0x34, 0x56, 0x78, 0x9a};

NSMutableString *output = [NSMutableString string];

NSLog(@"Raw Data: %@", [NSData dataWithBytes:source length:20]);

int sourceLen = sizeof(source);

for (int i = 0; i < sourceLen; ++i) {
    const unsigned char thisChar = source[i];
    if (thisChar == ' '){
        [output appendString:@"+"];
    } else if (thisChar == '.' || thisChar == '-' || thisChar == '_' || thisChar == '~' || 
               (thisChar >= 'a' && thisChar <= 'z') ||
               (thisChar >= 'A' && thisChar <= 'Z') ||
               (thisChar >= '0' && thisChar <= '9')) {
        [output appendFormat:@"%c", thisChar];
    } else {
        [output appendFormat:@"%%%02X", thisChar];
    }
}

NSLog(@"Encoded:  %@", output);

Output is:

Raw Data: <12345678 9abcdef1 23456789 abcdef12 3456789a>
Encoded:  %124Vx%9A%BC%DE%F1%23Eg%89%AB%CD%EF%124Vx%9A

Good luck!

like image 198
Adam B Avatar answered Nov 08 '22 18:11

Adam B