diff --git a/core/core.js b/core/core.js index 08d6ee54..cec9a20d 100644 --- a/core/core.js +++ b/core/core.js @@ -8,11 +8,19 @@ let gProcesses = {}; let gStatsTimer = false; const k_mime_types = { - 'json': 'text/json', - 'js': 'text/javascript', - 'html': 'text/html', 'css': 'text/css', + 'html': 'text/html', + 'js': 'text/javascript', + 'json': 'text/json', 'map': 'application/json', + 'svg': 'image/svg+xml', +}; + +const k_mime_type_is_trusted = { + 'application/json': true, + 'text/css': true, + 'text/javascript': true, + 'text/json': true, }; const k_magic_bytes = [ @@ -30,6 +38,15 @@ const k_magic_bytes = [ {bytes: [null, null, null, null, 0x66, 0x74, 0x79, 0x70, 0x6d, 0x70, 0x34, 0x32], type: 'video/mp4'}, ]; +let k_static_files = [ + {uri: '/', path: 'index.html', type: 'text/html; charset=UTF-8'}, + {uri: '/style.css', type: 'text/css; charset=UTF-8'}, + {uri: '/favicon.png', type: 'image/png'}, + {uri: '/client.js', type: 'text/javascript; charset=UTF-8'}, + {uri: '/tfrpc.js', type: 'text/javascript; charset=UTF-8', headers: {'Access-Control-Allow-Origin': 'null'}}, + {uri: '/robots.txt', type: 'text/plain; charset=UTF-8'}, +]; + const k_global_settings = { index: { type: 'string', @@ -459,15 +476,6 @@ function setGlobalSettings(settings) { } } -let kStaticFiles = [ - {uri: '/', path: 'index.html', type: 'text/html; charset=UTF-8'}, - {uri: '/style.css', type: 'text/css; charset=UTF-8'}, - {uri: '/favicon.png', type: 'image/png'}, - {uri: '/client.js', type: 'text/javascript; charset=UTF-8'}, - {uri: '/tfrpc.js', type: 'text/javascript; charset=UTF-8', headers: {'Access-Control-Allow-Origin': 'null'}}, - {uri: '/robots.txt', type: 'text/plain; charset=UTF-8'}, -]; - function startsWithBytes(data, bytes) { if (data.byteLength >= bytes.length) { let dataBytes = new Uint8Array(data.slice(0, bytes.length)); @@ -481,10 +489,10 @@ function startsWithBytes(data, bytes) { } async function staticFileHandler(request, response, blobId, uri) { - for (let i in kStaticFiles) { - if (uri === kStaticFiles[i].uri) { - let path = kStaticFiles[i].path || uri.substring(1); - let type = kStaticFiles[i].type || guessType(path); + for (let i in k_static_files) { + if (uri === k_static_files[i].uri) { + let path = k_static_files[i].path || uri.substring(1); + let type = k_static_files[i].type || guessTypeFromName(path); let stat = await File.stat('core/' + path); let id = `${stat.mtime}_${stat.size}`; @@ -500,7 +508,7 @@ async function staticFileHandler(request, response, blobId, uri) { 'Content-Length': data.byteLength, 'etag': '"' + id + '"', }, - kStaticFiles[i].headers || {})); + k_static_files[i].headers || {})); response.end(data); } return; @@ -552,17 +560,29 @@ async function wellKnownHandler(request, response, path) { } } +function guessTypeFromName(path) { + let extension = path.split('.').pop(); + return k_mime_types[extension]; +} + +function guessTypeFromMagicBytes(data) { + for (let magic of k_magic_bytes) { + if (startsWithBytes(data, magic.bytes)) { + return magic.content_type; + } + } +} + +function guessTypeUntrusted(path, data) { + let type = guessTypeFromMagicBytes(data) || guessTypeFromName(path); + if (k_mime_type_is_trusted[type]) { + return type; + } +} + function sendData(response, data, type, headers) { if (data) { - for (let magic of k_magic_bytes) { - if (startsWithBytes(data, magic.bytes)) { - response.writeHead(200, Object.assign({"Content-Type": magic.content_type, "Content-Length": data.byteLength}, headers || {})); - response.end(data); - return; - } - } - - response.writeHead(200, Object.assign({"Content-Type": type || "application/binary", "Content-Length": data.byteLength}, headers || {})); + response.writeHead(200, Object.assign({"Content-Type": type || guessTypeFromMagicBytes(data) || "application/binary", "Content-Length": data.byteLength}, headers || {})); response.end(data); } else { response.writeHead(404, Object.assign({"Content-Type": "text/plain; charset=utf-8", "Content-Length": "File not found".length}, headers || {})); @@ -580,35 +600,24 @@ async function getBlobOrContent(id) { } } -function guessType(path) { - const k_extension_to_type = { - 'css': 'text/css', - 'html': 'text/html', - 'js': 'text/javascript', - 'svg': 'image/svg+xml', - }; - let extension = path.split('.').pop(); - return k_extension_to_type[extension]; -} - async function blobHandler(request, response, blobId, uri) { - for (let i in kStaticFiles) { - if (uri === kStaticFiles[i].uri && kStaticFiles[i].path) { - let stat = await File.stat('core/' + kStaticFiles[i].path); + for (let i in k_static_files) { + if (uri === k_static_files[i].uri && k_static_files[i].path) { + let stat = await File.stat('core/' + k_static_files[i].path); let id = `${stat.mtime}_${stat.size}`; if (request.headers['if-none-match'] === '"' + id + '"') { response.writeHead(304, {}); response.end(); } else { - let data = await File.readFile('core/' + kStaticFiles[i].path); + let data = await File.readFile('core/' + k_static_files[i].path); response.writeHead(200, Object.assign( { - 'Content-Type': kStaticFiles[i].type, + 'Content-Type': k_static_files[i].type, 'Content-Length': data.byteLength, 'etag': '"' + id + '"', }, - kStaticFiles[i].headers || {})); + k_static_files[i].headers || {})); response.end(data); } return; @@ -751,7 +760,7 @@ async function blobHandler(request, response, blobId, uri) { let appObject = JSON.parse(data); data = appObject.files[uri.substring(1)]; data = await getBlobOrContent(data); - type = guessType(uri); + type = guessTypeUntrusted(uri, data); headers = { 'ETag': '"' + id + '"', 'Access-Control-Allow-Origin': '*', @@ -769,6 +778,7 @@ async function blobHandler(request, response, blobId, uri) { headers = { 'Access-Control-Allow-Origin': '*', }; + type = guessTypeUntrusted(uri, data); sendData(response, data, type, headers); } } diff --git a/core/index.html b/core/index.html index 9b9d1c77..ecc6763a 100644 --- a/core/index.html +++ b/core/index.html @@ -38,6 +38,7 @@ +