diff --git a/lib/core/index.js b/lib/core/index.js index e386c541..5a1764b0 100644 --- a/lib/core/index.js +++ b/lib/core/index.js @@ -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); } diff --git a/lib/core/show-dir/index.js b/lib/core/show-dir/index.js index 001e41a2..dcfbde73 100644 --- a/lib/core/show-dir/index.js +++ b/lib/core/show-dir/index.js @@ -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, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); +} + module.exports = (opts) => { // opts are parsed by opts.js, defaults already applied const cache = opts.cache; @@ -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); @@ -127,11 +138,11 @@ module.exports = (opts) => { ' ', ' ', ' ', - ` Index of ${he.encode(pathname)}`, + ` Index of ${escapeHtml(pathname)}`, ` `, ' ', ' ', - `

Index of ${he.encode(pathname)}

`, + `

Index of ${escapeHtml(pathname)}

`, ].join('\n')}\n`; html += ''; @@ -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 @@ -153,8 +164,8 @@ 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 = `*`; displayNameHTML = `${uncompressedName}` + @@ -162,7 +173,7 @@ module.exports = (opts) => { fileSize += '*'; } else { // Regular file or directory - displayNameHTML = `${he.encode(file[0]) + ((isDir) ? '/' : '')}`; + displayNameHTML = `${escapeHtml(file[0]) + ((isDir) ? '/' : '')}`; } const ext = file[0].split('.').pop(); @@ -191,12 +202,12 @@ module.exports = (opts) => { process.version }/ http-server ` + `server running @ ${ - he.encode(req.headers.host || '')}\n` + + escapeHtml(req.headers.host || '')}\n` + '' ; if (!failed) { - res.writeHead(200, { 'Content-Type': 'text/html' }); + res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); res.end(html); } }