diff --git a/test/webserver.mjs b/test/webserver.mjs index b5b1e7c30..f920165cf 100644 --- a/test/webserver.mjs +++ b/test/webserver.mjs @@ -16,6 +16,7 @@ /* eslint-disable no-var */ import fs from "fs"; +import fsPromises from "fs/promises"; import http from "http"; import path from "path"; @@ -38,33 +39,36 @@ var mimeTypes = { var defaultMimeType = "application/octet-stream"; -function WebServer() { - this.root = "."; - this.host = "localhost"; - this.port = 0; - this.server = null; - this.verbose = false; - this.cacheExpirationTime = 0; - this.disableRangeRequests = false; - this.hooks = { - GET: [crossOriginHandler], - POST: [], - }; -} -WebServer.prototype = { +class WebServer { + constructor() { + this.root = "."; + this.host = "localhost"; + this.port = 0; + this.server = null; + this.verbose = false; + this.cacheExpirationTime = 0; + this.disableRangeRequests = false; + this.hooks = { + GET: [crossOriginHandler], + POST: [], + }; + } + start(callback) { - this._ensureNonZeroPort(); - this.server = http.createServer(this._handler.bind(this)); + this.#ensureNonZeroPort(); + this.server = http.createServer(this.#handler.bind(this)); this.server.listen(this.port, this.host, callback); console.log( "Server running at http://" + this.host + ":" + this.port + "/" ); - }, + } + stop(callback) { this.server.close(callback); this.server = null; - }, - _ensureNonZeroPort() { + } + + #ensureNonZeroPort() { if (!this.port) { // If port is 0, a random port will be chosen instead. Do not set a host // name to make sure that the port is synchronously set by .listen(). @@ -76,8 +80,10 @@ WebServer.prototype = { this.port = address ? address.port : 8000; server.close(); } - }, - _handler(req, res) { + } + + #handler(req, res) { + var self = this; var url = req.url.replaceAll("//", "/"); var urlParts = /([^?]*)((?:\?(.*))?)/.exec(url); try { @@ -122,7 +128,6 @@ WebServer.prototype = { } var disableRangeRequests = this.disableRangeRequests; - var cacheExpirationTime = this.cacheExpirationTime; var filePath; fs.realpath(path.join(this.root, pathPart), checkFile); @@ -158,7 +163,7 @@ WebServer.prototype = { return; } if (isDir) { - serveDirectoryIndex(filePath); + self.#serveDirectoryIndex(res, pathPart, queryPart, filePath); return; } @@ -178,8 +183,10 @@ WebServer.prototype = { if (verbose) { console.log(url + ": range " + start + " - " + end); } - serveRequestedFileRange( + self.#serveFileRange( + res, filePath, + fileSize, start, isNaN(end) ? fileSize : end + 1 ); @@ -188,154 +195,154 @@ WebServer.prototype = { if (verbose) { console.log(url); } - serveRequestedFile(filePath); + self.#serveFile(res, filePath, fileSize); + } + } + + async #serveDirectoryIndex(response, pathPart, queryPart, directory) { + response.setHeader("Content-Type", "text/html"); + response.writeHead(200); + + if (queryPart === "frame") { + response.end( + ` +
+ `, + "utf8" + ); + return; } - function escapeHTML(untrusted) { + let files; + try { + files = await fsPromises.readdir(directory); + } catch { + response.end(); + return; + } + + response.write( + ` + + + + +no files found
\n"); - } - if (!all && queryPart !== "side") { - res.write( - "(only PDF files are shown, " + - 'show all)
\n' - ); - } - res.end(""); - }); } - function serveRequestedFile(reqFilePath) { - var stream = fs.createReadStream(reqFilePath, { flags: "rs" }); - - stream.on("error", function (error) { - res.writeHead(500); - res.end(); - }); - - var ext = path.extname(reqFilePath).toLowerCase(); - var contentType = mimeTypes[ext] || defaultMimeType; - - if (!disableRangeRequests) { - res.setHeader("Accept-Ranges", "bytes"); - } - res.setHeader("Content-Type", contentType); - res.setHeader("Content-Length", fileSize); - if (cacheExpirationTime > 0) { - var expireTime = new Date(); - expireTime.setSeconds(expireTime.getSeconds() + cacheExpirationTime); - res.setHeader("Expires", expireTime.toUTCString()); - } - res.writeHead(200); - - stream.pipe(res); + if (files.length === 0) { + response.write("No files found
"); } - - function serveRequestedFileRange(reqFilePath, start, end) { - var stream = fs.createReadStream(reqFilePath, { - flags: "rs", - start, - end: end - 1, - }); - - stream.on("error", function (error) { - res.writeHead(500); - res.end(); - }); - - var ext = path.extname(reqFilePath).toLowerCase(); - var contentType = mimeTypes[ext] || defaultMimeType; - - res.setHeader("Accept-Ranges", "bytes"); - res.setHeader("Content-Type", contentType); - res.setHeader("Content-Length", end - start); - res.setHeader( - "Content-Range", - "bytes " + start + "-" + (end - 1) + "/" + fileSize + if (!all && queryPart !== "side") { + response.write( + '(only PDF files are shown, show all)
' ); - res.writeHead(206); - - stream.pipe(res); } - }, -}; + response.end(""); + } + + #serveFile(response, filePath, fileSize) { + const stream = fs.createReadStream(filePath, { flags: "rs" }); + stream.on("error", error => { + response.writeHead(500); + response.end(); + }); + + const extension = path.extname(filePath).toLowerCase(); + const contentType = mimeTypes[extension] || defaultMimeType; + + if (!this.disableRangeRequests) { + response.setHeader("Accept-Ranges", "bytes"); + } + response.setHeader("Content-Type", contentType); + response.setHeader("Content-Length", fileSize); + if (this.cacheExpirationTime > 0) { + const expireTime = new Date(); + expireTime.setSeconds(expireTime.getSeconds() + this.cacheExpirationTime); + response.setHeader("Expires", expireTime.toUTCString()); + } + response.writeHead(200); + stream.pipe(response); + } + + #serveFileRange(response, filePath, fileSize, start, end) { + const stream = fs.createReadStream(filePath, { + flags: "rs", + start, + end: end - 1, + }); + stream.on("error", error => { + response.writeHead(500); + response.end(); + }); + + const extension = path.extname(filePath).toLowerCase(); + const contentType = mimeTypes[extension] || defaultMimeType; + + response.setHeader("Accept-Ranges", "bytes"); + response.setHeader("Content-Type", contentType); + response.setHeader("Content-Length", end - start); + response.setHeader( + "Content-Range", + `bytes ${start}-${end - 1}/${fileSize}` + ); + response.writeHead(206); + stream.pipe(response); + } +} // This supports the "Cross-origin" test in test/unit/api_spec.js // It is here instead of test.js so that when the test will still complete as