Path: blob/master/platform/web/export/editor_http_server.cpp
10278 views
/**************************************************************************/1/* editor_http_server.cpp */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930#include "editor_http_server.h"3132void EditorHTTPServer::_server_thread_poll(void *data) {33EditorHTTPServer *web_server = static_cast<EditorHTTPServer *>(data);34while (!web_server->server_quit.is_set()) {35OS::get_singleton()->delay_usec(6900);36{37MutexLock lock(web_server->server_lock);38web_server->_poll();39}40}41}4243void EditorHTTPServer::_clear_client() {44peer = Ref<StreamPeer>();45tls = Ref<StreamPeerTLS>();46tcp = Ref<StreamPeerTCP>();47memset(req_buf, 0, sizeof(req_buf));48time = 0;49req_pos = 0;50}5152void EditorHTTPServer::_set_internal_certs(Ref<Crypto> p_crypto) {53const String cache_path = EditorPaths::get_singleton()->get_cache_dir();54const String key_path = cache_path.path_join("html5_server.key");55const String crt_path = cache_path.path_join("html5_server.crt");56bool regen = !FileAccess::exists(key_path) || !FileAccess::exists(crt_path);57if (!regen) {58key = Ref<CryptoKey>(CryptoKey::create());59cert = Ref<X509Certificate>(X509Certificate::create());60if (key->load(key_path) != OK || cert->load(crt_path) != OK) {61regen = true;62}63}64if (regen) {65key = p_crypto->generate_rsa(2048);66key->save(key_path);67cert = p_crypto->generate_self_signed_certificate(key, "CN=godot-debug.local,O=A Game Dev,C=XXA", "20140101000000", "20340101000000");68cert->save(crt_path);69}70}7172void EditorHTTPServer::_send_response() {73Vector<String> psa = String((char *)req_buf).split("\r\n");74int len = psa.size();75ERR_FAIL_COND_MSG(len < 4, "Not enough response headers, got: " + itos(len) + ", expected >= 4.");7677Vector<String> req = psa[0].split(" ", false);78ERR_FAIL_COND_MSG(req.size() < 2, "Invalid protocol or status code.");7980// Wrong protocol81ERR_FAIL_COND_MSG(req[0] != "GET" || req[2] != "HTTP/1.1", "Invalid method or HTTP version.");8283const int query_index = req[1].find_char('?');84const String path = (query_index == -1) ? req[1] : req[1].substr(0, query_index);8586const String req_file = path.get_file();87const String req_ext = path.get_extension();88const String cache_path = EditorPaths::get_singleton()->get_temp_dir().path_join("web");89const String filepath = cache_path.path_join(req_file);9091if (!mimes.has(req_ext) || !FileAccess::exists(filepath)) {92String s = "HTTP/1.1 404 Not Found\r\n";93s += "Connection: Close\r\n";94s += "\r\n";95CharString cs = s.utf8();96peer->put_data((const uint8_t *)cs.get_data(), cs.size() - 1);97return;98}99const String ctype = mimes[req_ext];100101Ref<FileAccess> f = FileAccess::open(filepath, FileAccess::READ);102ERR_FAIL_COND(f.is_null());103String s = "HTTP/1.1 200 OK\r\n";104s += "Connection: Close\r\n";105s += "Content-Type: " + ctype + "\r\n";106s += "Access-Control-Allow-Origin: *\r\n";107s += "Cross-Origin-Opener-Policy: same-origin\r\n";108s += "Cross-Origin-Embedder-Policy: require-corp\r\n";109s += "Cache-Control: no-store, max-age=0\r\n";110s += "\r\n";111CharString cs = s.utf8();112Error err = peer->put_data((const uint8_t *)cs.get_data(), cs.size() - 1);113if (err != OK) {114ERR_FAIL();115}116117while (true) {118uint8_t bytes[4096];119uint64_t read = f->get_buffer(bytes, 4096);120if (read == 0) {121break;122}123err = peer->put_data(bytes, read);124if (err != OK) {125ERR_FAIL();126}127}128}129130void EditorHTTPServer::_poll() {131if (!server->is_listening()) {132return;133}134if (tcp.is_null()) {135if (!server->is_connection_available()) {136return;137}138tcp = server->take_connection();139peer = tcp;140time = OS::get_singleton()->get_ticks_usec();141}142if (OS::get_singleton()->get_ticks_usec() - time > 1000000) {143_clear_client();144return;145}146if (tcp->get_status() != StreamPeerTCP::STATUS_CONNECTED) {147return;148}149150if (use_tls) {151if (tls.is_null()) {152tls = Ref<StreamPeerTLS>(StreamPeerTLS::create());153peer = tls;154if (tls->accept_stream(tcp, TLSOptions::server(key, cert)) != OK) {155_clear_client();156return;157}158}159tls->poll();160if (tls->get_status() == StreamPeerTLS::STATUS_HANDSHAKING) {161// Still handshaking, keep waiting.162return;163}164if (tls->get_status() != StreamPeerTLS::STATUS_CONNECTED) {165_clear_client();166return;167}168}169170while (true) {171char *r = (char *)req_buf;172int l = req_pos - 1;173if (l > 3 && r[l] == '\n' && r[l - 1] == '\r' && r[l - 2] == '\n' && r[l - 3] == '\r') {174_send_response();175_clear_client();176return;177}178179int read = 0;180ERR_FAIL_COND(req_pos >= 4096);181Error err = peer->get_partial_data(&req_buf[req_pos], 1, read);182if (err != OK) {183// Got an error184_clear_client();185return;186} else if (read != 1) {187// Busy, wait next poll188return;189}190req_pos += read;191}192}193194void EditorHTTPServer::stop() {195server_quit.set();196if (server_thread.is_started()) {197server_thread.wait_to_finish();198}199if (server.is_valid()) {200server->stop();201}202_clear_client();203}204205Error EditorHTTPServer::listen(int p_port, IPAddress p_address, bool p_use_tls, String p_tls_key, String p_tls_cert) {206MutexLock lock(server_lock);207if (server->is_listening()) {208return ERR_ALREADY_IN_USE;209}210use_tls = p_use_tls;211if (use_tls) {212Ref<Crypto> crypto = Crypto::create();213if (crypto.is_null()) {214return ERR_UNAVAILABLE;215}216if (!p_tls_key.is_empty() && !p_tls_cert.is_empty()) {217key = Ref<CryptoKey>(CryptoKey::create());218Error err = key->load(p_tls_key);219ERR_FAIL_COND_V(err != OK, err);220cert = Ref<X509Certificate>(X509Certificate::create());221err = cert->load(p_tls_cert);222ERR_FAIL_COND_V(err != OK, err);223} else {224_set_internal_certs(crypto);225}226}227Error err = server->listen(p_port, p_address);228if (err == OK) {229server_quit.clear();230server_thread.start(_server_thread_poll, this);231}232return err;233}234235bool EditorHTTPServer::is_listening() const {236MutexLock lock(server_lock);237return server->is_listening();238}239240EditorHTTPServer::EditorHTTPServer() {241mimes["html"] = "text/html";242mimes["js"] = "application/javascript";243mimes["json"] = "application/json";244mimes["pck"] = "application/octet-stream";245mimes["png"] = "image/png";246mimes["svg"] = "image/svg";247mimes["wasm"] = "application/wasm";248server.instantiate();249stop();250}251252EditorHTTPServer::~EditorHTTPServer() {253stop();254}255256257