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 @@
+