forked from cory/tildefriends
		
	This simplifies upgrading an HTTP request to a websocket, I believe, and fixes sending refresh auth tokens.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4791 ed5197a5-7fde-0310-b194-c3ffbd925b24
This commit is contained in:
		| @@ -202,11 +202,9 @@ function socket(request, response, client) { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if (refresh) { | 	response.upgrade(100, refresh ? { | ||||||
| 		return { |  | ||||||
| 		'Set-Cookie': `session=${refresh.token}; path=/; Max-Age=${refresh.interval}; Secure; SameSite=Strict`, | 		'Set-Cookie': `session=${refresh.token}; path=/; Max-Age=${refresh.interval}; Secure; SameSite=Strict`, | ||||||
| 		}; | 	} : {}); | ||||||
| 	} |  | ||||||
| } | } | ||||||
|  |  | ||||||
| export { socket, App }; | export { socket, App }; | ||||||
|   | |||||||
| @@ -982,7 +982,7 @@ loadSettings().then(function() { | |||||||
| 		httpd.set_http_redirect(gGlobalSettings.http_redirect); | 		httpd.set_http_redirect(gGlobalSettings.http_redirect); | ||||||
| 	} | 	} | ||||||
| 	httpd.all("/login", auth.handler); | 	httpd.all("/login", auth.handler); | ||||||
| 	httpd.registerSocketHandler("/app/socket", app.socket); | 	httpd.all("/app/socket", app.socket); | ||||||
| 	httpd.all("", function(request, response) { | 	httpd.all("", function(request, response) { | ||||||
| 		let match; | 		let match; | ||||||
| 		if (request.uri === "/" || request.uri === "") { | 		if (request.uri === "/" || request.uri === "") { | ||||||
|   | |||||||
							
								
								
									
										139
									
								
								src/httpd.js.c
									
									
									
									
									
								
							
							
						
						
									
										139
									
								
								src/httpd.js.c
									
									
									
									
									
								
							| @@ -21,6 +21,8 @@ | |||||||
|  |  | ||||||
| #define tf_countof(a) ((int)(sizeof((a)) / sizeof(*(a)))) | #define tf_countof(a) ((int)(sizeof((a)) / sizeof(*(a)))) | ||||||
|  |  | ||||||
|  | static JSValue _httpd_websocket_upgrade(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv); | ||||||
|  |  | ||||||
| static JSClassID _httpd_class_id; | static JSClassID _httpd_class_id; | ||||||
| static JSClassID _httpd_request_class_id; | static JSClassID _httpd_request_class_id; | ||||||
|  |  | ||||||
| @@ -42,6 +44,34 @@ static JSValue _httpd_response_write_head(JSContext* context, JSValueConst this_ | |||||||
| 	return JS_UNDEFINED; | 	return JS_UNDEFINED; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | static int _object_to_headers(JSContext* context, JSValue object, const char** headers, int headers_length) | ||||||
|  | { | ||||||
|  | 	int count = 0; | ||||||
|  | 	JSPropertyEnum* ptab = NULL; | ||||||
|  | 	uint32_t plen = 0; | ||||||
|  | 	JS_GetOwnPropertyNames(context, &ptab, &plen, object, JS_GPN_STRING_MASK); | ||||||
|  | 	for (; count < (int)plen && count < headers_length / 2; ++count) | ||||||
|  | 	{ | ||||||
|  | 		JSValue key = JS_AtomToString(context, ptab[count].atom); | ||||||
|  | 		JSPropertyDescriptor desc; | ||||||
|  | 		JSValue key_value = JS_NULL; | ||||||
|  | 		if (JS_GetOwnProperty(context, &desc, object, ptab[count].atom) == 1) | ||||||
|  | 		{ | ||||||
|  | 			key_value = desc.value; | ||||||
|  | 			JS_FreeValue(context, desc.setter); | ||||||
|  | 			JS_FreeValue(context, desc.getter); | ||||||
|  | 		} | ||||||
|  | 		headers[count * 2 + 0] = JS_ToCString(context, key); | ||||||
|  | 		headers[count * 2 + 1] = JS_ToCString(context, key_value); | ||||||
|  | 	} | ||||||
|  | 	for (uint32_t i = 0; i < plen; ++i) | ||||||
|  | 	{ | ||||||
|  | 		JS_FreeAtom(context, ptab[i].atom); | ||||||
|  | 	} | ||||||
|  | 	js_free(context, ptab); | ||||||
|  | 	return count; | ||||||
|  | } | ||||||
|  |  | ||||||
| static JSValue _httpd_response_end(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) | static JSValue _httpd_response_end(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) | ||||||
| { | { | ||||||
| 	tf_http_request_t* request = JS_GetOpaque(this_val, _httpd_request_class_id); | 	tf_http_request_t* request = JS_GetOpaque(this_val, _httpd_request_class_id); | ||||||
| @@ -71,40 +101,16 @@ static JSValue _httpd_response_end(JSContext* context, JSValueConst this_val, in | |||||||
| 	JS_ToInt32(context, &status, response_status); | 	JS_ToInt32(context, &status, response_status); | ||||||
| 	JS_FreeValue(context, response_status); | 	JS_FreeValue(context, response_status); | ||||||
|  |  | ||||||
| 	const char** headers = NULL; | 	const char* headers[64] = { 0 }; | ||||||
| 	int headers_length = 0; |  | ||||||
| 	JSValue response_headers = JS_GetPropertyStr(context, this_val, "response_headers"); | 	JSValue response_headers = JS_GetPropertyStr(context, this_val, "response_headers"); | ||||||
| 	JSPropertyEnum* ptab; | 	int headers_count = _object_to_headers(context, response_headers, headers, tf_countof(headers)); | ||||||
| 	uint32_t plen; |  | ||||||
| 	JS_GetOwnPropertyNames(context, &ptab, &plen, response_headers, JS_GPN_STRING_MASK); |  | ||||||
| 	headers = alloca(sizeof(const char*) * plen * 2); |  | ||||||
| 	headers_length = plen; |  | ||||||
| 	for (uint32_t i = 0; i < plen; ++i) |  | ||||||
| 	{ |  | ||||||
| 		JSValue key = JS_AtomToString(context, ptab[i].atom); |  | ||||||
| 		JSPropertyDescriptor desc; |  | ||||||
| 		JSValue key_value = JS_NULL; |  | ||||||
| 		if (JS_GetOwnProperty(context, &desc, response_headers, ptab[i].atom) == 1) |  | ||||||
| 		{ |  | ||||||
| 			key_value = desc.value; |  | ||||||
| 			JS_FreeValue(context, desc.setter); |  | ||||||
| 			JS_FreeValue(context, desc.getter); |  | ||||||
| 		} |  | ||||||
| 		headers[i * 2 + 0] = JS_ToCString(context, key); |  | ||||||
| 		headers[i * 2 + 1] = JS_ToCString(context, key_value); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	tf_http_respond(request, status, headers, headers_length, data, length); | 	tf_http_respond(request, status, headers, headers_count, data, length); | ||||||
|  |  | ||||||
| 	for (int i = 0; i < headers_length * 2; i++) | 	for (int i = 0; i < headers_count * 2; i++) | ||||||
| 	{ | 	{ | ||||||
| 		JS_FreeCString(context, headers[i]); | 		JS_FreeCString(context, headers[i]); | ||||||
| 	} | 	} | ||||||
| 	for (uint32_t i = 0; i < plen; ++i) |  | ||||||
| 	{ |  | ||||||
| 		JS_FreeAtom(context, ptab[i].atom); |  | ||||||
| 	} |  | ||||||
| 	js_free(context, ptab); |  | ||||||
| 	JS_FreeValue(context, buffer); | 	JS_FreeValue(context, buffer); | ||||||
| 	return JS_UNDEFINED; | 	return JS_UNDEFINED; | ||||||
| } | } | ||||||
| @@ -204,6 +210,7 @@ static void _httpd_callback_internal(tf_http_request_t* request, bool is_websock | |||||||
| 	JS_SetPropertyStr(context, response_object, "writeHead", JS_NewCFunction(context, _httpd_response_write_head, "writeHead", 2)); | 	JS_SetPropertyStr(context, response_object, "writeHead", JS_NewCFunction(context, _httpd_response_write_head, "writeHead", 2)); | ||||||
| 	JS_SetPropertyStr(context, response_object, "end", JS_NewCFunction(context, _httpd_response_end, "end", 1)); | 	JS_SetPropertyStr(context, response_object, "end", JS_NewCFunction(context, _httpd_response_end, "end", 1)); | ||||||
| 	JS_SetPropertyStr(context, response_object, "send", JS_NewCFunction(context, _httpd_response_send, "send", 2)); | 	JS_SetPropertyStr(context, response_object, "send", JS_NewCFunction(context, _httpd_response_send, "send", 2)); | ||||||
|  | 	JS_SetPropertyStr(context, response_object, "upgrade", JS_NewCFunction(context, _httpd_websocket_upgrade, "upgrade", 2)); | ||||||
| 	JSValue args[] = | 	JSValue args[] = | ||||||
| 	{ | 	{ | ||||||
| 		request_object, | 		request_object, | ||||||
| @@ -213,18 +220,8 @@ static void _httpd_callback_internal(tf_http_request_t* request, bool is_websock | |||||||
| 	tf_util_report_error(context, response); | 	tf_util_report_error(context, response); | ||||||
| 	JS_FreeValue(context, request_object); | 	JS_FreeValue(context, request_object); | ||||||
| 	JS_FreeValue(context, response); | 	JS_FreeValue(context, response); | ||||||
|  |  | ||||||
| 	if (is_websocket) |  | ||||||
| 	{ |  | ||||||
| 		request->on_message = _httpd_message_callback; |  | ||||||
| 		request->context = context; |  | ||||||
| 		request->user_data = JS_VALUE_GET_PTR(response_object); |  | ||||||
| 	} |  | ||||||
| 	else |  | ||||||
| 	{ |  | ||||||
| 	JS_FreeValue(context, response_object); | 	JS_FreeValue(context, response_object); | ||||||
| } | } | ||||||
| } |  | ||||||
|  |  | ||||||
| static bool _httpd_redirect(tf_http_request_t* request) | static bool _httpd_redirect(tf_http_request_t* request) | ||||||
| { | { | ||||||
| @@ -255,13 +252,10 @@ static void _httpd_callback(tf_http_request_t* request) | |||||||
| 	_httpd_callback_internal(request, false); | 	_httpd_callback_internal(request, false); | ||||||
| } | } | ||||||
|  |  | ||||||
| static void _httpd_websocket_callback(tf_http_request_t* request) | static JSValue _httpd_websocket_upgrade(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) | ||||||
| { | { | ||||||
| 	if (_httpd_redirect(request)) | 	tf_http_request_t* request = JS_GetOpaque(this_val, _httpd_request_class_id); | ||||||
| 	{ | 	tf_http_request_ref(request); | ||||||
| 		return; |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	const char* header_connection = tf_http_request_get_header(request, "connection"); | 	const char* header_connection = tf_http_request_get_header(request, "connection"); | ||||||
| 	const char* header_upgrade = tf_http_request_get_header(request, "upgrade"); | 	const char* header_upgrade = tf_http_request_get_header(request, "upgrade"); | ||||||
| 	const char* header_sec_websocket_key = tf_http_request_get_header(request, "sec-websocket-key"); | 	const char* header_sec_websocket_key = tf_http_request_get_header(request, "sec-websocket-key"); | ||||||
| @@ -281,26 +275,51 @@ static void _httpd_websocket_callback(tf_http_request_t* request) | |||||||
| 		SHA1(key_magic, size, digest); | 		SHA1(key_magic, size, digest); | ||||||
| 		char key[41] = { 0 }; | 		char key[41] = { 0 }; | ||||||
| 		tf_base64_encode(digest, sizeof(digest), key, sizeof(key)); | 		tf_base64_encode(digest, sizeof(digest), key, sizeof(key)); | ||||||
| 		enum { k_headers_count = 4 }; |  | ||||||
| 		const char* headers[k_headers_count * 2] = | 		const char* headers[64] = { 0 }; | ||||||
| 		{ | 		int headers_count = 0; | ||||||
| 			"Upgrade", "websocket", |  | ||||||
| 			"Connection", "Upgrade", | 		headers[headers_count * 2 + 0] = "Upgrade"; | ||||||
| 			"Sec-WebSocket-Accept", key, | 		headers[headers_count * 2 + 1] = "websocket"; | ||||||
| 			"Sec-WebSocket-Version", "13", | 		headers_count++; | ||||||
| 		}; |  | ||||||
|  | 		headers[headers_count * 2 + 0] = "Connection"; | ||||||
|  | 		headers[headers_count * 2 + 1] = "Upgrade"; | ||||||
|  | 		headers_count++; | ||||||
|  |  | ||||||
|  | 		headers[headers_count * 2 + 0] = "Sec-WebSocket-Accept"; | ||||||
|  | 		headers[headers_count * 2 + 1] = key; | ||||||
|  | 		headers_count++; | ||||||
|  |  | ||||||
| 		bool send_version = | 		bool send_version = | ||||||
| 			!tf_http_request_get_header(request, "sec-websocket-version") || | 			!tf_http_request_get_header(request, "sec-websocket-version") || | ||||||
| 			strcmp(tf_http_request_get_header(request, "sec-websocket-version"), "13") != 0; | 			strcmp(tf_http_request_get_header(request, "sec-websocket-version"), "13") != 0; | ||||||
|  | 		if (send_version) | ||||||
|  | 		{ | ||||||
|  | 			headers[headers_count * 2 + 0] = "Sec-WebSocket-Accept"; | ||||||
|  | 			headers[headers_count * 2 + 1] = key; | ||||||
|  | 			headers_count++; | ||||||
|  | 		} | ||||||
|  | 		headers_count += _object_to_headers(context, argv[1], headers + headers_count * 2, tf_countof(headers) - headers_count * 2); | ||||||
|  |  | ||||||
|  | 		for (int i = 0; i < headers_count; i += 2) | ||||||
|  | 		{ | ||||||
|  | 			tf_printf("[%d] %s = %s\n", i / 2, headers[i * 2 + 0], headers[i * 2 + 1]); | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		tf_http_request_websocket_upgrade(request); | 		tf_http_request_websocket_upgrade(request); | ||||||
| 		tf_http_respond(request, 101, headers, send_version ? k_headers_count : (k_headers_count - 1), NULL, 0); | 		tf_http_respond(request, 101, headers, headers_count, NULL, 0); | ||||||
|  |  | ||||||
|  | 		request->on_message = _httpd_message_callback; | ||||||
|  | 		request->context = context; | ||||||
|  | 		request->user_data = JS_VALUE_GET_PTR(JS_DupValue(context, this_val)); | ||||||
| 	} | 	} | ||||||
| 	else | 	else | ||||||
| 	{ | 	{ | ||||||
| 		tf_http_respond(request, 400, NULL, 0, NULL, 0); | 		tf_http_respond(request, 400, NULL, 0, NULL, 0); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	_httpd_callback_internal(request, true); | 	return JS_UNDEFINED; | ||||||
| } | } | ||||||
|  |  | ||||||
| static JSValue _httpd_endpoint_all(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) | static JSValue _httpd_endpoint_all(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) | ||||||
| @@ -314,17 +333,6 @@ static JSValue _httpd_endpoint_all(JSContext* context, JSValueConst this_val, in | |||||||
| 	return JS_UNDEFINED; | 	return JS_UNDEFINED; | ||||||
| } | } | ||||||
|  |  | ||||||
| static JSValue _httpd_register_socket_handler(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) |  | ||||||
| { |  | ||||||
| 	tf_http_t* http = JS_GetOpaque(this_val, _httpd_class_id); |  | ||||||
| 	const char* pattern = JS_ToCString(context, argv[0]); |  | ||||||
| 	http_handler_data_t* data = tf_malloc(sizeof(http_handler_data_t)); |  | ||||||
| 	*data = (http_handler_data_t) { .context = context, .callback = JS_DupValue(context, argv[1]) }; |  | ||||||
| 	tf_http_add_handler(http, pattern, _httpd_websocket_callback, data); |  | ||||||
| 	JS_FreeCString(context, pattern); |  | ||||||
| 	return JS_UNDEFINED; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| static JSValue _httpd_endpoint_start(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) | static JSValue _httpd_endpoint_start(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) | ||||||
| { | { | ||||||
| 	tf_http_t* http = JS_GetOpaque(this_val, _httpd_class_id); | 	tf_http_t* http = JS_GetOpaque(this_val, _httpd_class_id); | ||||||
| @@ -513,7 +521,6 @@ void tf_httpd_register(JSContext* context) | |||||||
|  |  | ||||||
| 	JS_SetPropertyStr(context, httpd, "handlers", JS_NewObject(context)); | 	JS_SetPropertyStr(context, httpd, "handlers", JS_NewObject(context)); | ||||||
| 	JS_SetPropertyStr(context, httpd, "all", JS_NewCFunction(context, _httpd_endpoint_all, "all", 2)); | 	JS_SetPropertyStr(context, httpd, "all", JS_NewCFunction(context, _httpd_endpoint_all, "all", 2)); | ||||||
| 	JS_SetPropertyStr(context, httpd, "registerSocketHandler", JS_NewCFunction(context, _httpd_register_socket_handler, "register_socket_handler", 2)); |  | ||||||
| 	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, global, "httpd", httpd); | 	JS_SetPropertyStr(context, global, "httpd", httpd); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user