Skip to content

Commit

Permalink
[HTML5] HTTP server uses optional SSL.
Browse files Browse the repository at this point in the history
Generates a key/cert snakeoil pair or use a custom SSL cert/key.
This is of course false security, and potentially detrimental for it.

But, so long, those are the requirements browser vendors agreed on to
use things like the Gamepad API, and more advanced topics like wasm
threads.

You don't need this if you run on localhost (at least!), but you do
need this (or a much safer nginx proxy) to try those things on your
local network (e.g. when debugging a phone, networking, etc).
  • Loading branch information
Faless committed Apr 27, 2021
1 parent 7ad901d commit c9dcf2b
Showing 1 changed file with 88 additions and 12 deletions.
100 changes: 88 additions & 12 deletions platform/javascript/export/export.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@

#include "core/io/image_loader.h"
#include "core/io/json.h"
#include "core/io/stream_peer_ssl.h"
#include "core/io/tcp_server.h"
#include "core/io/zip_io.h"
#include "editor/editor_export.h"
Expand All @@ -41,19 +42,46 @@
class EditorHTTPServer : public Reference {
private:
Ref<TCP_Server> server;
Ref<StreamPeerTCP> connection;
Map<String, String> mimes;
Ref<StreamPeerTCP> tcp;
Ref<StreamPeerSSL> ssl;
Ref<StreamPeer> peer;
Ref<CryptoKey> key;
Ref<X509Certificate> cert;
bool use_ssl = false;
uint64_t time = 0;
uint8_t req_buf[4096];
int req_pos = 0;

void _clear_client() {
connection = Ref<StreamPeerTCP>();
peer = Ref<StreamPeer>();
ssl = Ref<StreamPeerSSL>();
tcp = Ref<StreamPeerTCP>();
memset(req_buf, 0, sizeof(req_buf));
time = 0;
req_pos = 0;
}

void _set_internal_certs(Ref<Crypto> p_crypto) {
const String cache_path = EditorSettings::get_singleton()->get_cache_dir();
const String key_path = cache_path.plus_file("html5_server.key");
const String crt_path = cache_path.plus_file("html5_server.crt");
bool regen = !FileAccess::exists(key_path) || !FileAccess::exists(crt_path);
if (!regen) {
key = Ref<CryptoKey>(CryptoKey::create());
cert = Ref<X509Certificate>(X509Certificate::create());
if (key->load(key_path) != OK || cert->load(crt_path) != OK) {
regen = true;
}
}
if (regen) {
key = p_crypto->generate_rsa(2048);
key->save(key_path);
cert = p_crypto->generate_self_signed_certificate(key, "CN=godot-debug.local,O=A Game Dev,C=XXA", "20140101000000", "20340101000000");
cert->save(crt_path);
}
}

public:
EditorHTTPServer() {
mimes["html"] = "text/html";
Expand All @@ -72,7 +100,24 @@ class EditorHTTPServer : public Reference {
_clear_client();
}

Error listen(int p_port, IP_Address p_address) {
Error listen(int p_port, IP_Address p_address, bool p_use_ssl, String p_ssl_key, String p_ssl_cert) {
use_ssl = p_use_ssl;
if (use_ssl) {
Ref<Crypto> crypto = Crypto::create();
if (crypto.is_null()) {
return ERR_UNAVAILABLE;
}
if (!p_ssl_key.is_empty() && !p_ssl_cert.is_empty()) {
key = Ref<CryptoKey>(CryptoKey::create());
Error err = key->load(p_ssl_key);
ERR_FAIL_COND_V(err != OK, err);
cert = Ref<X509Certificate>(X509Certificate::create());
err = cert->load(p_ssl_cert);
ERR_FAIL_COND_V(err != OK, err);
} else {
_set_internal_certs(crypto);
}
}
return server->listen(p_port, p_address);
}

Expand Down Expand Up @@ -101,7 +146,7 @@ class EditorHTTPServer : public Reference {
s += "Connection: Close\r\n";
s += "\r\n";
CharString cs = s.utf8();
connection->put_data((const uint8_t *)cs.get_data(), cs.size() - 1);
peer->put_data((const uint8_t *)cs.get_data(), cs.size() - 1);
return;
}
const String ctype = mimes[req_ext];
Expand All @@ -117,7 +162,7 @@ class EditorHTTPServer : public Reference {
s += "Cache-Control: no-store, max-age=0\r\n";
s += "\r\n";
CharString cs = s.utf8();
Error err = connection->put_data((const uint8_t *)cs.get_data(), cs.size() - 1);
Error err = peer->put_data((const uint8_t *)cs.get_data(), cs.size() - 1);
if (err != OK) {
memdelete(f);
ERR_FAIL();
Expand All @@ -129,7 +174,7 @@ class EditorHTTPServer : public Reference {
if (read < 1) {
break;
}
err = connection->put_data(bytes, read);
err = peer->put_data(bytes, read);
if (err != OK) {
memdelete(f);
ERR_FAIL();
Expand All @@ -142,21 +187,43 @@ class EditorHTTPServer : public Reference {
if (!server->is_listening()) {
return;
}
if (connection.is_null()) {
if (tcp.is_null()) {
if (!server->is_connection_available()) {
return;
}
connection = server->take_connection();
tcp = server->take_connection();
peer = tcp;
time = OS::get_singleton()->get_ticks_usec();
}
if (OS::get_singleton()->get_ticks_usec() - time > 1000000) {
_clear_client();
return;
}
if (connection->get_status() != StreamPeerTCP::STATUS_CONNECTED) {
if (tcp->get_status() != StreamPeerTCP::STATUS_CONNECTED) {
return;
}

if (use_ssl) {
if (ssl.is_null()) {
ssl = Ref<StreamPeerSSL>(StreamPeerSSL::create());
peer = ssl;
ssl->set_blocking_handshake_enabled(false);
if (ssl->accept_stream(tcp, key, cert) != OK) {
_clear_client();
return;
}
}
ssl->poll();
if (ssl->get_status() == StreamPeerSSL::STATUS_HANDSHAKING) {
// Still handshaking, keep waiting.
return;
}
if (ssl->get_status() != StreamPeerSSL::STATUS_CONNECTED) {
_clear_client();
return;
}
}

while (true) {
char *r = (char *)req_buf;
int l = req_pos - 1;
Expand All @@ -168,7 +235,7 @@ class EditorHTTPServer : public Reference {

int read = 0;
ERR_FAIL_COND(req_pos >= 4096);
Error err = connection->get_partial_data(&req_buf[req_pos], 1, read);
Error err = peer->get_partial_data(&req_buf[req_pos], 1, read);
if (err != OK) {
// Got an error
_clear_client();
Expand Down Expand Up @@ -669,19 +736,23 @@ Error EditorExportPlatformJavaScript::run(const Ref<EditorExportPreset> &p_prese
}
ERR_FAIL_COND_V_MSG(!bind_ip.is_valid(), ERR_INVALID_PARAMETER, "Invalid editor setting 'export/web/http_host': '" + bind_host + "'. Try using '127.0.0.1'.");

const bool use_ssl = EDITOR_GET("export/web/use_ssl");
const String ssl_key = EDITOR_GET("export/web/ssl_key");
const String ssl_cert = EDITOR_GET("export/web/ssl_certificate");

// Restart server.
{
MutexLock lock(server_lock);

server->stop();
err = server->listen(bind_port, bind_ip);
err = server->listen(bind_port, bind_ip, use_ssl, ssl_key, ssl_cert);
}
if (err != OK) {
EditorNode::get_singleton()->show_warning(TTR("Error starting HTTP server:") + "\n" + itos(err));
return err;
}

OS::get_singleton()->shell_open(String("http://" + bind_host + ":" + itos(bind_port) + "/tmp_js_export.html"));
OS::get_singleton()->shell_open(String((use_ssl ? "https://" : "http://") + bind_host + ":" + itos(bind_port) + "/tmp_js_export.html"));
// FIXME: Find out how to clean up export files after running the successfully
// exported game. Might not be trivial.
return OK;
Expand Down Expand Up @@ -731,7 +802,12 @@ EditorExportPlatformJavaScript::~EditorExportPlatformJavaScript() {
void register_javascript_exporter() {
EDITOR_DEF("export/web/http_host", "localhost");
EDITOR_DEF("export/web/http_port", 8060);
EDITOR_DEF("export/web/use_ssl", false);
EDITOR_DEF("export/web/ssl_key", "");
EDITOR_DEF("export/web/ssl_certificate", "");
EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::INT, "export/web/http_port", PROPERTY_HINT_RANGE, "1,65535,1"));
EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/web/ssl_key", PROPERTY_HINT_GLOBAL_FILE, "*.key"));
EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/web/ssl_certificate", PROPERTY_HINT_GLOBAL_FILE, "*.crt,*.pem"));

Ref<EditorExportPlatformJavaScript> platform;
platform.instance();
Expand Down

0 comments on commit c9dcf2b

Please sign in to comment.