Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/webtorrent/webtorrent.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFeross Aboukhadijeh <feross@feross.org>2017-02-10 07:15:59 +0300
committerFeross Aboukhadijeh <feross@feross.org>2017-02-10 07:15:59 +0300
commitdd86f016e4bf104d690f6809f9becc29304ddd3f (patch)
tree6b84c9076951e0e8f692d98d9bcc7cefe0ebee26 /lib/server.js
parentdfea7406ae643d91f9d77c4a5e5f1374d7faf37e (diff)
Refactor http server; support content-disposition
Refactored the server into many smaller functions to make it easier to understand all the different code paths. - added a Content-Disposition header, which tells the browser the file's name, since we use urls like http://localhost:port/0 <-- no human-readable file name - Server returns valid HTML documents (with all the required tags) now. - Return 204 status for OPTIONS request - reduce access-control-max-age to chromium max of 600s - respond to OPTIONS requests that lack 'access-control-request-headers' (before they were treated as GET) - return '405 invalid verb' for all other verbs For: https://github.com/brave/browser-laptop/issues/6737
Diffstat (limited to 'lib/server.js')
-rw-r--r--lib/server.js140
1 files changed, 107 insertions, 33 deletions
diff --git a/lib/server.js b/lib/server.js
index a812eab..af46b85 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 '<li><a download="' + file.name + '" href="/' + i + '">' + file.path + '</a> ' +
- '(' + file.length + ' bytes)</li>'
- }).join('<br>')
-
- var html = '<h1>' + torrent.name + '</h1><ol>' + listHtml + '</ol>'
- 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 '<li><a download="' + file.name + '" href="/' + i + '">' + file.path + '</a> ' +
+ '(' + file.length + ' bytes)</li>'
+ }).join('<br>')
+
+ var html = getPageHTML(
+ torrent.name + ' - WebTorrent',
+ '<h1>' + torrent.name + '</h1><ol>' + listHtml + '</ol>'
+ )
+ res.end(html)
+ }
+
+ function serve404Page () {
+ res.statusCode = 404
+ res.setHeader('Content-Type', 'text/html')
+
+ var html = getPageHTML('404 - Not Found', '<h1>404 - Not Found</h1>')
+ 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', '<h1>405 - Method Not Allowed</h1>')
+ res.end(html)
+ }
}
return server
}
+
+function getPageHTML (title, pageHtml) {
+ return '<!DOCTYPE html><html lang="en"><head>' +
+ '<meta charset="utf-8">' +
+ '<title>' + title + '</title>' +
+ '</head><body>' + pageHtml + '</body></html>'
+}
+
+// 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)
+}