diff --git a/lib/server.js b/lib/server.js
index a812eab8..af46b854 100644
--- a/lib/server.js
+++ b/lib/server.js
@@ -1,7 +1,6 @@
module.exports = Server
var arrayRemove = require('unordered-array-remove')
-var debug = require('debug')('webtorrent:server')
var http = require('http')
var mime = require('mime')
var pump = require('pump')
@@ -51,59 +50,108 @@ function Server (torrent, opts) {
}
function onRequest (req, res) {
- debug('onRequest')
+ var pathname = url.parse(req.url).pathname
+
+ if (pathname === '/favicon.ico') {
+ return serve404Page()
+ }
+
+ // Allow CORS requests to read responses
+ if (req.headers.origin) {
+ 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' && req.headers['access-control-request-headers']) {
- res.setHeader('Access-Control-Allow-Methods', 'POST, GET, OPTIONS')
- res.setHeader(
- 'Access-Control-Allow-Headers',
- req.headers['access-control-request-headers']
- )
- res.setHeader('Access-Control-Max-Age', '1728000')
- return res.end()
+ if (req.method === 'OPTIONS') {
+ return serveOptionsRequest()
}
- if (req.headers.origin) {
- res.setHeader('Access-Control-Allow-Origin', req.headers.origin)
+ if (req.method === 'GET' || req.method === 'HEAD') {
+ if (torrent.ready) {
+ handleRequest()
+ } else {
+ pendingReady.push(onReady)
+ torrent.once('ready', onReady)
+ }
+ return
}
- var pathname = url.parse(req.url).pathname
- if (pathname === '/favicon.ico') return res.end()
+ 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 (torrent.ready) {
- onReady()
- } else {
- pendingReady.push(onReady)
- torrent.once('ready', onReady)
+ 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 === '/') {
- res.setHeader('Content-Type', 'text/html')
- var listHtml = torrent.files.map(function (file, i) {
- return '
' + file.path + ' ' +
- '(' + file.length + ' bytes)'
- }).join('
')
-
- var html = '' + torrent.name + '
' + listHtml + '
'
- return res.end(html)
+ return serveIndexPage()
}
var index = Number(pathname.slice(1))
if (Number.isNaN(index) || index >= torrent.files.length) {
- res.statusCode = 404
- return res.end('404 Not Found')
+ return serve404Page()
}
var file = torrent.files[index]
+ serveFile(file)
+ }
- res.setHeader('Accept-Ranges', 'bytes')
- res.setHeader('Content-Type', mime.lookup(file.name))
+ function serveIndexPage () {
res.statusCode = 200
+ res.setHeader('Content-Type', 'text/html')
+
+ var listHtml = torrent.files.map(function (file, i) {
+ return '' + file.path + ' ' +
+ '(' + file.length + ' bytes)'
+ }).join('
')
+
+ var html = getPageHTML(
+ torrent.name + ' - WebTorrent',
+ '' + torrent.name + '
' + listHtml + '
'
+ )
+ res.end(html)
+ }
+
+ function serve404Page () {
+ res.statusCode = 404
+ res.setHeader('Content-Type', 'text/html')
+
+ var html = getPageHTML('404 - Not Found', '404 - Not Found
')
+ res.end(html)
+ }
+
+ function serveFile (file) {
+ res.statusCode = 200
+ res.setHeader('Content-Type', mime.lookup(file.name))
+
+ // Support range-requests
+ res.setHeader('Accept-Ranges', 'bytes')
+
+ // Set name of file (for "Save Page As..." dialog)
+ res.setHeader(
+ 'Content-Disposition',
+ 'inline; filename*=UTF-8\'\'' + encodeRFC5987(file.name)
+ )
// Support DLNA streaming
res.setHeader('transferMode.dlna.org', 'Streaming')
@@ -117,11 +165,11 @@ function Server (torrent, opts) {
var range = rangeParser(file.length, req.headers.range || '')
if (Array.isArray(range)) {
+ res.statusCode = 206 // indicates that range-request was understood
+
// no support for multi-range request, just use the first range
range = range[0]
- res.statusCode = 206
- debug('range %s', JSON.stringify(range))
res.setHeader(
'Content-Range',
'bytes ' + range.start + '-' + range.end + '/' + file.length
@@ -138,7 +186,33 @@ function Server (torrent, opts) {
pump(file.createReadStream(range), res)
}
+
+ function serveMethodNotAllowed () {
+ res.statusCode = 405
+ res.setHeader('Content-Type', 'text/html')
+ var html = getPageHTML('405 - Method Not Allowed', '405 - Method Not Allowed
')
+ res.end(html)
+ }
}
return server
}
+
+function getPageHTML (title, pageHtml) {
+ return '' +
+ '' +
+ '' + title + '' +
+ '' + pageHtml + ''
+}
+
+// From https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent
+function encodeRFC5987 (str) {
+ return encodeURIComponent(str)
+ // Note that although RFC3986 reserves "!", RFC5987 does not,
+ // so we do not need to escape it
+ .replace(/['()]/g, escape) // i.e., %27 %28 %29
+ .replace(/\*/g, '%2A')
+ // The following are not required for percent-encoding per RFC5987,
+ // so we can allow for a little better readability over the wire: |`^
+ .replace(/%(?:7C|60|5E)/g, unescape)
+}