forked from cory/tildefriends
Move mime type shenanigans from JS => C.
This commit is contained in:
parent
74bb2151c1
commit
523c9c9ad2
@ -22,7 +22,8 @@ class TfUserElement extends LitElement {
|
||||
let image = html`<span
|
||||
class="w3-theme-light w3-circle"
|
||||
style="display: inline-block; width: 2em; height: 2em; text-align: center; line-height: 2em"
|
||||
>?</span>`;
|
||||
>?</span
|
||||
>`;
|
||||
let name = this.users?.[this.id]?.name;
|
||||
name =
|
||||
name !== undefined
|
||||
@ -31,7 +32,8 @@ class TfUserElement extends LitElement {
|
||||
|
||||
if (this.users[this.id]) {
|
||||
let image_link = this.users[this.id].image;
|
||||
image_link = typeof image_link == 'string' ? image_link : image_link?.link;
|
||||
image_link =
|
||||
typeof image_link == 'string' ? image_link : image_link?.link;
|
||||
if (image_link !== undefined) {
|
||||
image = html`<img
|
||||
class="w3-circle"
|
||||
@ -41,8 +43,7 @@ class TfUserElement extends LitElement {
|
||||
}
|
||||
}
|
||||
return html` <div style="display: inline-block; font-weight: bold">
|
||||
${image}
|
||||
${name}
|
||||
${image} ${name}
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
@ -208,7 +208,10 @@ class TfNavigationElement extends LitElement {
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
} else if (this.credentials?.session?.name && this.credentials.session.name !== 'guest') {
|
||||
} else if (
|
||||
this.credentials?.session?.name &&
|
||||
this.credentials.session.name !== 'guest'
|
||||
) {
|
||||
return html`
|
||||
<link type="text/css" rel="stylesheet" href="/static/w3.css" />
|
||||
<button
|
||||
|
141
core/core.js
141
core/core.js
@ -8,116 +8,6 @@ let gStatsTimer = false;
|
||||
const k_content_security_policy =
|
||||
'sandbox allow-downloads allow-top-navigation-by-user-activation';
|
||||
|
||||
const k_mime_types = {
|
||||
css: 'text/css',
|
||||
html: 'text/html',
|
||||
js: 'text/javascript',
|
||||
json: 'text/json',
|
||||
map: 'application/json',
|
||||
svg: 'image/svg+xml',
|
||||
};
|
||||
|
||||
const k_magic_bytes = [
|
||||
{bytes: [0xff, 0xd8, 0xff, 0xdb], type: 'image/jpeg'},
|
||||
{
|
||||
bytes: [
|
||||
0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01,
|
||||
],
|
||||
type: 'image/jpeg',
|
||||
},
|
||||
{bytes: [0xff, 0xd8, 0xff, 0xee], type: 'image/jpeg'},
|
||||
{
|
||||
bytes: [
|
||||
0xff,
|
||||
0xd8,
|
||||
0xff,
|
||||
0xe1,
|
||||
null,
|
||||
null,
|
||||
0x45,
|
||||
0x78,
|
||||
0x69,
|
||||
0x66,
|
||||
0x00,
|
||||
0x00,
|
||||
],
|
||||
type: 'image/jpeg',
|
||||
},
|
||||
{bytes: [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a], type: 'image/png'},
|
||||
{bytes: [0x47, 0x49, 0x46, 0x38, 0x37, 0x61], type: 'image/gif'},
|
||||
{bytes: [0x47, 0x49, 0x46, 0x38, 0x39, 0x61], type: 'image/gif'},
|
||||
{
|
||||
bytes: [
|
||||
0x52,
|
||||
0x49,
|
||||
0x46,
|
||||
0x46,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
0x57,
|
||||
0x45,
|
||||
0x42,
|
||||
0x50,
|
||||
],
|
||||
type: 'image/webp',
|
||||
},
|
||||
{bytes: [0x3c, 0x73, 0x76, 0x67], type: 'image/svg+xml'},
|
||||
{
|
||||
bytes: [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
0x66,
|
||||
0x74,
|
||||
0x79,
|
||||
0x70,
|
||||
0x6d,
|
||||
0x70,
|
||||
0x34,
|
||||
0x32,
|
||||
],
|
||||
type: 'audio/mpeg',
|
||||
},
|
||||
{
|
||||
bytes: [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
0x66,
|
||||
0x74,
|
||||
0x79,
|
||||
0x70,
|
||||
0x69,
|
||||
0x73,
|
||||
0x6f,
|
||||
0x6d,
|
||||
],
|
||||
type: 'video/mp4',
|
||||
},
|
||||
{
|
||||
bytes: [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
0x66,
|
||||
0x74,
|
||||
0x79,
|
||||
0x70,
|
||||
0x6d,
|
||||
0x70,
|
||||
0x34,
|
||||
0x32,
|
||||
],
|
||||
type: 'video/mp4',
|
||||
},
|
||||
{bytes: [0x4d, 0x54, 0x68, 0x64], type: 'audio/midi'},
|
||||
];
|
||||
|
||||
let k_static_files = [
|
||||
{uri: '/', path: 'index.html', type: 'text/html; charset=UTF-8'},
|
||||
];
|
||||
@ -941,29 +831,6 @@ function startsWithBytes(data, bytes) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} path
|
||||
* @returns
|
||||
*/
|
||||
function guessTypeFromName(path) {
|
||||
let extension = path.split('.').pop();
|
||||
return k_mime_types[extension];
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} data
|
||||
* @returns
|
||||
*/
|
||||
function guessTypeFromMagicBytes(data) {
|
||||
for (let magic of k_magic_bytes) {
|
||||
if (startsWithBytes(data, magic.bytes)) {
|
||||
return magic.type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} response
|
||||
@ -979,7 +846,9 @@ function sendData(response, data, type, headers, status_code) {
|
||||
Object.assign(
|
||||
{
|
||||
'Content-Type':
|
||||
type || guessTypeFromMagicBytes(data) || 'application/binary',
|
||||
type ||
|
||||
httpd.mime_type_from_magic_bytes(data) ||
|
||||
'application/binary',
|
||||
'Content-Length': data.byteLength,
|
||||
},
|
||||
headers || {}
|
||||
@ -1348,7 +1217,9 @@ async function blobHandler(request, response, blobId, uri) {
|
||||
'Content-Security-Policy': k_content_security_policy,
|
||||
};
|
||||
data = await getBlobOrContent(id);
|
||||
let type = guessTypeFromName(uri) || guessTypeFromMagicBytes(data);
|
||||
let type =
|
||||
httpd.mime_type_from_extension(uri) ||
|
||||
httpd.mime_type_from_magic_bytes(data);
|
||||
sendData(response, data, type, headers);
|
||||
}
|
||||
} else {
|
||||
|
175
src/httpd.js.c
175
src/httpd.js.c
@ -498,6 +498,151 @@ static JSValue _httpd_auth_query(JSContext* context, JSValueConst this_val, int
|
||||
return result;
|
||||
}
|
||||
|
||||
typedef struct _magic_bytes_t
|
||||
{
|
||||
const char* type;
|
||||
uint8_t bytes[12];
|
||||
uint8_t ignore[12];
|
||||
} magic_bytes_t;
|
||||
|
||||
static bool _magic_bytes_match(const magic_bytes_t* magic, const uint8_t* actual, size_t size)
|
||||
{
|
||||
if (size < sizeof(magic->bytes))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int length = (int)tf_min(sizeof(magic->bytes), size);
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
if ((magic->bytes[i] & ~magic->ignore[i]) != (actual[i] & ~magic->ignore[i]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static JSValue _httpd_mime_type_from_magic_bytes(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||
{
|
||||
JSValue result = JS_UNDEFINED;
|
||||
size_t size = 0;
|
||||
uint8_t* bytes = tf_util_try_get_array_buffer(context, &size, argv[0]);
|
||||
if (bytes)
|
||||
{
|
||||
|
||||
const magic_bytes_t k_magic_bytes[] = {
|
||||
{
|
||||
.type = "image/jpeg",
|
||||
.bytes = { 0xff, 0xd8, 0xff, 0xdb },
|
||||
},
|
||||
{
|
||||
.type = "image/jpeg",
|
||||
.bytes = { 0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01 },
|
||||
},
|
||||
{
|
||||
.type = "image/jpeg",
|
||||
.bytes = { 0xff, 0xd8, 0xff, 0xee },
|
||||
},
|
||||
{
|
||||
.type = "image/jpeg",
|
||||
.bytes = { 0xff, 0xd8, 0xff, 0xe1, 0x00, 0x00, 0x45, 0x78, 0x69, 0x66, 0x00, 0x00 },
|
||||
.ignore = { 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
|
||||
},
|
||||
{
|
||||
.type = "image/png",
|
||||
.bytes = { 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a },
|
||||
},
|
||||
{
|
||||
.type = "image/gif",
|
||||
.bytes = { 0x47, 0x49, 0x46, 0x38, 0x37, 0x61 },
|
||||
},
|
||||
{
|
||||
.type = "image/gif",
|
||||
.bytes = { 0x47, 0x49, 0x46, 0x38, 0x39, 0x61 },
|
||||
},
|
||||
{
|
||||
.type = "image/webp",
|
||||
.bytes = { 0x52, 0x49, 0x46, 0x46, 0x00, 0x00, 0x00, 0x00, 0x57, 0x45, 0x42, 0x50 },
|
||||
.ignore = { 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00 },
|
||||
},
|
||||
{
|
||||
.type = "image/svg+xml",
|
||||
.bytes = { 0x3c, 0x73, 0x76, 0x67 },
|
||||
},
|
||||
{
|
||||
.type = "audio/mpeg",
|
||||
.bytes = { 0x00, 0x00, 0x00, 0x00, 0x66, 0x74, 0x79, 0x70, 0x6d, 0x70, 0x34, 0x32 },
|
||||
.ignore = { 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
|
||||
},
|
||||
{
|
||||
.type = "video/mp4",
|
||||
.bytes = { 0x00, 0x00, 0x00, 0x00, 0x66, 0x74, 0x79, 0x70, 0x69, 0x73, 0x6f, 0x6d },
|
||||
.ignore = { 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
|
||||
},
|
||||
{
|
||||
.type = "video/mp4",
|
||||
.bytes = { 0x00, 0x00, 0x00, 0x00, 0x66, 0x74, 0x79, 0x70, 0x6d, 0x70, 0x34, 0x32 },
|
||||
.ignore = { 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
|
||||
},
|
||||
{
|
||||
.type = "audio/midi",
|
||||
.bytes = { 0x4d, 0x54, 0x68, 0x64 },
|
||||
},
|
||||
};
|
||||
|
||||
for (int i = 0; i < tf_countof(k_magic_bytes); i++)
|
||||
{
|
||||
if (_magic_bytes_match(&k_magic_bytes[i], bytes, size))
|
||||
{
|
||||
result = JS_NewString(context, k_magic_bytes[i].type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static const char* _ext_to_content_type(const char* ext, bool use_fallback)
|
||||
{
|
||||
if (ext)
|
||||
{
|
||||
typedef struct _ext_type_t
|
||||
{
|
||||
const char* ext;
|
||||
const char* type;
|
||||
} ext_type_t;
|
||||
|
||||
const ext_type_t k_types[] = {
|
||||
{ .ext = ".html", .type = "text/html; charset=UTF-8" },
|
||||
{ .ext = ".js", .type = "text/javascript; charset=UTF-8" },
|
||||
{ .ext = ".mjs", .type = "text/javascript; charset=UTF-8" },
|
||||
{ .ext = ".css", .type = "text/css; charset=UTF-8" },
|
||||
{ .ext = ".png", .type = "image/png" },
|
||||
{ .ext = ".json", .type = "application/json" },
|
||||
{ .ext = ".map", .type = "application/json" },
|
||||
{ .ext = ".svg", .type = "image/svg+xml" },
|
||||
};
|
||||
|
||||
for (int i = 0; i < tf_countof(k_types); i++)
|
||||
{
|
||||
if (strcmp(ext, k_types[i].ext) == 0)
|
||||
{
|
||||
return k_types[i].type;
|
||||
}
|
||||
}
|
||||
}
|
||||
return use_fallback ? "application/binary" : NULL;
|
||||
}
|
||||
|
||||
static JSValue _httpd_mime_type_from_extension(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||
{
|
||||
const char* name = JS_ToCString(context, argv[0]);
|
||||
const char* type = _ext_to_content_type(strrchr(name, '.'), false);
|
||||
JS_FreeCString(context, name);
|
||||
return type ? JS_NewString(context, type) : JS_UNDEFINED;
|
||||
}
|
||||
|
||||
static void _httpd_finalizer(JSRuntime* runtime, JSValue value)
|
||||
{
|
||||
tf_http_t* http = JS_GetOpaque(value, _httpd_class_id);
|
||||
@ -627,30 +772,6 @@ typedef struct _http_file_t
|
||||
char etag[512];
|
||||
} http_file_t;
|
||||
|
||||
static const char* _ext_to_content_type(const char* ext)
|
||||
{
|
||||
if (ext)
|
||||
{
|
||||
if (strcmp(ext, ".html") == 0)
|
||||
{
|
||||
return "text/html; charset=UTF-8";
|
||||
}
|
||||
else if (strcmp(ext, ".js") == 0 || strcmp(ext, ".mjs") == 0)
|
||||
{
|
||||
return "text/javascript; charset=UTF-8";
|
||||
}
|
||||
else if (strcmp(ext, ".css") == 0)
|
||||
{
|
||||
return "text/css; charset=UTF-8";
|
||||
}
|
||||
else if (strcmp(ext, ".png") == 0)
|
||||
{
|
||||
return "image/png";
|
||||
}
|
||||
}
|
||||
return "application/binary";
|
||||
}
|
||||
|
||||
static void _httpd_endpoint_static_read(tf_task_t* task, const char* path, int result, const void* data, void* user_data)
|
||||
{
|
||||
http_file_t* file = user_data;
|
||||
@ -659,7 +780,7 @@ static void _httpd_endpoint_static_read(tf_task_t* task, const char* path, int r
|
||||
{
|
||||
if (strcmp(path, "core/tfrpc.js") == 0)
|
||||
{
|
||||
const char* content_type = _ext_to_content_type(strrchr(path, '.'));
|
||||
const char* content_type = _ext_to_content_type(strrchr(path, '.'), true);
|
||||
const char* headers[] = {
|
||||
"Content-Type",
|
||||
content_type,
|
||||
@ -672,7 +793,7 @@ static void _httpd_endpoint_static_read(tf_task_t* task, const char* path, int r
|
||||
}
|
||||
else
|
||||
{
|
||||
const char* content_type = _ext_to_content_type(strrchr(path, '.'));
|
||||
const char* content_type = _ext_to_content_type(strrchr(path, '.'), true);
|
||||
const char* headers[] = {
|
||||
"Content-Type",
|
||||
content_type,
|
||||
@ -1519,6 +1640,8 @@ void tf_httpd_register(JSContext* context)
|
||||
JS_SetPropertyStr(context, httpd, "start", JS_NewCFunction(context, _httpd_endpoint_start, "start", 2));
|
||||
JS_SetPropertyStr(context, httpd, "set_http_redirect", JS_NewCFunction(context, _httpd_set_http_redirect, "set_http_redirect", 1));
|
||||
JS_SetPropertyStr(context, httpd, "auth_query", JS_NewCFunction(context, _httpd_auth_query, "auth_query", 1));
|
||||
JS_SetPropertyStr(context, httpd, "mime_type_from_magic_bytes", JS_NewCFunction(context, _httpd_mime_type_from_magic_bytes, "mime_type_from_magic_bytes", 1));
|
||||
JS_SetPropertyStr(context, httpd, "mime_type_from_extension", JS_NewCFunction(context, _httpd_mime_type_from_extension, "mime_type_from_extension", 1));
|
||||
JS_SetPropertyStr(context, global, "httpd", httpd);
|
||||
JS_FreeValue(context, global);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user