support custom filter error messages

In addition to returning a boolean (`true` for allowed, `false` for
disallowed), you can return an `Error` object to disallow and provide a
custom reason.

Fixes #85
This commit is contained in:
Feross Aboukhadijeh 2015-07-08 10:13:52 -07:00
parent f13accfc42
commit 89b3fb3086
3 changed files with 119 additions and 32 deletions

View File

@ -113,16 +113,18 @@ var server = new Server({
http: true, // enable http server? [default=true]
ws: true, // enable websocket server? [default=false]
filter: function (infoHash, params, cb) {
// black/whitelist for disallowing/allowing torrents [default=allow all]
// this example only allows this one torrent
cb(infoHash === 'aaa67059ed6bd08362da625b3ae77f6f4a075aaa')
// Blacklist/whitelist function for allowing/disallowing torrents. If this option is
// omitted, all torrents are allowed. It is possible to interface with a database or
// external system before deciding to allow/deny, because this function is async.
// it's possible to interface with a database or external system before
// deciding, because this function is async
// It is possible to block by peer id (whitelisting torrent clients) or by secret
// key (private trackers). Full access to the original HTTP/UDP request parameters
// are available n `params`.
// it's possible to block by peer id (whitelisting torrent clients) or
// by secret key, as you get full access to the original http/udp
// request parameters in `params`
// This example only allows one torrent.
var allowed = (infoHash === 'aaa67059ed6bd08362da625b3ae77f6f4a075aaa')
cb(allowed)
})
})

View File

@ -385,11 +385,13 @@ Server.prototype._onAnnounce = function (params, cb) {
function createSwarm () {
if (self._filter) {
self._filter(params.info_hash, params, function (allowed) {
if (allowed) {
if (allowed instanceof Error) {
cb(allowed)
} else if (!allowed) {
cb(new Error('disallowed info_hash'))
} else {
swarm = self.createSwarm(params.info_hash)
announce()
} else {
cb(new Error('disallowed info_hash'))
}
})
} else {
@ -432,7 +434,7 @@ Server.prototype._onAnnounce = function (params, cb) {
})
} // else, return full peer objects (used for websocket responses)
cb(err, response)
cb(null, response)
})
}

View File

@ -13,7 +13,7 @@ var parsedLeaves = parseTorrent(leaves)
var peerId = new Buffer('01234567890123456789')
function testFilterOption (t, serverType) {
t.plan(6)
t.plan(8)
var opts = serverType === 'http' ? { udp: false } : { http: false }
opts.filter = function (infoHash, params, cb) {
process.nextTick(function () {
@ -44,29 +44,33 @@ function testFilterOption (t, serverType) {
client.once('warning', function (err) {
t.ok(/disallowed info_hash/.test(err.message), 'got client warning')
client.destroy()
client = new Client(peerId, port, parsedLeaves)
client.destroy(function () {
t.pass('client destroyed')
client = new Client(peerId, port, parsedLeaves)
client.on('error', function (err) {
t.error(err)
})
client.on('warning', function (err) {
t.error(err)
})
client.on('update', function () {
t.pass('got announce')
client.destroy()
server.close(function () {
t.pass('server closed')
client.on('error', function (err) {
t.error(err)
})
client.on('warning', function (err) {
t.error(err)
})
})
server.on('start', function () {
t.equal(Object.keys(server.torrents).length, 1)
})
client.on('update', function () {
t.pass('got announce')
client.destroy(function () {
t.pass('client destroyed')
})
server.close(function () {
t.pass('server closed')
})
})
client.start()
server.on('start', function () {
t.equal(Object.keys(server.torrents).length, 1)
})
client.start()
})
})
server.once('warning', function (err) {
@ -85,3 +89,82 @@ test('http: filter option blocks tracker from tracking torrent', function (t) {
test('udp: filter option blocks tracker from tracking torrent', function (t) {
testFilterOption(t, 'udp')
})
function testFilterCustomError (t, serverType) {
t.plan(8)
var opts = serverType === 'http' ? { udp: false } : { http: false }
opts.filter = function (infoHash, params, cb) {
process.nextTick(function () {
if (infoHash === parsedBitlove.infoHash) cb(new Error('bitlove blocked'))
else cb(true)
})
}
var server = new Server(opts)
server.on('error', function (err) {
t.error(err)
})
server.listen(0, function () {
var port = server[serverType].address().port
var announceUrl = serverType === 'http'
? 'http://127.0.0.1:' + port + '/announce'
: 'udp://127.0.0.1:' + port
parsedBitlove.announce = [ announceUrl ]
parsedLeaves.announce = [ announceUrl ]
var client = new Client(peerId, port, parsedBitlove)
client.on('error', function (err) {
t.error(err)
})
client.once('warning', function (err) {
t.ok(/bitlove blocked/.test(err.message), 'got client warning')
client.destroy(function () {
t.pass('client destroyed')
client = new Client(peerId, port, parsedLeaves)
client.on('error', function (err) {
t.error(err)
})
client.on('warning', function (err) {
t.error(err)
})
client.on('update', function () {
t.pass('got announce')
client.destroy(function () {
t.pass('client destroyed')
})
server.close(function () {
t.pass('server closed')
})
})
server.on('start', function () {
t.equal(Object.keys(server.torrents).length, 1)
})
client.start()
})
})
server.once('warning', function (err) {
t.ok(/bitlove blocked/.test(err.message), 'got server warning')
t.equal(Object.keys(server.torrents).length, 0)
})
client.start()
})
}
test('http: filter option with custom error', function (t) {
testFilterCustomError(t, 'http')
})
test('udp: filter option filter option with custom error', function (t) {
testFilterCustomError(t, 'udp')
})