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:
		
							
								
								
									
										98
									
								
								core/core.js
									
									
									
									
									
								
							
							
						
						
									
										98
									
								
								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);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -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>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
		<script>window.litDisableBundleWarning = true;</script>
 | 
			
		||||
		<script src="/split/split.min.js"></script>
 | 
			
		||||
		<script src="/smoothie/smoothie.js"></script>
 | 
			
		||||
		<script src="/static/client.js" type="module"></script>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user