Skip to content

Commit

Permalink
fix: use same directory listing format as nginx
Browse files Browse the repository at this point in the history
  • Loading branch information
flakey5 committed Nov 15, 2023
1 parent b854489 commit 266c781
Show file tree
Hide file tree
Showing 7 changed files with 81 additions and 62 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
"type": "module",
"scripts": {
"start": "wrangler dev --remote",
"format": "prettier --check --write \"**/*.{ts,js,json,md,hbs}\"",
"prettier": "prettier --check \"**/*.{ts,js,json,md,hbs}\"",
"format": "prettier --check --write \"**/*.{ts,js,json,md}\"",
"prettier": "prettier --check \"**/*.{ts,js,json,md}\"",
"lint": "eslint ./src",
"test:unit": "node --test --test-reporter=@reporters/github --test-reporter-destination=stdout --test-reporter=spec --test-reporter-destination=stdout --loader=tsx ./tests/unit/index.test.ts",
"test:e2e": "wrangler deploy --dry-run --outdir=dist && node --test --test-reporter=@reporters/github --test-reporter-destination=stdout --test-reporter=spec --test-reporter-destination=stdout --loader=tsx ./tests/e2e/index.test.ts",
Expand Down
4 changes: 1 addition & 3 deletions scripts/compile-handlebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,7 @@ templatesToParse.then(files => {

// The Handlebars.precompile returns a JavaScript object
// and then we make a default export of the object
const javascriptTemplate = `export default ${removeStringIndents(
compiledTemplate
)}`;
const javascriptTemplate = `export default ${compiledTemplate}`;

const outputFilename = filePath.replace('.hbs', '.out.js');

Expand Down
79 changes: 55 additions & 24 deletions src/handlers/strategies/directoryListing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,20 @@ import { S3_MAX_KEYS, S3_RETRY_LIMIT } from '../../constants/limits';
// Applies the Template into a Handlebars Template Function
const handleBarsTemplate = Handlebars.template(htmlTemplate);

const months = [
'Jan',
'Feb',
'Mar',
'Apr',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec',
];

/**
* @TODO: Simplify the iteration logic or make it more readable
*
Expand All @@ -35,34 +49,34 @@ export function renderDirectoryListing(
env: Env
): Response {
// Holds the contents of the listing (directories and files)
const tableElements = [];

// There shouldn't really be a case where we're listing the root
// directory (/) when this is deployed, so always add the option
// to go up a directory
tableElements.push({
href: '../',
name: '../',
lastModified: '-',
size: '-',
});
const tableElements: object[] = [];

const urlPathname = `${url.pathname}${url.pathname.endsWith('/') ? '' : '/'}`;

// Renders all the subdirectories within the Directory
delimitedPrefixes.forEach(name => {
const extra = encodeURIComponent(name.substring(0, name.length - 1));

let displayName: string;
let displayNamePaddingRight: string = ''; // hate this
if (name.length > 50) {
displayName = name.substring(0, 49) + '>';
} else {
displayName = name;
displayNamePaddingRight = ' '.repeat(50 - name.length);
}

tableElements.push({
href: `${urlPathname}${extra}/`,
name,
href: `${extra}/`,
displayNamePaddingRight,
name: displayName,
lastModified: '-',
size: '-',
size: ' -',
});
});

// Last time any of the files within the directory got modified
let lastModified: Date | undefined = undefined;
let directoryLastModified: Date | undefined = undefined;

// Renders all the Files within the Directory
objects.forEach(object => {
Expand All @@ -71,20 +85,35 @@ export function renderDirectoryListing(
// Find the most recent date a file in this
// directory was modified, we'll use it
// in the `Last-Modified` header
if (lastModified === undefined || object.LastModified! > lastModified) {
lastModified = object.LastModified!;
if (
directoryLastModified === undefined ||
object.LastModified! > directoryLastModified
) {
directoryLastModified = object.LastModified!;
}

let dateStr = object.LastModified!.toISOString();
const lastModified = object.LastModified!;
const dateStr = `${lastModified.getDay()}-${months.at(
lastModified.getMonth()
)}-${lastModified.getFullYear()} ${lastModified.getHours()}:${lastModified.getMinutes()}`;

let displayName: string = '';
let displayNamePaddingRight: string = ''; // hate this
if (name!.length > 50) {
displayName = name!.substring(0, 49) + '>';
} else {
displayName = name!;
displayNamePaddingRight = ' '.repeat(50 - name!.length);
}

dateStr = dateStr.split('.')[0].replace('T', ' ');
dateStr = dateStr.slice(0, dateStr.lastIndexOf(':')) + 'Z';
const bytes = niceBytes(object.Size!);

tableElements.push({
href: `${urlPathname}${encodeURIComponent(name ?? '')}`,
name,
name: displayName,
displayNamePaddingRight,
lastModified: dateStr,
size: niceBytes(object.Size!),
size: ' '.repeat(20 - bytes.length) + bytes,
});
});

Expand All @@ -95,11 +124,13 @@ export function renderDirectoryListing(
});

// Gets an UTC-string on the ISO-8901 format of last modified date
const lastModifiedUTC = (lastModified ?? new Date()).toUTCString();
const directoryLastModifiedUtc = (
directoryLastModified ?? new Date()
).toUTCString();

return new Response(request.method === 'GET' ? renderedListing : null, {
headers: {
'last-modified': lastModifiedUTC,
'last-modified': directoryLastModifiedUtc,
'content-type': 'text/html',
'cache-control': env.DIRECTORY_CACHE_CONTROL || 'no-store',
},
Expand Down
38 changes: 9 additions & 29 deletions src/templates/directoryListing.hbs
Original file line number Diff line number Diff line change
@@ -1,31 +1,11 @@
<html>
<head>
<title>Index of {{pathname}}</title>
<meta name='viewport' content='width=device-width, initial-scale=1.0' />
<meta charset='utf-8' />
<style type='text/css'>
td { padding-right: 16px; text-align: right; font-family: monospace }
td:nth-of-type(1) { text-align: left; overflow-wrap: anywhere }
td:nth-of-type(3) { white-space: nowrap } th { text-align: left; } @media
(prefers-color-scheme: dark) { body { color: white; background-color:
#1c1b22; } a { color: #3391ff; } a:visited { color: #C63B65; } }
</style>
</head>
<body>
<h1>Index of {{pathname}}</h1>
<table>
<tr>
<th>Filename</th>
<th>Modified</th>
<th>Size</th>
</tr>
{{#each entries}}
<tr>
<td><a href='{{href}}'>{{name}}</a></td>
<td>{{lastModified}}</td>
<td>{{size}}</td>
</tr>
{{/each}}
</table>
</body>
<head><title>Index of {{pathname}}</title></head>
<body>
<h1>Index of {{pathname}}</h1><hr /><pre><a href='../'>../</a>
{{#each entries}}
<a href='{{href}}'>{{name}}</a>{{displayNamePaddingRight}}
{{lastModified}}
{{size}}
{{/each}}
</pre><hr /></body>
</html>
2 changes: 1 addition & 1 deletion src/templates/directoryListing.out.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions tests/e2e/directory.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { after, before, describe, it } from 'node:test';
import assert from 'node:assert';
import { readFileSync } from 'node:fs';
import { readFileSync, writeFileSync } from 'node:fs';
import { readFile } from 'node:fs/promises';
import http from 'http';
import { Miniflare } from 'miniflare';
Expand Down Expand Up @@ -93,7 +93,10 @@ describe('Directory Tests (Restricted Directory Listing)', () => {
// it'll pass for the other listings and therefore
// don't need to test it over and over again
const body = await res.text();
assert.strictEqual(body, expectedHtml.replaceAll('\n', ''));
assert.strictEqual(
body.replaceAll('\r', ''),
expectedHtml.replaceAll('\r', '')
);
});

it('allows `/dist/`', async () => {
Expand Down
9 changes: 8 additions & 1 deletion tests/e2e/test-data/expected-html/dist.txt
Original file line number Diff line number Diff line change
@@ -1 +1,8 @@
<!DOCTYPE html><html><head><title>Index of /dist/</title><meta name='viewport' content='width=device-width, initial-scale=1.0' /><meta charset='utf-8' /><style type='text/css'>td { padding-right: 16px; text-align: right; font-family: monospace }td:nth-of-type(1) { text-align: left; overflow-wrap: anywhere }td:nth-of-type(3) { white-space: nowrap } th { text-align: left; } @media(prefers-color-scheme: dark) { body { color: white; background-color:#1c1b22; } a { color: #3391ff; } a:visited { color: #C63B65; } }</style></head><body><h1>Index of /dist/</h1><table><tr><th>Filename</th><th>Modified</th><th>Size</th></tr><tr><td><a href='../'>../</a></td><td>-</td><td>-</td></tr><tr><td><a href='/dist/latest/'>latest/</a></td><td>-</td><td>-</td></tr><tr><td><a href='/dist/index.json'>index.json</a></td><td>2023-09-12 05:43Z</td><td>18 B</td></tr></table></body></html>
<!DOCTYPE html><html>
<head><title>Index of /dist/</title></head>
<body>
<h1>Index of /dist/</h1><hr><pre><a href='../'>../</a>
<a href='latest/'>latest/</a> - -
<a href='/dist/index.json'>index.json</a> 1-Oct-2023 22:43 18 B
</pre><hr /></body>
</html>

0 comments on commit 266c781

Please sign in to comment.