Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion lib/core/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ function decodePathname(pathname) {

const nonUrlSafeCharsRgx = /[\x00-\x1F\x20\x7F-\uFFFF]+/g;
function ensureUriEncoded(text) {
return text
return String(text).replace(nonUrlSafeCharsRgx, encodeURIComponent);
}

Expand Down
29 changes: 20 additions & 9 deletions lib/core/show-dir/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,17 @@ const status = require('../status-handlers');
const supportedIcons = styles.icons;
const css = styles.css;

// Only escape HTML-unsafe characters, preserve CJK/unicode as-is for
// clients that do not decode HTML entities (e.g. Switch DBI).
function escapeHtml(str) {
return String(str)
.replace(/&/g, '&')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}

module.exports = (opts) => {
// opts are parsed by opts.js, defaults already applied
const cache = opts.cache;
Expand Down Expand Up @@ -69,7 +80,7 @@ module.exports = (opts) => {
files = files.filter(filename => filename.slice(0, 1) !== '.');
}

res.setHeader('content-type', 'text/html');
res.setHeader('content-type', 'text/html; charset=utf-8');
res.setHeader('etag', etag(stat, weakEtags));
res.setHeader('last-modified', (new Date(stat.mtime)).toUTCString());
res.setHeader('cache-control', cache);
Expand Down Expand Up @@ -127,11 +138,11 @@ module.exports = (opts) => {
' <head>',
' <meta charset="utf-8">',
' <meta name="viewport" content="width=device-width">',
` <title>Index of ${he.encode(pathname)}</title>`,
` <title>Index of ${escapeHtml(pathname)}</title>`,
` <style type="text/css">${css}</style>`,
' </head>',
' <body>',
`<h1>Index of ${he.encode(pathname)}</h1>`,
`<h1>Index of ${escapeHtml(pathname)}</h1>`,
].join('\n')}\n`;

html += '<table>';
Expand All @@ -144,7 +155,7 @@ module.exports = (opts) => {

// append trailing slash and query for dir entry
if (isDir) {
href += `/${he.encode((parsed.search) ? parsed.search : '')}`;
href += `/${escapeHtml((parsed.search) ? parsed.search : '')}`;
}

// Handle compressed files with uncompressed names
Expand All @@ -153,16 +164,16 @@ module.exports = (opts) => {

if (file[2] && file[2].uncompressedName) {
// This is a compressed file, show both names with separate links
const uncompressedName = he.encode(file[2].uncompressedName);
const compressedName = he.encode(file[0]);
const uncompressedName = escapeHtml(file[2].uncompressedName);
const compressedName = escapeHtml(file[0]);
const uncompressedHref = `./${encodeURIComponent(file[2].uncompressedName)}`;
const asterisk = `<span title="served from compressed file">*</span>`;
displayNameHTML = `<a href="${uncompressedHref}">${uncompressedName}</a>` +
`${asterisk} (<a href="${href}">${compressedName}</a>)`;
fileSize += '*';
} else {
// Regular file or directory
displayNameHTML = `<a href="${href}">${he.encode(file[0]) + ((isDir) ? '/' : '')}</a>`;
displayNameHTML = `<a href="${href}">${escapeHtml(file[0]) + ((isDir) ? '/' : '')}</a>`;
}

const ext = file[0].split('.').pop();
Expand Down Expand Up @@ -191,12 +202,12 @@ module.exports = (opts) => {
process.version
}/ <a href="https://github.com/http-party/http-server">http-server</a> ` +
`server running @ ${
he.encode(req.headers.host || '')}</address>\n` +
escapeHtml(req.headers.host || '')}</address>\n` +
'</body></html>'
;

if (!failed) {
res.writeHead(200, { 'Content-Type': 'text/html' });
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
res.end(html);
}
}
Expand Down