Skip to content

Instantly share code, notes, and snippets.

@nkcmr
Created October 10, 2013 12:55
Show Gist options
  • Save nkcmr/6917870 to your computer and use it in GitHub Desktop.
Save nkcmr/6917870 to your computer and use it in GitHub Desktop.
It took 2 days for me to figure out how to process torrent files in JavaScript. So I am publishing my findings. Fully commented and helpful links are dropped in. I hope if you find this it saves you a few hours of hair-pulling and cursing at your screen. Spoiler alert: If you don't know a whole lot about encodings, you're gonna have a bad time ;-;
var debug = require("debug")("torrent:read"); //npm install debug
var bencode = require("bencode"); //npm install bencode - https://en.wikipedia.org/wiki/Bencode
var fs = require("fs");
var crypto = require("crypto");
var _ = require("underscore"); //npm install underscore
var percent_encoding = {
encode: function(buffer) {
var ret = "", a2z, AtoZ, zero2nine, other_valid_symbols, all_unreserved_symbols;
// ASCII table reference - http://www.ascii-code.com/
// Percent encoding reference - https://en.wikipedia.org/wiki/Percent-encoding
// ASCII codes for lower case "a" to lower case "z" is 97 - 122
a2z = _.range(97, 123);
// ASCII codes for upper case "A" to upper case "Z" is 65 - 90
AtoZ = _.range(65, 91);
// Other unreserved symbols are [ - ] , [ _ ] , [ ~ ] and [ . ]
other_valid_symbols = [45, 95, 126, 46];
// ASCII codes for 0 to 9 are 48 - 57
zero2nine = _.range(48, 58);
// Combine all of those ranges into one handy-dandy array
all_unreserved_symbols = _.union(a2z, AtoZ, other_valid_symbols, zero2nine);
// Make sure variable being passed is a buffer
if (Buffer.isBuffer(buffer)) {
// Lets parse across the buffer
for (var i = 0; i < buffer.length; i++) {
// Store the bytecode
var bytecode = buffer[i];
// Is the bytecode NOT in unreserved (Percent encoding) space?
if ( ! _.contains(all_unreserved_symbols, bytecode)) {
// bytecode is not in unreserved space - convert to hex value and append a '%'
// convert base-10 integer to its hexedecimal value - i.e 209 would become "d1"
var hex_value = buffer[i].toString(16);
// apply a "%" i.e. "d1" becomes "%d1"
var url_entity = "%" + hex_value;
//append to the return string
ret += url_entity;
} else {
//bytecode is in unreserved space - convert to ASCII representation
// Store the single bytecode into a very tiny buffer
var buff = new Buffer([bytecode]);
// Convert the buffer into ASCII code
var ascii_code = buff.toString("ascii");
//Append the friendly character to the return string
ret += ascii_code;
}
}
return ret;
} else {
throw new Error("First parameter must be a buffer!");
}
}
};
//Official torrent info_hash calculation process!
// Step 1 - read in the raw torrent file
var torrent_file = fs.readFileSync("~/Desktop/ubuntu-12.04.torrent");
// Step 2 - convert the raw torrent file from bencode to a json object
var torrent_metadata = bencode.decode(torrent_file);
// Step 3 - grab the VALUE of the info dictionary/object and then re-encode it to bencode
var info_section = bencode.encode(torrent_metadata.info);
// Very helpful information about BitTorrent files and Trackers and other stuff - https://wiki.theory.org/BitTorrentSpecification#Tracker_HTTP.2FHTTPS_Protocol
// Step 4 - calculate the sha1 hash of the bencode-ed info dictionary/object
var sha1_hash = crypto.createHash("sha1").update(info_section).digest();
// Step 5 - create a new buffer of the calculated sha1 hash of the info dictionary and don't apply any encoding
var sha1_buffer = new Buffer(sha1_hash);
// Step 6 - convert bytecodes that are in unreserved (precent encoding) space into their appropriate ascii code and reserved entities to a %[hex_value]
var info_hash = percent_encoding.encode(sha1_buffer);
debug("20-byte sha1 hash buffer: %j", sha1_buffer);
debug("40-byte sha1 hash value: %s", sha1_buffer.toString("hex"))
debug("Torrent URL-encoded info_hash: %s", percent_encoding.encode(sha1_buffer));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment