From 526ec9a44980e95274b29e66278ea488f2681728 Mon Sep 17 00:00:00 2001 From: Feross Aboukhadijeh Date: Fri, 7 Apr 2017 18:02:07 -0700 Subject: [PATCH] Add `origin` option for torrent.createServer() When the origin option is specified, only requests from the given origin will be allowed. This is useful to add additional security to any app that is starting a WebTorrent server but doesn't want it to be exposed to the entire Web. --- docs/api.md | 12 +++++++++--- lib/server.js | 30 ++++++++++++++++++++++++------ 2 files changed, 33 insertions(+), 9 deletions(-) diff --git a/docs/api.md b/docs/api.md index 70046e4d..65316ea5 100644 --- a/docs/api.md +++ b/docs/api.md @@ -85,7 +85,7 @@ If `opts` is specified, then the default options (shown below) will be overridde ```js { - announce: [], // Torrent trackers to use (added to list in .torrent or magnet uri) + announce: [String], // Torrent trackers to use (added to list in .torrent or magnet uri) getAnnounceOpts: Function, // Custom callback to allow sending extra parameters to the tracker maxWebConns: Number, // Max number of simultaneous connections per web seed [default=4] path: String, // Folder to download files to (default=`/tmp/webtorrent/`) @@ -316,13 +316,19 @@ Deprioritizes a range of previously selected pieces. Marks a range of pieces as critical priority to be downloaded ASAP. From `start` to `end` (both inclusive). -## `torrent.createServer([requestListener])` +## `torrent.createServer([opts])` Create an http server to serve the contents of this torrent, dynamically fetching the needed torrent pieces to satisfy http requests. Range requests are supported. Returns an `http.Server` instance (got from calling `http.createServer`). If -`requestListener` is specified, it is added to the 'request' event. +`opts` is specified, it can have the following properties: + +```js +{ + origin: String // Allow requests from specific origin. `false` for same-origin. [default: '*'] +} +``` Visiting the root of the server `/` will show a list of links to individual files. Access individual files at `/` where `` is the index in the `torrent.files` array diff --git a/lib/server.js b/lib/server.js index af1665d7..be45ddc1 100644 --- a/lib/server.js +++ b/lib/server.js @@ -7,8 +7,10 @@ var pump = require('pump') var rangeParser = require('range-parser') var url = require('url') -function Server (torrent, requestListener) { - var server = http.createServer(requestListener) +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 = [] @@ -41,6 +43,21 @@ function Server (torrent, requestListener) { 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) @@ -56,9 +73,9 @@ function Server (torrent, requestListener) { return serve404Page() } - // Allow CORS requests to read responses - if (req.headers.origin) { - res.setHeader('Access-Control-Allow-Origin', req.headers.origin || '*') + // Allow cross-origin requests (CORS) + if (isOriginAllowed(req)) { + res.setHeader('Access-Control-Allow-Origin', req.headers.origin) } // Prevent browser mime-type sniffing @@ -68,7 +85,8 @@ function Server (torrent, requestListener) { // by responding to the OPTIONS preflight request with the specified // origin and requested headers. if (req.method === 'OPTIONS') { - return serveOptionsRequest() + if (isOriginAllowed(req)) return serveOptionsRequest() + else return serveMethodNotAllowed() } if (req.method === 'GET' || req.method === 'HEAD') {