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
|
let image = html`<span
|
||||||
class="w3-theme-light w3-circle"
|
class="w3-theme-light w3-circle"
|
||||||
style="display: inline-block; width: 2em; height: 2em; text-align: center; line-height: 2em"
|
style="display: inline-block; width: 2em; height: 2em; text-align: center; line-height: 2em"
|
||||||
>?</span>`;
|
>?</span
|
||||||
|
>`;
|
||||||
let name = this.users?.[this.id]?.name;
|
let name = this.users?.[this.id]?.name;
|
||||||
name =
|
name =
|
||||||
name !== undefined
|
name !== undefined
|
||||||
@ -31,7 +32,8 @@ class TfUserElement extends LitElement {
|
|||||||
|
|
||||||
if (this.users[this.id]) {
|
if (this.users[this.id]) {
|
||||||
let image_link = this.users[this.id].image;
|
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) {
|
if (image_link !== undefined) {
|
||||||
image = html`<img
|
image = html`<img
|
||||||
class="w3-circle"
|
class="w3-circle"
|
||||||
@ -41,8 +43,7 @@ class TfUserElement extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return html` <div style="display: inline-block; font-weight: bold">
|
return html` <div style="display: inline-block; font-weight: bold">
|
||||||
${image}
|
${image} ${name}
|
||||||
${name}
|
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -208,7 +208,10 @@ class TfNavigationElement extends LitElement {
|
|||||||
</div>
|
</div>
|
||||||
</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`
|
return html`
|
||||||
<link type="text/css" rel="stylesheet" href="/static/w3.css" />
|
<link type="text/css" rel="stylesheet" href="/static/w3.css" />
|
||||||
<button
|
<button
|
||||||
|
141
core/core.js
141
core/core.js
@ -8,116 +8,6 @@ let gStatsTimer = false;
|
|||||||
const k_content_security_policy =
|
const k_content_security_policy =
|
||||||
'sandbox allow-downloads allow-top-navigation-by-user-activation';
|
'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 = [
|
let k_static_files = [
|
||||||
{uri: '/', path: 'index.html', type: 'text/html; charset=UTF-8'},
|
{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
|
* TODOC
|
||||||
* @param {*} response
|
* @param {*} response
|
||||||
@ -979,7 +846,9 @@ function sendData(response, data, type, headers, status_code) {
|
|||||||
Object.assign(
|
Object.assign(
|
||||||
{
|
{
|
||||||
'Content-Type':
|
'Content-Type':
|
||||||
type || guessTypeFromMagicBytes(data) || 'application/binary',
|
type ||
|
||||||
|
httpd.mime_type_from_magic_bytes(data) ||
|
||||||
|
'application/binary',
|
||||||
'Content-Length': data.byteLength,
|
'Content-Length': data.byteLength,
|
||||||
},
|
},
|
||||||
headers || {}
|
headers || {}
|
||||||
@ -1348,7 +1217,9 @@ async function blobHandler(request, response, blobId, uri) {
|
|||||||
'Content-Security-Policy': k_content_security_policy,
|
'Content-Security-Policy': k_content_security_policy,
|
||||||
};
|
};
|
||||||
data = await getBlobOrContent(id);
|
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);
|
sendData(response, data, type, headers);
|
||||||
}
|
}
|
||||||
} else {
|
} 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;
|
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)
|
static void _httpd_finalizer(JSRuntime* runtime, JSValue value)
|
||||||
{
|
{
|
||||||
tf_http_t* http = JS_GetOpaque(value, _httpd_class_id);
|
tf_http_t* http = JS_GetOpaque(value, _httpd_class_id);
|
||||||
@ -627,30 +772,6 @@ typedef struct _http_file_t
|
|||||||
char etag[512];
|
char etag[512];
|
||||||
} http_file_t;
|
} 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)
|
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;
|
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)
|
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[] = {
|
const char* headers[] = {
|
||||||
"Content-Type",
|
"Content-Type",
|
||||||
content_type,
|
content_type,
|
||||||
@ -672,7 +793,7 @@ static void _httpd_endpoint_static_read(tf_task_t* task, const char* path, int r
|
|||||||
}
|
}
|
||||||
else
|
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[] = {
|
const char* headers[] = {
|
||||||
"Content-Type",
|
"Content-Type",
|
||||||
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, "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, "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, "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_SetPropertyStr(context, global, "httpd", httpd);
|
||||||
JS_FreeValue(context, global);
|
JS_FreeValue(context, global);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user