Skip to content

Commit

Permalink
fix(http/file_server): dealing with dir listing view that contain sys…
Browse files Browse the repository at this point in the history
…tem files (denoland#3371)
  • Loading branch information
ayame113 authored and mxdvl committed May 16, 2023
1 parent f04cc93 commit d4f79d8
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 15 deletions.
43 changes: 29 additions & 14 deletions http/file_server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ async function serveDirIndex(
options: {
showDotfiles: boolean;
target: string;
quiet: boolean | undefined;
},
): Promise<Response> {
const { showDotfiles } = options;
Expand All @@ -294,13 +295,27 @@ async function serveDirIndex(
const filePath = posix.join(dirPath, entry.name);
const fileUrl = encodeURIComponent(posix.join(dirUrl, entry.name))
.replaceAll("%2F", "/");
const entryInfo = Deno.stat(filePath).then((fileInfo): EntryInfo => ({
mode: modeToString(entry.isDirectory, fileInfo.mode),
size: entry.isFile ? formatBytes(fileInfo.size ?? 0) : "",
name: `${entry.name}${entry.isDirectory ? "/" : ""}`,
url: `${fileUrl}${entry.isDirectory ? "/" : ""}`,
}));
listEntryPromise.push(entryInfo);

listEntryPromise.push((async () => {
try {
const fileInfo = await Deno.stat(filePath);
return {
mode: modeToString(entry.isDirectory, fileInfo.mode),
size: entry.isFile ? formatBytes(fileInfo.size ?? 0) : "",
name: `${entry.name}${entry.isDirectory ? "/" : ""}`,
url: `${fileUrl}${entry.isDirectory ? "/" : ""}`,
};
} catch (error) {
// Note: Deno.stat for windows system files may be rejected with os error 32.
if (!options.quiet) logError(error);
return {
mode: "(unknown mode)",
size: "",
name: `${entry.name}${entry.isDirectory ? "/" : ""}`,
url: `${fileUrl}${entry.isDirectory ? "/" : ""}`,
};
}
})());
}

const listEntry = await Promise.all(listEntryPromise);
Expand Down Expand Up @@ -554,11 +569,7 @@ export async function serveDir(req: Request, opts: ServeDirOptions = {}) {
try {
response = await createServeDirResponse(req, opts);
} catch (error) {
if (!opts.quiet) {
console.error(
red(error instanceof Error ? error.message : "[non-error thrown]"),
);
}
if (!opts.quiet) logError(error);
response = serveFallback(error);
}

Expand Down Expand Up @@ -595,7 +606,7 @@ async function createServeDirResponse(
const urlRoot = opts.urlRoot;
const showIndex = opts.showIndex ?? true;
const showDotfiles = opts.showDotfiles || false;
const { etagAlgorithm, showDirListing } = opts;
const { etagAlgorithm, showDirListing, quiet } = opts;

const url = new URL(req.url);
const decodedUrl = decodeURIComponent(url.pathname);
Expand Down Expand Up @@ -671,12 +682,16 @@ async function createServeDirResponse(
}

if (showDirListing) { // serve directory list
return serveDirIndex(fsPath, { showDotfiles, target });
return serveDirIndex(fsPath, { showDotfiles, target, quiet });
}

return createCommonResponse(Status.NotFound);
}

function logError(error: unknown) {
console.error(red(error instanceof Error ? error.message : `${error}`));
}

function main() {
const serverArgs = parse(Deno.args, {
string: ["port", "host", "cert", "key", "header"],
Expand Down
27 changes: 26 additions & 1 deletion http/file_server_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
assertEquals,
assertStringIncludes,
} from "../testing/asserts.ts";
import { stub } from "../testing/mock.ts";
import { iterateReader } from "../streams/iterate_reader.ts";
import { writeAll } from "../streams/write_all.ts";
import { TextLineStream } from "../streams/text_line_stream.ts";
Expand Down Expand Up @@ -57,7 +58,7 @@ async function startFileServer({
],
cwd: moduleDir,
stdout: "piped",
stderr: "null",
stderr: "inherit",
});
child = fileServer.spawn();
// Once fileServer is ready it will write to its stdout.
Expand Down Expand Up @@ -283,6 +284,30 @@ Deno.test("serveDirIndex with filename including hash symbol", async function ()
}
});

Deno.test("serveDirIndex returns a response even if fileinfo is inaccessible", async function () {
// Note: Deno.stat for windows system files may be rejected with os error 32.
// Mock Deno.stat to test that the dirlisting page can be generated
// even if the fileInfo for a particular file cannot be obtained.

// Assuming that fileInfo of `test file.txt` cannot be accessible
const denoStatStub = stub(Deno, "stat", (path): Promise<Deno.FileInfo> => {
if (path.toString().includes("test file.txt")) {
return Promise.reject(new Error("__stubed_error__"));
}
return denoStatStub.original.call(Deno, path);
});

try {
const url = "http://localhost:4507/http/testdata/";
const res = await serveDir(new Request(url), { showDirListing: true });

assertEquals(res.status, 200);
assertStringIncludes(await res.text(), "/testdata/test%20file.txt");
} finally {
denoStatStub.restore();
}
});

Deno.test("serveFallback", async function () {
await startFileServer();
try {
Expand Down

0 comments on commit d4f79d8

Please sign in to comment.