const Router = require('express-promise-router'); const bencode = require('bencode'); const qs = require('querystring'); const peerListFormatters = require('../lib/peerListFormatters'); const bqs = require('../lib/binaryQuerystring'); const INFO_HASH_REGEX = new RegExp('^[0-9a-f]{40}$', 'i'); module.exports = ({ dht }) => { const app = Router(); app.get( '/', async ( { log, originalUrl, query: { info_hash: infoHash, compact, json, event = '' }, }, res, ) => { // worst case if every character is percent encoded, or best case if every _is_ decoded if (!infoHash || infoHash.length > 60 || infoHash < 20) { res .status(400) .end(bencode.encode({ 'failure reason': 'invalid arguments' })); return; } if (infoHash.length !== 20) { infoHash = bqs.fromUrl(originalUrl).info_hash; if (!infoHash || infoHash.length !== 20) { res .status(400) .end(bencode.encode({ 'failure reason': 'invalid arguments' })); log({ status: 'refused', type: 'dhtGateway' }); return; } } infoHash = Buffer.from(infoHash, 'latin1').toString('hex'); if (!infoHash.match(INFO_HASH_REGEX)) { res .status(400) .end(bencode.encode({ 'failure reason': 'invalid arguments' })); log({ status: 'refused', type: 'dhtGateway' }); return; } let format = 'bencoded'; if (compact === '1') { format = 'compact'; } else if (json === '1') { format = 'json'; } try { let peers = []; // a completed or stopped torrent does not need peers if (event !== 'completed' && event !== 'stopped') { peers = await dht.lookup(infoHash); } // this sends the response as well as formatting it peerListFormatters(peers, res, format); log({ status: 'served', type: 'dhtGateway', format, count: peers.length, }); } catch (e) { res .status(500) .end(bencode.encode({ 'failure reason': 'internal error' })); log({ status: 'failed', type: 'dhtGateway', format, error: e.message, stack: e.stack, }); } }, ); return app; };