Skip to content

Commit

Permalink
Extract and modernize the webserver's directory listing code
Browse files Browse the repository at this point in the history
The `handler` method contained this code in an inline function, which
made the `handler` method big and harder to read. Moreover, this code
relied on variables from the outer scope, which made it harder to reason
about because the inputs and outputs weren't easily visible.

This commit fixes the problems by extracting the directory listing code
into a dedicated private method, and modernizing it to use e.g. `const`/
`let` instead of `var` and using template strings.
  • Loading branch information
timvandermeij committed Feb 11, 2024
1 parent 336fcff commit 935e8c8
Showing 1 changed file with 86 additions and 80 deletions.
166 changes: 86 additions & 80 deletions test/webserver.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -146,7 +147,7 @@ class WebServer {

var fileSize;

function statFile(err, stats) {
async function statFile(err, stats) {
if (err) {
res.writeHead(500);
res.end();
Expand All @@ -162,7 +163,7 @@ class WebServer {
return;
}
if (isDir) {
serveDirectoryIndex(filePath);
await self.#serveDirectoryIndex(res, pathPart, queryPart, filePath);
return;
}

Expand Down Expand Up @@ -196,97 +197,102 @@ class WebServer {
}
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(
`<html>
<frameset cols=*,200>
<frame name=pdf>
<frame src="${encodeURI(pathPart)}?side">
</frameset>
</html>`,
"utf8"
);
return;
}

let files;
try {
files = await fsPromises.readdir(directory);
} catch {
response.end();
return;
}

function escapeHTML(untrusted) {
response.write(
`<html>
<head>
<meta charset="utf-8">
</head>
<body>
<h1>Index of ${pathPart}</h1>
`

Check failure

Code scanning / CodeQL

Reflected cross-site scripting High test

Cross-site scripting vulnerability due to a
user-provided value
.
);
if (pathPart !== "/") {
response.write('<a href="..">..</a><br>');
}

const all = queryPart === "all";
const escapeHTML = untrusted =>
// Escape untrusted input so that it can safely be used in a HTML response
// in HTML and in HTML attributes.
return untrusted
untrusted
.replaceAll("&", "&amp;")
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;")
.replaceAll('"', "&quot;")
.replaceAll("'", "&#39;");
}

function serveDirectoryIndex(dir) {
res.setHeader("Content-Type", "text/html");
res.writeHead(200);

if (queryPart === "frame") {
res.end(
"<html><frameset cols=*,200><frame name=pdf>" +
'<frame src="' +
encodeURI(pathPart) +
'?side"></frameset></html>',
"utf8"
);
return;
for (const file of files) {
let stat;
const item = pathPart + file;
let href = "";
let label = "";
let extraAttributes = "";

try {
stat = fs.statSync(path.join(directory, file));
} catch (ex) {
href = encodeURI(item);
label = `${file} (${ex})`;
extraAttributes = ' style="color:red"';
}
var all = queryPart === "all";
fs.readdir(dir, function (err, files) {
if (err) {
res.end();
return;

if (stat) {
if (stat.isDirectory()) {
href = encodeURI(item);
label = file;
} else if (path.extname(file).toLowerCase() === ".pdf") {
href = `/web/viewer.html?file=${encodeURIComponent(item)}`;
label = file;
extraAttributes = ' target="pdf"';
} else if (all) {
href = encodeURI(item);
label = file;
}
res.write(
'<html><head><meta charset="utf-8"></head><body>' +
"<h1>PDFs of " +
pathPart +
"</h1>\n"
}

if (label) {
response.write(
`<a href="${escapeHTML(href)}"${extraAttributes}>${escapeHTML(label)}</a><br>`
);
if (pathPart !== "/") {
res.write('<a href="..">..</a><br>\n');
}
files.forEach(function (file) {
var stat;
var item = pathPart + file;
var href = "";
var label = "";
var extraAttributes = "";
try {
stat = fs.statSync(path.join(dir, file));
} catch (e) {
href = encodeURI(item);
label = file + " (" + e + ")";
extraAttributes = ' style="color:red"';
}
if (stat) {
if (stat.isDirectory()) {
href = encodeURI(item);
label = file;
} else if (path.extname(file).toLowerCase() === ".pdf") {
href = "/web/viewer.html?file=" + encodeURIComponent(item);
label = file;
extraAttributes = ' target="pdf"';
} else if (all) {
href = encodeURI(item);
label = file;
}
}
if (label) {
res.write(
'<a href="' +
escapeHTML(href) +
'"' +
extraAttributes +
">" +
escapeHTML(label) +
"</a><br>\n"
);
}
});
if (files.length === 0) {
res.write("<p>no files found</p>\n");
}
if (!all && queryPart !== "side") {
res.write(
"<hr><p>(only PDF files are shown, " +
'<a href="?all">show all</a>)</p>\n'
);
}
res.end("</body></html>");
});
}
}

if (files.length === 0) {
response.write("<p>No files found</p>");
}
if (!all && queryPart !== "side") {
response.write(
'<hr><p>(only PDF files are shown, <a href="?all">show all</a>)</p>'
);
}
response.end("</body></html>");
}

#serveFile(response, filePath, fileSize) {
Expand Down

0 comments on commit 935e8c8

Please sign in to comment.