module.exports = Server var arrayRemove = require('unordered-array-remove') var http = require('http') var mime = require('mime') var pump = require('pump') var rangeParser = require('range-parser') var url = require('url') function Server (torrent, opts) { var server = http.createServer() if (!opts) opts = {} if (!opts.origin) opts.origin = '*' // allow all origins by default var sockets = [] var pendingReady = [] var closed = false server.on('connection', onConnection) server.on('request', onRequest) var _close = server.close server.close = function (cb) { closed = true server.removeListener('connection', onConnection) server.removeListener('request', onRequest) while (pendingReady.length) { var onReady = pendingReady.pop() torrent.removeListener('ready', onReady) } torrent = null _close.call(server, cb) } server.destroy = function (cb) { sockets.forEach(function (socket) { socket.destroy() }) // Only call `server.close` if user has not called it already if (!cb) cb = function () {} if (closed) process.nextTick(cb) else server.close(cb) } function isOriginAllowed (req) { // When `origin` option is `false`, deny all cross-origin requests if (opts.origin === false) return false // Requests without an 'Origin' header are not actually cross-origin, so just // deny them if (req.headers.origin == null) return false // The user allowed all origins if (opts.origin === '*') return true // Allow requests where the 'Origin' header matches the `opts.origin` setting return req.headers.origin === opts.origin } function onConnection (socket) { socket.setTimeout(36000000) sockets.push(socket) socket.once('close', function () { arrayRemove(sockets, sockets.indexOf(socket)) }) } function onRequest (req, res) { var pathname = url.parse(req.url).pathname if (pathname === '/favicon.ico') { return serve404Page() } // Allow cross-origin requests (CORS) if (isOriginAllowed(req)) { res.setHeader('Access-Control-Allow-Origin', req.headers.origin) } // Prevent browser mime-type sniffing res.setHeader('X-Content-Type-Options', 'nosniff') // Allow CORS requests to specify arbitrary headers, e.g. 'Range', // by responding to the OPTIONS preflight request with the specified // origin and requested headers. if (req.method === 'OPTIONS') { if (isOriginAllowed(req)) return serveOptionsRequest() else return serveMethodNotAllowed() } if (req.method === 'GET' || req.method === 'HEAD') { if (torrent.ready) { handleRequest() } else { pendingReady.push(onReady) torrent.once('ready', onReady) } return } return serveMethodNotAllowed() function serveOptionsRequest () { res.statusCode = 204 // no content res.setHeader('Access-Control-Max-Age', '600') res.setHeader('Access-Control-Allow-Methods', 'GET,HEAD,PUT,PATCH,POST,DELETE') if (req.headers['access-control-request-headers']) { res.setHeader( 'Access-Control-Allow-Headers', req.headers['access-control-request-headers'] ) } res.end() } function onReady () { arrayRemove(pendingReady, pendingReady.indexOf(onReady)) handleRequest() } function handleRequest () { if (pathname === '/') { return serveIndexPage() } var index = Number(pathname.split('/')[1]) if (Number.isNaN(index) || index >= torrent.files.length) { return serve404Page() } var file = torrent.files[index] serveFile(file) } function serveIndexPage () { res.statusCode = 200 res.setHeader('Content-Type', 'text/html') var listHtml = torrent.files.map(function (file, i) { return '