forked from cory/tildefriends
Don't let browsers render untrusted HTML or SVG outside of the iframe. Do let them fetch JS and such.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4297 ed5197a5-7fde-0310-b194-c3ffbd925b24
This commit is contained in:
parent
cc92d0e316
commit
c807e21c6b
92
core/core.js
92
core/core.js
@ -8,11 +8,19 @@ let gProcesses = {};
|
|||||||
let gStatsTimer = false;
|
let gStatsTimer = false;
|
||||||
|
|
||||||
const k_mime_types = {
|
const k_mime_types = {
|
||||||
'json': 'text/json',
|
|
||||||
'js': 'text/javascript',
|
|
||||||
'html': 'text/html',
|
|
||||||
'css': 'text/css',
|
'css': 'text/css',
|
||||||
|
'html': 'text/html',
|
||||||
|
'js': 'text/javascript',
|
||||||
|
'json': 'text/json',
|
||||||
'map': 'application/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 = [
|
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'},
|
{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 = {
|
const k_global_settings = {
|
||||||
index: {
|
index: {
|
||||||
type: 'string',
|
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) {
|
function startsWithBytes(data, bytes) {
|
||||||
if (data.byteLength >= bytes.length) {
|
if (data.byteLength >= bytes.length) {
|
||||||
let dataBytes = new Uint8Array(data.slice(0, 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) {
|
async function staticFileHandler(request, response, blobId, uri) {
|
||||||
for (let i in kStaticFiles) {
|
for (let i in k_static_files) {
|
||||||
if (uri === kStaticFiles[i].uri) {
|
if (uri === k_static_files[i].uri) {
|
||||||
let path = kStaticFiles[i].path || uri.substring(1);
|
let path = k_static_files[i].path || uri.substring(1);
|
||||||
let type = kStaticFiles[i].type || guessType(path);
|
let type = k_static_files[i].type || guessTypeFromName(path);
|
||||||
|
|
||||||
let stat = await File.stat('core/' + path);
|
let stat = await File.stat('core/' + path);
|
||||||
let id = `${stat.mtime}_${stat.size}`;
|
let id = `${stat.mtime}_${stat.size}`;
|
||||||
@ -500,7 +508,7 @@ async function staticFileHandler(request, response, blobId, uri) {
|
|||||||
'Content-Length': data.byteLength,
|
'Content-Length': data.byteLength,
|
||||||
'etag': '"' + id + '"',
|
'etag': '"' + id + '"',
|
||||||
},
|
},
|
||||||
kStaticFiles[i].headers || {}));
|
k_static_files[i].headers || {}));
|
||||||
response.end(data);
|
response.end(data);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@ -552,17 +560,29 @@ async function wellKnownHandler(request, response, path) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendData(response, data, type, headers) {
|
function guessTypeFromName(path) {
|
||||||
if (data) {
|
let extension = path.split('.').pop();
|
||||||
|
return k_mime_types[extension];
|
||||||
|
}
|
||||||
|
|
||||||
|
function guessTypeFromMagicBytes(data) {
|
||||||
for (let magic of k_magic_bytes) {
|
for (let magic of k_magic_bytes) {
|
||||||
if (startsWithBytes(data, magic.bytes)) {
|
if (startsWithBytes(data, magic.bytes)) {
|
||||||
response.writeHead(200, Object.assign({"Content-Type": magic.content_type, "Content-Length": data.byteLength}, headers || {}));
|
return magic.content_type;
|
||||||
response.end(data);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
response.writeHead(200, Object.assign({"Content-Type": type || "application/binary", "Content-Length": data.byteLength}, headers || {}));
|
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) {
|
||||||
|
response.writeHead(200, Object.assign({"Content-Type": type || guessTypeFromMagicBytes(data) || "application/binary", "Content-Length": data.byteLength}, headers || {}));
|
||||||
response.end(data);
|
response.end(data);
|
||||||
} else {
|
} else {
|
||||||
response.writeHead(404, Object.assign({"Content-Type": "text/plain; charset=utf-8", "Content-Length": "File not found".length}, headers || {}));
|
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) {
|
async function blobHandler(request, response, blobId, uri) {
|
||||||
for (let i in kStaticFiles) {
|
for (let i in k_static_files) {
|
||||||
if (uri === kStaticFiles[i].uri && kStaticFiles[i].path) {
|
if (uri === k_static_files[i].uri && k_static_files[i].path) {
|
||||||
let stat = await File.stat('core/' + kStaticFiles[i].path);
|
let stat = await File.stat('core/' + k_static_files[i].path);
|
||||||
let id = `${stat.mtime}_${stat.size}`;
|
let id = `${stat.mtime}_${stat.size}`;
|
||||||
|
|
||||||
if (request.headers['if-none-match'] === '"' + id + '"') {
|
if (request.headers['if-none-match'] === '"' + id + '"') {
|
||||||
response.writeHead(304, {});
|
response.writeHead(304, {});
|
||||||
response.end();
|
response.end();
|
||||||
} else {
|
} 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(
|
response.writeHead(200, Object.assign(
|
||||||
{
|
{
|
||||||
'Content-Type': kStaticFiles[i].type,
|
'Content-Type': k_static_files[i].type,
|
||||||
'Content-Length': data.byteLength,
|
'Content-Length': data.byteLength,
|
||||||
'etag': '"' + id + '"',
|
'etag': '"' + id + '"',
|
||||||
},
|
},
|
||||||
kStaticFiles[i].headers || {}));
|
k_static_files[i].headers || {}));
|
||||||
response.end(data);
|
response.end(data);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@ -751,7 +760,7 @@ async function blobHandler(request, response, blobId, uri) {
|
|||||||
let appObject = JSON.parse(data);
|
let appObject = JSON.parse(data);
|
||||||
data = appObject.files[uri.substring(1)];
|
data = appObject.files[uri.substring(1)];
|
||||||
data = await getBlobOrContent(data);
|
data = await getBlobOrContent(data);
|
||||||
type = guessType(uri);
|
type = guessTypeUntrusted(uri, data);
|
||||||
headers = {
|
headers = {
|
||||||
'ETag': '"' + id + '"',
|
'ETag': '"' + id + '"',
|
||||||
'Access-Control-Allow-Origin': '*',
|
'Access-Control-Allow-Origin': '*',
|
||||||
@ -769,6 +778,7 @@ async function blobHandler(request, response, blobId, uri) {
|
|||||||
headers = {
|
headers = {
|
||||||
'Access-Control-Allow-Origin': '*',
|
'Access-Control-Allow-Origin': '*',
|
||||||
};
|
};
|
||||||
|
type = guessTypeUntrusted(uri, data);
|
||||||
sendData(response, data, type, headers);
|
sendData(response, data, type, headers);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,6 +38,7 @@
|
|||||||
<iframe id="document" sandbox="allow-forms allow-scripts allow-top-navigation allow-modals allow-downloads" style="width: 100%; height: 100%; border: 0"></iframe>
|
<iframe id="document" sandbox="allow-forms allow-scripts allow-top-navigation allow-modals allow-downloads" style="width: 100%; height: 100%; border: 0"></iframe>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<script>window.litDisableBundleWarning = true;</script>
|
||||||
<script src="/split/split.min.js"></script>
|
<script src="/split/split.min.js"></script>
|
||||||
<script src="/smoothie/smoothie.js"></script>
|
<script src="/smoothie/smoothie.js"></script>
|
||||||
<script src="/static/client.js" type="module"></script>
|
<script src="/static/client.js" type="module"></script>
|
||||||
|
Loading…
Reference in New Issue
Block a user