From cd8f07aaa78fc488eba3b0e435350e5c2823e53e Mon Sep 17 00:00:00 2001 From: udo Date: Sat, 18 Apr 2026 14:04:58 +0000 Subject: [PATCH] Refactor FastCGI server and compiler, enhance HTTP header parsing, and add WebSocket support - Refactored FastCGIServer class to improve socket handling and added shutdown functionality. - Updated compiler functions to streamline HTML and text literal processing. - Enhanced split_kv function to support uppercase keys and added split_http_headers for better HTTP header parsing. - Introduced new session management functions to validate and handle session IDs. - Added WebSocket frame parsing and handling in the URI module. - Created systemd service scripts for easier deployment and management of the UCE FastCGI runtime. - Added new test cases for header handling, time parsing, and WebSocket functionality. --- README.md | 18 + doc/areas/ob.txt | 4 +- doc/pages/ob_clear.txt | 11 - doc/pages/ob_close.txt | 11 + .../{ob_get_clear.txt => ob_get_close.txt} | 3 +- doc/pages/ob_start.txt | 2 +- etc/uce/settings.cfg | 4 +- scripts/build_linux.sh | 2 +- scripts/systemd/manage-uce-service.sh | 50 ++ scripts/systemd/uce.service | 22 + src/fastcgi/src/fcgicc.cc | 505 +++++++++++------- src/fastcgi/src/fcgicc.h | 56 +- src/lib/compiler.cpp | 59 +- src/lib/compiler2.cpp | 0 src/lib/functionlib.cpp | 86 ++- src/lib/functionlib.h | 8 +- src/lib/sys.cpp | 38 +- src/lib/types.cpp | 2 +- src/lib/types.h | 34 +- src/lib/uri.cpp | 162 +++++- src/lib/uri.h | 66 +++ src/linux_fastcgi.cpp | 37 +- test/header.uce | 25 + test/index.uce | 3 + test/parse_time.uce | 30 ++ test/websockets.ws.uce | 42 ++ 26 files changed, 913 insertions(+), 367 deletions(-) delete mode 100644 doc/pages/ob_clear.txt create mode 100644 doc/pages/ob_close.txt rename doc/pages/{ob_get_clear.txt => ob_get_close.txt} (65%) create mode 100755 scripts/systemd/manage-uce-service.sh create mode 100644 scripts/systemd/uce.service create mode 100644 src/lib/compiler2.cpp create mode 100644 test/header.uce create mode 100644 test/parse_time.uce create mode 100644 test/websockets.ws.uce diff --git a/README.md b/README.md index f7c755f..6c988d8 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,24 @@ This is in the early stages of development. Don't use this for anything importan The aim of this project is to make a PHP-like runtime that enables server-side "scripting" using C/C++. At the core is a multi-worker FastCGI server that can be talked to from Nginx or similar front-end servers. UCE has a shared-nothing isolated architecture to serve page requests. To minimize the potential for memory leaks, UCE uses a per-request memory arena. UCE also provides a PHP-like API to ease web development. Advanced features such as an integrated WebSockets broker are planned. UCE aims to use minimal dependencies (at the moment, the only dependency is the Clang compiler). UCE pages are automatically recompiled and dynamically reloaded as necessary. +## Service Setup + +The repository now includes a systemd unit template and a management script for the FastCGI runtime: + +- `scripts/systemd/uce.service` +- `scripts/systemd/manage-uce-service.sh` + +Typical usage on the deployment host: + +```bash +scripts/systemd/manage-uce-service.sh setup +scripts/systemd/manage-uce-service.sh status +scripts/systemd/manage-uce-service.sh restart +scripts/systemd/manage-uce-service.sh logs 200 +``` + +The service runs the runtime from the repository root so `COMPILER_SYS_PATH` resolves correctly and nginx can forward `.uce` requests to the Unix socket defined in `/etc/uce/settings.cfg`. + # API Memcache Functions diff --git a/doc/areas/ob.txt b/doc/areas/ob.txt index 412319a..fea6ddc 100644 --- a/doc/areas/ob.txt +++ b/doc/areas/ob.txt @@ -2,9 +2,9 @@ Output / Invocation Functions call_file load -ob_clear +ob_close ob_get -ob_get_clear +ob_get_close ob_start print render_file diff --git a/doc/pages/ob_clear.txt b/doc/pages/ob_clear.txt deleted file mode 100644 index 02d4c02..0000000 --- a/doc/pages/ob_clear.txt +++ /dev/null @@ -1,11 +0,0 @@ -:sig -void ob_clear() - -:params -(none) - -:desc -Discard the current output buffer. - -:see ->ob diff --git a/doc/pages/ob_close.txt b/doc/pages/ob_close.txt new file mode 100644 index 0000000..b2fca80 --- /dev/null +++ b/doc/pages/ob_close.txt @@ -0,0 +1,11 @@ +:sig +void ob_close() + +:params +(none) + +:desc +Discard the current output buffer. If are more output buffers on the stack, switch to the next one. + +:see +>ob diff --git a/doc/pages/ob_get_clear.txt b/doc/pages/ob_get_close.txt similarity index 65% rename from doc/pages/ob_get_clear.txt rename to doc/pages/ob_get_close.txt index 124080d..9667f3d 100644 --- a/doc/pages/ob_get_clear.txt +++ b/doc/pages/ob_get_close.txt @@ -1,11 +1,12 @@ :sig -String ob_get_clear() +String ob_get_close() :params return value : content of the current output buffer :desc Returns the contents of the current output buffer and then discards the buffer. +If are more output buffers on the stack, switch to the next one. :see >ob diff --git a/doc/pages/ob_start.txt b/doc/pages/ob_start.txt index 14e3273..6e5138d 100644 --- a/doc/pages/ob_start.txt +++ b/doc/pages/ob_start.txt @@ -5,7 +5,7 @@ void ob_start() (none) :desc -Starts a new output buffer. All subsequent output will be directed into this buffer. +Starts a new output buffer. All subsequent output will be directed into this buffer. Every call to ob_start() starts a new buffer and puts that buffer on the output buffer stack. ob_close() and ob_get_close() destroy buffers and remove them from the stack. :see >ob diff --git a/etc/uce/settings.cfg b/etc/uce/settings.cfg index 8a4535b..4422746 100644 --- a/etc/uce/settings.cfg +++ b/etc/uce/settings.cfg @@ -4,8 +4,8 @@ TMP_UPLOAD_PATH=/tmp/uce/uploads SESSION_PATH=/tmp/uce/sessions # LISTEN ON SOCKETS -SOCKET_PATH=/run/uce.sock -LISTEN_PORT=9993 +FCGI_SOCKET_PATH=/run/uce.sock +FCGI_PORT=9993 # PRECOMPILE .uce FILES PRECOMPILE_FILES_IN= diff --git a/scripts/build_linux.sh b/scripts/build_linux.sh index 95c0c6d..8498a10 100755 --- a/scripts/build_linux.sh +++ b/scripts/build_linux.sh @@ -3,7 +3,7 @@ cd "$(dirname "$0")" cd .. BUILDMODE=${2:-"debug"} -OPT_FLAG="O3" +OPT_FLAG="O0" GF="uce_fastcgi" mkdir bin > /dev/null 2>&1 diff --git a/scripts/systemd/manage-uce-service.sh b/scripts/systemd/manage-uce-service.sh new file mode 100755 index 0000000..3badab6 --- /dev/null +++ b/scripts/systemd/manage-uce-service.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [[ ${EUID:-0} -ne 0 ]]; then + echo "This script must run as root." >&2 + exit 1 +fi + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +UNIT_NAME="uce.service" +UNIT_SOURCE="$SCRIPT_DIR/uce.service" +UNIT_DEST="/etc/systemd/system/$UNIT_NAME" +CONFIG_SOURCE="$REPO_ROOT/etc/uce/settings.cfg" +CONFIG_DEST="/etc/uce/settings.cfg" + +action="${1:-setup}" + +install_unit() { + install -D -m 0644 "$UNIT_SOURCE" "$UNIT_DEST" + if [[ ! -f "$CONFIG_DEST" ]]; then + install -D -m 0644 "$CONFIG_SOURCE" "$CONFIG_DEST" + fi + systemctl daemon-reload +} + +case "$action" in + install) + install_unit + ;; + setup) + install_unit + systemctl enable --now "$UNIT_NAME" + ;; + enable) + install_unit + systemctl enable "$UNIT_NAME" + ;; + start|stop|restart|status) + systemctl "$action" "$UNIT_NAME" + ;; + logs) + lines="${2:-100}" + journalctl -u "$UNIT_NAME" -n "$lines" --no-pager + ;; + *) + echo "Usage: $0 [install|setup|enable|start|stop|restart|status|logs [lines]]" >&2 + exit 1 + ;; +esac diff --git a/scripts/systemd/uce.service b/scripts/systemd/uce.service new file mode 100644 index 0000000..59a91d2 --- /dev/null +++ b/scripts/systemd/uce.service @@ -0,0 +1,22 @@ +[Unit] +Description=UCE FastCGI Runtime +After=network-online.target mariadb.service memcached.service +Wants=network-online.target + +[Service] +Type=simple +WorkingDirectory=/Code/uce.openfu.com/uce +ExecStartPre=/usr/bin/mkdir -p /tmp/uce/work /tmp/uce/uploads /tmp/uce/sessions +ExecStartPre=/usr/bin/rm -f /run/uce.sock +ExecStartPre=/usr/bin/bash /Code/uce.openfu.com/uce/scripts/build_linux.sh +ExecStart=/Code/uce.openfu.com/uce/bin/uce_fastcgi.linux.bin +ExecStopPost=/usr/bin/rm -f /run/uce.sock +Restart=always +RestartSec=2 +TimeoutStopSec=15 +KillMode=mixed +StandardOutput=journal +StandardError=journal + +[Install] +WantedBy=multi-user.target diff --git a/src/fastcgi/src/fcgicc.cc b/src/fastcgi/src/fcgicc.cc index 357c10c..3df231e 100644 --- a/src/fastcgi/src/fcgicc.cc +++ b/src/fastcgi/src/fcgicc.cc @@ -47,138 +47,150 @@ #include "../fastcgi_devkit/fastcgi.h" - -FastCGIServer::RequestInfo::RequestInfo() : - params_closed(false), - in_closed(false), - output_closed(false) +void +FastCGIServer::shutdown() { -} + if(getpid() != parent_pid) // if we're a child process, we must not close the handles + return; + for (std::vector::iterator it = server_sockets.begin(); + it != server_sockets.end(); ++it) + { + printf("Closing server socket %i\n", *it); + close(*it); + sleep(1); + } + for (std::vector::iterator it = listen_unlink.begin(); + it != listen_unlink.end(); ++it) + unlink(it->c_str()); -FastCGIServer::Connection::Connection() : - close_responsibility(false), - close_socket(false) -{ + for (std::map::iterator it = client_sockets.begin(); + it != client_sockets.end(); ++it) + { + close(it->first); + for (RequestList::iterator req_it = it->second->requests.begin(); + req_it != it->second->requests.end(); ++req_it) + delete req_it->second; + delete it->second; + } -} - -FastCGIServer::FastCGIServer() -{ + sleep(1); + server_sockets.clear(); } FastCGIServer::~FastCGIServer() { - - if(my_pid != parent_pid) // if we're a child process, we must not close the handles - return; - - for (std::vector::iterator it = listen_sockets.begin(); - it != listen_sockets.end(); ++it) - close(*it); - - for (std::vector::iterator it = listen_unlink.begin(); - it != listen_unlink.end(); ++it) - unlink(it->c_str()); - - for (std::map::iterator it = read_sockets.begin(); - it != read_sockets.end(); ++it) { - close(it->first); - for (RequestList::iterator req_it = it->second->requests.begin(); - req_it != it->second->requests.end(); ++req_it) - delete req_it->second; - delete it->second; - } - + shutdown(); } +int +FastCGIServer::listen_http(unsigned tcp_port) +{ + int server_socket = listen(tcp_port); + server_socket_types[server_socket] = 'H'; + return server_socket; +} -void +int FastCGIServer::listen(unsigned tcp_port) { - int listen_socket = socket(PF_INET, SOCK_STREAM, 0); - if (listen_socket == -1) - throw std::runtime_error("socket() failed"); + int server_socket = socket(PF_INET, SOCK_STREAM, 0); + if (server_socket == -1) + throw std::runtime_error("socket() failed"); try { - struct sockaddr_in sa; - bzero(&sa, sizeof(sa)); - sa.sin_family = AF_INET; - sa.sin_port = htons(tcp_port); - sa.sin_addr.s_addr = htonl(INADDR_ANY); - if (bind(listen_socket, (struct sockaddr*)&sa, sizeof(sa)) == -1) - throw std::runtime_error("bind() failed"); - if (::listen(listen_socket, 100)) - throw std::runtime_error("listen() failed"); + int iSetOption = 1; + setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, (char*)&iSetOption, + sizeof(iSetOption)); - listen_sockets.push_back(listen_socket); + struct sockaddr_in sa; + bzero(&sa, sizeof(sa)); + sa.sin_family = AF_INET; + sa.sin_port = htons(tcp_port); + sa.sin_addr.s_addr = htonl(INADDR_ANY); + if (bind(server_socket, (struct sockaddr*)&sa, sizeof(sa)) == -1) + throw std::runtime_error("bind() failed"); + if (::listen(server_socket, 100)) + throw std::runtime_error("listen() failed"); + + server_sockets.push_back(server_socket); } catch (...) { - close(listen_socket); - throw; + close(server_socket); + throw; } + + server_socket_types[server_socket] = 'F'; + printf("(P) listening to #%i port %i\n", server_socket, tcp_port); + return server_socket; } - -void +int FastCGIServer::listen(const std::string& local_path) { - int listen_socket = socket(PF_UNIX, SOCK_STREAM, 0); - if (listen_socket == -1) + int server_socket = socket(PF_UNIX, SOCK_STREAM, 0); + if (server_socket == -1) throw std::runtime_error("socket() failed"); try { - struct sockaddr_un sa; - bzero(&sa, sizeof(sa)); - sa.sun_family = AF_LOCAL; + struct sockaddr_un sa; + bzero(&sa, sizeof(sa)); + sa.sun_family = AF_LOCAL; - std::string::size_type size = local_path.size(); - if (size >= sizeof(sa.sun_path)) - throw std::runtime_error("path too long"); - if (local_path.find_first_of('\0') != std::string::npos) - throw std::runtime_error("null character in path"); + std::string::size_type size = local_path.size(); + if (size >= sizeof(sa.sun_path)) + throw std::runtime_error("path too long"); + if (local_path.find_first_of('\0') != std::string::npos) + throw std::runtime_error("null character in path"); - std::memcpy(sa.sun_path, local_path.data(), size); + std::memcpy(sa.sun_path, local_path.data(), size); - unlink(local_path.c_str()); - try { - if (bind(listen_socket, (struct sockaddr*)&sa, - sizeof(sa) - (sizeof(sa.sun_path) - size - 1)) == -1) - throw std::runtime_error("bind() failed"); + unlink(local_path.c_str()); + try { + if (bind(server_socket, (struct sockaddr*)&sa, + sizeof(sa) - (sizeof(sa.sun_path) - size - 1)) == -1) + throw std::runtime_error("bind() failed"); - if (::listen(listen_socket, 100)) - throw std::runtime_error("listen() failed"); + if (::listen(server_socket, 100)) + throw std::runtime_error("listen() failed"); - listen_sockets.push_back(listen_socket); - listen_unlink.push_back(local_path); + server_sockets.push_back(server_socket); + listen_unlink.push_back(local_path); + + printf("(P) listening to #%i socket %s\n", server_socket, local_path.c_str()); + + } catch (...) { + unlink(local_path.c_str()); + throw; + } } catch (...) { - unlink(local_path.c_str()); - throw; - } - - } catch (...) { - close(listen_socket); - throw; + close(server_socket); + throw; } + return server_socket; } - -void -FastCGIServer::abandon_files() +int +FastCGIServer::send_output_buffer(Connection& con) { - listen_unlink.clear(); + int write_result = write(con.client_socket, + con.output_buffer.data(), + con.output_buffer.size()); + if (write_result == -1) + throw std::runtime_error("write() failed"); + con.output_buffer.erase(0, write_result); + return write_result; } - void FastCGIServer::process(int timeout_ms) { - char buffer[4096]; + char buffer[64*1024]; fd_set fs_read; fd_set fs_write; int nfd = 0; @@ -187,16 +199,16 @@ FastCGIServer::process(int timeout_ms) FD_ZERO(&fs_read); FD_ZERO(&fs_write); - for(auto socket_handle : listen_sockets) + for(auto socket_handle : server_sockets) { FD_SET(socket_handle, &fs_read); nfd = std::max(nfd, socket_handle); } - for(auto con : read_sockets) + for(auto con : client_sockets) { FD_SET(con.first, &fs_read); - if (!con.second->output_buffer.empty()) + if (!con.second->output_buffer.empty() || con.second->close_socket) FD_SET(con.first, &fs_write); nfd = std::max(nfd, con.first); } @@ -209,18 +221,29 @@ FastCGIServer::process(int timeout_ms) else throw std::runtime_error("select() failed"); - for(auto socket_handle : listen_sockets) + for(auto socket_handle : server_sockets) if (FD_ISSET(socket_handle, &fs_read)) { - int posix_con = accept(socket_handle, NULL, NULL); - if (posix_con == -1) + int client_socket = accept(socket_handle, NULL, NULL); + if (client_socket == -1) throw std::runtime_error("accept() failed"); - read_sockets[posix_con] = new Connection(); - read_sockets[posix_con]->posix_con = posix_con; + printf("Opening socket %i\n", client_socket); + client_sockets[client_socket] = new Connection(); + client_sockets[client_socket]->client_socket = client_socket; + client_sockets[client_socket]->server_socket = socket_handle; + client_sockets[client_socket]->type = server_socket_types[socket_handle]; + if(client_sockets[client_socket]->type == 'H') + { + FastCGIRequest* new_request = new FastCGIRequest(); + new_request->resources.client_socket = client_socket; + new_request->resources.server_socket = socket_handle; + new_request->stats.time_init = microtime(); + client_sockets[client_socket]->requests[client_socket] = new_request; + } } - for (std::map::iterator it = read_sockets.begin(); - it != read_sockets.end();) + for (std::map::iterator it = client_sockets.begin(); + it != client_sockets.end();) { int read_socket = it->first; @@ -229,39 +252,62 @@ FastCGIServer::process(int timeout_ms) int read_result = read(read_socket, buffer, sizeof(buffer)); if (read_result == -1) if (errno == ECONNRESET) - goto close_socket; + goto close_socket; else - throw std::runtime_error("read() on socket failed"); - if (read_result == 0) - it->second->close_socket = true; - else { + throw std::runtime_error("read() on socket failed"); + if (read_result == 0) + { + if(it->second->type == 'H' && it->second->input_buffer != "") + { + process_http_request( + *client_sockets[it->second->client_socket]->requests[it->second->client_socket], + it->second->input_buffer); + if(it->second->close_socket || !it->second->output_buffer.empty()) + it->second->input_buffer = ""; + else + it->second->close_socket = true; + } + else + { + it->second->close_socket = true; + } + } + else + { it->second->input_buffer.append(buffer, read_result); - process_connection_read(*it->second); + if(it->second->type == 'H') + { + process_http_request( + *client_sockets[it->second->client_socket]->requests[it->second->client_socket], + it->second->input_buffer); + if(it->second->close_socket || !it->second->output_buffer.empty()) + it->second->input_buffer = ""; + } + else + { + read_fgci(*it->second); + } } } if (!it->second->output_buffer.empty() && FD_ISSET(read_socket, &fs_write)) { - process_connection_write(*it->second); - int write_result = write(read_socket, - it->second->output_buffer.data(), - it->second->output_buffer.size()); - if (write_result == -1) - throw std::runtime_error("write() failed"); - it->second->output_buffer.erase(0, write_result); + write_fgci(*it->second); + send_output_buffer(*it->second); } if (it->second->close_socket && it->second->output_buffer.empty()) { close_socket: + printf("Closing socket %i\n", it->first); int close_result = close(it->first); if (close_result == -1 && errno != ECONNRESET) throw std::runtime_error("close() failed"); Connection* connection = it->second; - read_sockets.erase(it++); + client_sockets.erase(it++); delete connection; - if(calls_until_termination != -1 && read_sockets.size() == 0) + if(calls_until_termination != -1 && client_sockets.size() == 0) { calls_until_termination -= 1; if(calls_until_termination <= 0) @@ -272,6 +318,54 @@ FastCGIServer::process(int timeout_ms) } } +void +FastCGIServer::process_http_request(FastCGIRequest& request, String& data) +{ + auto header_end = data.find("\r\n\r\n"); + if(header_end == String::npos) + return; + + if(request.params.size() == 0) + { + request.params = split_http_headers(data.substr(0, header_end)); + request.flags.params_closed = true; + if(request.params["HTTP_SCRIPT_FILENAME"] != "") + request.params["SCRIPT_FILENAME"] = request.params["HTTP_SCRIPT_FILENAME"]; + else if(request.params["SCRIPT_FILENAME"] == "" && request.params["DOCUMENT_URI"] != "") + { + String document_root = first(request.params["DOCUMENT_ROOT"], get_cwd()); + if(document_root.length() > 1 && document_root[document_root.length()-1] == '/') + document_root.resize(document_root.length()-1); + request.params["DOCUMENT_ROOT"] = document_root; + request.params["SCRIPT_FILENAME"] = document_root + request.params["DOCUMENT_URI"]; + } + } + + request.in = data.substr(header_end + 4); + u64 content_length = int_val(first(request.params["CONTENT_LENGTH"], "0")); + if(request.in.length() < content_length) + return; + request.flags.input_closed = true; + + if(request.params["HTTP_UPGRADE"] == "websocket") + { + printf("(i) got upgrade request %s\n", var_dump(request.params).c_str()); + client_sockets[request.resources.client_socket]->close_socket = true; + } + else + { + on_complete(request); + + assemble_output_buffer( + request, + client_sockets[request.resources.client_socket]); + + printf("data written: %i bytes\n", + client_sockets[request.resources.client_socket]->output_buffer.size()); + + client_sockets[request.resources.client_socket]->close_socket = true; + } +} void FastCGIServer::process_forever() @@ -280,18 +374,8 @@ FastCGIServer::process_forever() process(); } -int -FastCGIServer::call_completion_handler(FastCGIRequest& request) -{ - //printf("- request complete\n"); - ////switch_to_arena(request.mem); - auto result = on_complete(request); - ////switch_to_system_alloc(); - return(result); -} - void -FastCGIServer::process_connection_read(Connection& connection) +FastCGIServer::read_fgci(Connection& connection) { std::string::size_type n = 0; while (connection.input_buffer.size() - n >= FCGI_HEADER_LEN) { @@ -316,7 +400,7 @@ FastCGIServer::process_connection_read(Connection& connection) { case FCGI_GET_VALUES: { - Pairs pairs = parse_pairs(content, content_length); + Pairs pairs = parse_pairs_fcgi(content, content_length); std::string::size_type base = connection.output_buffer.size(); connection.output_buffer.push_back(FCGI_VERSION_1); @@ -325,13 +409,13 @@ FastCGIServer::process_connection_read(Connection& connection) for (Pairs::iterator it = pairs.begin(); it != pairs.end(); ++it) if (it->first == FCGI_MAX_CONNS) - write_pair(connection.output_buffer, + write_pair_fcgi(connection.output_buffer, it->first, std::string("100")); else if (it->first == FCGI_MAX_REQS) - write_pair(connection.output_buffer, + write_pair_fcgi(connection.output_buffer, it->first, std::string("1000")); else if (it->first == FCGI_MPXS_CONNS) - write_pair(connection.output_buffer, + write_pair_fcgi(connection.output_buffer, it->first, std::string("1")); std::string::size_type len = connection.output_buffer.size() - base; @@ -361,7 +445,7 @@ FastCGIServer::process_connection_read(Connection& connection) connection.output_buffer.append( reinterpret_cast(&unknown), sizeof(unknown)); if (connection.close_responsibility) - connection.close_socket = true; + connection.close_socket = true; break; } @@ -382,12 +466,10 @@ FastCGIServer::process_connection_read(Connection& connection) printf("(!) %i requests in flight at the same time!\n", connection.requests.size()); } - //switch_to_arena(request_arena); - RequestInfo* new_request = new RequestInfo(); - new_request->resources.fcgi_socket = connection.posix_con; - //new_request->mem = request_arena; + FastCGIRequest* new_request = new FastCGIRequest(); + new_request->resources.client_socket = connection.client_socket; + new_request->resources.server_socket = connection.server_socket; new_request->stats.time_init = microtime(); - //switch_to_system_alloc(); connection.requests[request_id] = new_request; break; @@ -420,25 +502,26 @@ FastCGIServer::process_connection_read(Connection& connection) if (it == connection.requests.end()) break; - RequestInfo& request = *it->second; + FastCGIRequest& request = *it->second; //switch_to_arena(it->second->mem); - if (!request.params_closed) + if (!request.flags.params_closed) if (content_length != 0) - request.params_buffer.append(content, content_length); + request.resources.params_buffer.append(content, content_length); else { - request.params = parse_pairs(request.params_buffer.data(), - request.params_buffer.size()); - request.params_buffer.clear(); - request.params_closed = true; - - request.status = on_request(request); - if (request.status == 0 && !request.in.empty()) - { - request.status = on_data(request); - if (request.status == 0 && request.in_closed) - request.status = call_completion_handler(request); - } - process_write_request(connection, request_id, request); + request.params = parse_pairs_fcgi( + request.resources.params_buffer.data(), + request.resources.params_buffer.size()); + request.resources.params_buffer.clear(); + request.flags.params_closed = true; + //std::cout << "Params " << var_dump(request.params) << "\n"; + request.flags.status = on_request(request); + if (request.flags.status == 0 && !request.in.empty()) + { + request.flags.status = on_data(request); + if (request.flags.status == 0 && request.flags.input_closed) + request.flags.status = on_complete(request); + } + request_write_fgci(connection, request_id, request); } //switch_to_system_alloc(); break; @@ -449,22 +532,22 @@ FastCGIServer::process_connection_read(Connection& connection) if (it == connection.requests.end()) break; - RequestInfo& request = *it->second; + FastCGIRequest& request = *it->second; //switch_to_arena(it->second->mem); - if (!request.in_closed) + if (!request.flags.input_closed) if (content_length != 0) { - request.in.append(content, content_length); - if (request.params_closed && request.status == 0) - { - request.status = on_data(request); - process_write_request(connection, request_id, request); - } + request.in.append(content, content_length); + if (request.flags.params_closed && request.flags.status == 0) + { + request.flags.status = on_data(request); + request_write_fgci(connection, request_id, request); + } } else { - request.in_closed = true; - if (request.params_closed && request.status == 0) { - request.status = call_completion_handler(request); - process_write_request(connection, request_id, request); - } + request.flags.input_closed = true; + if (request.flags.params_closed && request.flags.status == 0) { + request.flags.status = on_complete(request); + request_write_fgci(connection, request_id, request); + } } //switch_to_system_alloc(); break; @@ -490,56 +573,70 @@ FastCGIServer::process_connection_read(Connection& connection) connection.input_buffer.erase(0, n); } +void +FastCGIServer::assemble_output_buffer(FastCGIRequest& request, Connection* connection) +{ + request.out = + request.response_code+"\r\n"+ + var_dump(request.header, "", "\r\n") + + var_dump(request.set_cookies, "", "\r\n") + + "\r\n"; + + for(auto obs : request.ob_stack) + { + request.out += obs->str(); + delete obs; + } + request.ob_stack.clear(); + request.flags.output_closed = true; + request.stats.time_end = microtime(); + if(request.flags.log_request) + printf("(r) pid:%i\t%s\t%0.6fs\tfps:%0.0f\tout:%0.1fkB\tmem:%0.0f/%0.0fkB\n", + my_pid, + request.params["REQUEST_URI"].c_str(), + request.stats.time_end - request.stats.time_start, + 1.0 / (request.stats.time_end - request.stats.time_start), + (f32)(request.out.length()/1024), + (f32)(request.stats.mem_high/1024), + (f32)(request.stats.mem_alloc/1024) + ); + + if(connection) + { + connection->output_buffer.clear(); + connection->output_buffer.append(request.out); + request.out.clear(); + } + +} void -FastCGIServer::process_write_request(Connection& connection, RequestID id, - RequestInfo& request) +FastCGIServer::request_write_fgci(Connection& connection, RequestID id, + FastCGIRequest& request) { if (!request.out.empty()) { - write_data(connection.output_buffer, id, request.out, FCGI_STDOUT); + write_data_fcgi(connection.output_buffer, id, request.out, FCGI_STDOUT); //switch_to_arena(request.mem); request.out.clear(); //switch_to_system_alloc(); } if (!request.err.empty()) { - write_data(connection.output_buffer, id, request.err, FCGI_STDERR); + write_data_fcgi(connection.output_buffer, id, request.err, FCGI_STDERR); //switch_to_arena(request.mem); request.err.clear(); //switch_to_system_alloc(); } - if ((request.in_closed || request.status != 0) && - !request.output_closed) + if ((request.flags.input_closed || request.flags.status != 0) && + !request.flags.output_closed) { //switch_to_arena(request.mem); - request.out = - var_dump(request.header, "", "\r\n") + - var_dump(request.set_cookies, "", "\r\n") + - "\r\n"; - - for(auto obs : request.ob_stack) - { - request.out += obs->str(); - delete obs; - } - request.ob_stack.clear(); + assemble_output_buffer(request); //switch_to_system_alloc(); - write_data(connection.output_buffer, id, request.out, FCGI_STDOUT); - write_data(connection.output_buffer, id, request.err, FCGI_STDERR); - - request.stats.time_end = microtime(); - if(request.flags.log_request) - printf("(r) pid:%i\t%s\t%0.6fs\tfps:%0.0f\tout:%0.1fkB\tmem:%0.0f/%0.0fkB\n", - my_pid, - request.params["REQUEST_URI"].c_str(), - request.stats.time_end - request.stats.time_start, - 1.0 / (request.stats.time_end - request.stats.time_start), - (f32)(request.out.length()/1024), - (f32)(request.stats.mem_high/1024), - (f32)(request.stats.mem_alloc/1024) - ); + write_data_fcgi(connection.output_buffer, id, request.out, FCGI_STDOUT); + write_data_fcgi(connection.output_buffer, id, request.err, FCGI_STDERR); FCGI_EndRequestRecord complete; bzero(&complete, sizeof(complete)); @@ -548,17 +645,17 @@ FastCGIServer::process_write_request(Connection& connection, RequestID id, complete.header.requestIdB1 = (id >> 8) & 0xff; complete.header.requestIdB0 = id & 0xff; complete.header.contentLengthB0 = sizeof(complete.body); - complete.body.appStatusB3 = (request.status >> 24) & 0xff; - complete.body.appStatusB2 = (request.status >> 16) & 0xff; - complete.body.appStatusB1 = (request.status >> 8) & 0xff; - complete.body.appStatusB0 = request.status & 0xff; + complete.body.appStatusB3 = (request.flags.status >> 24) & 0xff; + complete.body.appStatusB2 = (request.flags.status >> 16) & 0xff; + complete.body.appStatusB1 = (request.flags.status >> 8) & 0xff; + complete.body.appStatusB0 = request.flags.status & 0xff; complete.body.protocolStatus = FCGI_REQUEST_COMPLETE; connection.output_buffer.append( reinterpret_cast(&complete), sizeof(complete)); if (connection.close_responsibility) connection.close_socket = true; - request.output_closed = true; + //printf("- output done\n"); } //switch_to_system_alloc(); @@ -566,16 +663,16 @@ FastCGIServer::process_write_request(Connection& connection, RequestID id, void -FastCGIServer::process_connection_write(Connection& connection) +FastCGIServer::write_fgci(Connection& connection) { for (RequestList::iterator it = connection.requests.begin(); it != connection.requests.end();) { - process_write_request(connection, it->first, *it->second); - if (it->second->params_closed && it->second->in_closed) + request_write_fgci(connection, it->first, *it->second); + if (it->second->flags.params_closed && it->second->flags.input_closed) { //switch_to_arena(it->second->mem); - //printf("- process_connection_write close\n"); + //printf("- write_fgci close\n"); delete it->second; //switch_to_system_alloc(); connection.requests.erase(it++); @@ -587,7 +684,7 @@ FastCGIServer::process_connection_write(Connection& connection) FastCGIServer::Pairs -FastCGIServer::parse_pairs(const char* data, std::string::size_type n) +FastCGIServer::parse_pairs_fcgi(const char* data, std::string::size_type n) { Pairs pairs; @@ -633,7 +730,7 @@ FastCGIServer::parse_pairs(const char* data, std::string::size_type n) void -FastCGIServer::write_pair(std::string& buffer, +FastCGIServer::write_pair_fcgi(std::string& buffer, const std::string& key, const std::string& value) { if (key.size() > 0x7f) { @@ -658,7 +755,7 @@ FastCGIServer::write_pair(std::string& buffer, void -FastCGIServer::write_data(std::string& buffer, RequestID id, +FastCGIServer::write_data_fcgi(std::string& buffer, RequestID id, const std::string& input, unsigned char type) { FCGI_Header header; diff --git a/src/fastcgi/src/fcgicc.h b/src/fastcgi/src/fcgicc.h index 4b592f7..b8eb867 100644 --- a/src/fastcgi/src/fcgicc.h +++ b/src/fastcgi/src/fcgicc.h @@ -41,65 +41,55 @@ class FastCGIServer { public: - FastCGIServer(); ~FastCGIServer(); + void shutdown(); // called when the parameters and standard input have been receieved std::function on_request = 0; std::function on_data = 0; std::function on_complete = 0; - void listen(unsigned tcp_port); - void listen(const std::string& local_path); - void abandon_files(); + int listen(unsigned tcp_port); + int listen_http(unsigned tcp_port); + int listen(const std::string& local_path); void process(int timeout_ms = -1); // timeout_ms<0 blocks forever void process_forever(); int calls_until_termination = 8; // set this to -1 to never terminate - int call_completion_handler(FastCGIRequest& request); - -protected: - struct RequestInfo : FastCGIRequest { - RequestInfo(); - - std::string params_buffer; - bool params_closed; - bool in_closed; - int status; - bool output_closed; - - friend class FastCGIServer; - }; - typedef unsigned RequestID; - typedef std::map RequestList; - struct Connection { - Connection(); + typedef std::map RequestList; + struct Connection { RequestList requests; - u64 posix_con = 0; + u64 client_socket = 0; + u64 server_socket = 0; std::string input_buffer; std::string output_buffer; - bool close_responsibility; - bool close_socket; + bool close_responsibility = false; + bool close_socket = false; + char type = 'F'; // F = FastCGI, H = HttpServer }; typedef StringMap Pairs; - std::vector listen_sockets; + std::vector server_sockets; std::vector listen_unlink; - std::map read_sockets; + std::map server_socket_types; + std::map client_sockets; - void process_connection_read(Connection&); - static void process_write_request(Connection&, RequestID, RequestInfo&); - static void process_connection_write(Connection&); - static Pairs parse_pairs(const char*, std::string::size_type); - static void write_pair(std::string& buffer, + void read_fgci(Connection&); + static void request_write_fgci(Connection&, RequestID, FastCGIRequest&); + static void write_fgci(Connection&); + static Pairs parse_pairs_fcgi(const char*, std::string::size_type); + static void write_pair_fcgi(std::string& buffer, const std::string& key, const std::string&); - static void write_data(std::string& buffer, RequestID id, + static void write_data_fcgi(std::string& buffer, RequestID id, const std::string& input, unsigned char type); + static void assemble_output_buffer(FastCGIRequest& request, Connection* connection = 0); + int send_output_buffer(Connection& con); + void process_http_request(FastCGIRequest& request, String& data); }; diff --git a/src/lib/compiler.cpp b/src/lib/compiler.cpp index 7684a7d..df7de95 100644 --- a/src/lib/compiler.cpp +++ b/src/lib/compiler.cpp @@ -1,7 +1,7 @@ #include "compiler.h" #include -String process_html_literal(Request* context, SharedUnit* su, String content) +String process_text_literal(Request* context, SharedUnit* su, String content) { String pc; String HT_START = "print(R\"("; @@ -89,7 +89,12 @@ String process_html_literal(Request* context, SharedUnit* su, String content) String preprocess_shared_unit_char_wise(Request* context, SharedUnit* su, String content) { - String pc = "#include \"" + su->src_file_name + ".setup.h" + "\"\n#line 1\n"; + String pc = + ("#include \"")+context->server->config["COMPILER_SYS_PATH"] +"/src/lib/uce_lib.h\" \n"+ + file_get_contents( + context->server->config["COMPILER_SYS_PATH"] + "/" + context->server->config["SETUP_TEMPLATE"] + )+ + "#line 1\n"; String token = ""; String html_buffer = ""; u8 mode = 0; @@ -100,29 +105,13 @@ String preprocess_shared_unit_char_wise(Request* context, SharedUnit* su, String { char c = content[i]; current_line.append(1, c); - if(mode == 2) + if(mode == 1) { - auto end_pos = content.find(String("", i); - if(end_pos != String::npos) + if(c == '<' && (content[i+1] == '/') && (content[i+2] == '>')) { - u32 len = token.length() + 3 + end_pos - i; - html_buffer.append(content.substr(i, len - (token.length() == 0 ? 3 : 0))); - i += len - 1; - pc.append(process_html_literal(context, su, html_buffer)); - } - else - { - printf("(!) unterminated HTML literal <%s> in %s", token.c_str(), su->file_name.c_str()); - } - mode = 0; - } - else if(mode == 1) - { - if(isspace(c) || c == '>') - { - mode = 2; - if(token.length() > 0) - html_buffer.append(1, c); + i += 2; + pc.append(process_text_literal(context, su, html_buffer)); + mode = 0; } else { @@ -130,13 +119,12 @@ String preprocess_shared_unit_char_wise(Request* context, SharedUnit* su, String html_buffer.append(1, c); } } - else if(!inside_quote && c == '<' && (content[i+1] == '>'/* || isalpha(content[i+1])*/)) + else if(!inside_quote && c == '<' && (content[i+1] == '>')) { mode = 1; token = ""; html_buffer = ""; - if(content[i+1] != '>') - html_buffer.append(1, c); + i += 1; } else if(c == '\"') { @@ -206,7 +194,7 @@ void setup_unit_paths(Request* context, SharedUnit* su, String file_name) su->so_name = su->bin_path + "/" + su->bin_file_name; su->api_file_name = su->bin_path + "/" + su->src_file_name + ".exports.txt"; - su->setup_file_name = su->bin_path + "/" + su->src_file_name + ".setup.h"; + //su->setup_file_name = su->bin_path + "/" + su->src_file_name + ".setup.h"; } void load_shared_unit(Request* context, SharedUnit* su, String file_name) @@ -245,7 +233,7 @@ void load_shared_unit(Request* context, SharedUnit* su, String file_name) } } -String compile_setup_file(Request* context, SharedUnit* su) +/*String compile_setup_file(Request* context, SharedUnit* su) { String result = String("#ifndef UCE_LIB_INCLUDED\n") + @@ -254,17 +242,13 @@ String compile_setup_file(Request* context, SharedUnit* su) file_get_contents( context->server->config["COMPILER_SYS_PATH"] + "/" + context->server->config["SETUP_TEMPLATE"]) + "#endif \n"; - StringList declarations; - StringList load_units; - - result = replace(result, "/*load_declarations*/", join(declarations, "\n")); - result = replace(result, "/*load_units*/", join(load_units, "\n")); return(result); -} +}*/ void compile_shared_unit(Request* context, SharedUnit* su, String file_name) { //setup_unit_paths(context, su, file_name); + f64 comp_start = microtime(); if(!file_exists(su->file_name)) { @@ -274,7 +258,7 @@ void compile_shared_unit(Request* context, SharedUnit* su, String file_name) shell_exec("mkdir -p " + shell_escape(su->pre_path)); file_put_contents(su->pre_path + "/" + su->pre_file_name, preprocess_shared_unit(context, su)); - file_put_contents(su->setup_file_name, compile_setup_file(context, su)); + //file_put_contents(su->setup_file_name, compile_setup_file(context, su)); file_put_contents(su->api_file_name, join(su->api_declarations, "\n")); if(!su->opt_so_optional) @@ -293,7 +277,9 @@ void compile_shared_unit(Request* context, SharedUnit* su, String file_name) else { load_shared_unit(context, su, file_name); - //printf("(i) compiled unit %s\n", file_name.c_str()); + printf("(i) compiled unit %s in %f s\n", + (su->pre_path + "/" + su->pre_file_name).c_str(), + microtime() - comp_start); } } @@ -390,6 +376,7 @@ SharedUnit* compiler_load_shared_unit(Request* context, String file_name, String void compiler_invoke(Request* context, String file_name, DTree& call_param) { + printf("(i) compiler_invoke file %s\n", file_name.c_str()); auto su = compiler_load_shared_unit(context, file_name, "", false); if(su) { diff --git a/src/lib/compiler2.cpp b/src/lib/compiler2.cpp new file mode 100644 index 0000000..e69de29 diff --git a/src/lib/functionlib.cpp b/src/lib/functionlib.cpp index c661fcd..8c42175 100644 --- a/src/lib/functionlib.cpp +++ b/src/lib/functionlib.cpp @@ -6,7 +6,7 @@ String var_dump(StringMap map, String prefix, String postfix) for (auto it = map.begin(); it != map.end(); ++it) { - result.append(prefix + it->first + ": " + it->second + postfix); + result.append(prefix + to_upper(it->first) + ": " + it->second + postfix); } return(result); @@ -42,25 +42,15 @@ u8 hex_to_u8(String src) String to_lower(String s) { - String result = ""; - for(auto c : s) - { - if(c >= 'A' && c <= 'Z') - c = tolower(c); - result.append(1, c); - } + String result = s; + std::transform(result.begin(), result.end(),result.begin(), ::tolower); return(result); } String to_upper(String s) { - String result = ""; - for(auto c : s) - { - if(c >= 'A' && c <= 'Z') - c = toupper(c); - result.append(1, c); - } + String result = s; + std::transform(result.begin(), result.end(),result.begin(), ::toupper); return(result); } @@ -140,7 +130,7 @@ StringList split(String str, String delim) return(result); } -StringMap split_kv(String s, char separator, bool trim_whitespace) +StringMap split_kv(String s, char separator, bool trim_whitespace, bool uppercase_keys) { StringMap result; String k; @@ -157,7 +147,7 @@ StringMap split_kv(String s, char separator, bool trim_whitespace) if(c == separator) mode = 1; else - k.append(1, c); + k.append(1, uppercase_keys ? toupper(c) : c); } else { @@ -175,6 +165,68 @@ StringMap split_kv(String s, char separator, bool trim_whitespace) return(result); } +StringMap split_http_headers(String s) +{ + StringMap result; + String k; + String v; + String query_string; + String base_uri; + for(auto s : split(s, "\n")) + { + u8 mode = 0; + k = ""; + v = ""; + for(auto c : s) + { + if(mode == 0) + { + if(c == ':') + mode = 1; + else + k.append(1, c); + } + else + { + v.append(1, c); + } + } + if(k != "") + { + k = trim(k); + v = trim(v); + if(v == "") + { + if(result["REQUEST_METHOD"] == "") + { + result["REQUEST_METHOD"] = nibble(k, " "); + result["REQUEST_URI"] = nibble(k, " "); + String query_string = result["REQUEST_URI"]; + String base_uri = nibble(query_string, "?"); + result["SERVER_PROTOCOL"] = k; + result["SCRIPT_NAME"] = base_uri; + result["DOCUMENT_URI"] = base_uri; + result["QUERY_STRING"] = query_string; + } + else + { + if(k != "") + result["_"] = k; + } + } + else + { + String header_key = to_upper(k); + std::replace(header_key.begin(), header_key.end(), '-', '_'); + result["HTTP_"+header_key] = v; + if(header_key == "CONTENT_TYPE" || header_key == "CONTENT_LENGTH") + result[header_key] = v; + } + } + } + return(result); +} + String join(StringList l, String delim) { String result; diff --git a/src/lib/functionlib.h b/src/lib/functionlib.h index b963ae1..efbc7fb 100644 --- a/src/lib/functionlib.h +++ b/src/lib/functionlib.h @@ -11,7 +11,8 @@ String trim(String raw); StringList split_space(String str); StringList split(String str, String delim); StringList split_utf8(String s, bool compound_characters = false); -StringMap split_kv(String s, char separator = '=', bool trim_whitespace = true); +StringMap split_kv(String s, char separator = '=', bool trim_whitespace = true, bool uppercase_keys = false); +StringMap split_http_headers(String s); String join(StringList l, String delim = "\n"); String nibble(String& haystack, String delim); void json_consume_space(String s, u32& i); @@ -84,9 +85,10 @@ String var_dump(StringMap map, String prefix = "", String postfix = "\n"); String var_dump(StringList slist, String prefix = "", String postfix = "\n"); void ob_start(); -void ob_clear(); -String ob_get_clear(); +void ob_close(); String ob_get(); +String ob_get_close(); + String safe_name(String raw); #define is_bit_set(var,pos) ((var) & (1<<(pos))) diff --git a/src/lib/sys.cpp b/src/lib/sys.cpp index e7bcada..b611111 100644 --- a/src/lib/sys.cpp +++ b/src/lib/sys.cpp @@ -135,14 +135,28 @@ String file_get_contents(String file_name) bool file_put_contents(String file_name, String content) { - s32 fd = open(file_name.c_str(), O_WRONLY | O_CREAT | O_TRUNC); + s32 fd = open(file_name.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644); if(fd == -1) { printf("(!) Could not write %s\n", file_name.c_str()); return(false); } flock(fd, LOCK_EX); - write(fd, content.data(), content.length()); + const char* data = content.data(); + size_t remaining = content.length(); + while(remaining > 0) + { + auto bytes_written = write(fd, data, remaining); + if(bytes_written < 0) + { + flock(fd, LOCK_UN); + close(fd); + printf("(!) Could not fully write %s\n", file_name.c_str()); + return(false); + } + data += bytes_written; + remaining -= bytes_written; + } flock(fd, LOCK_UN); close(fd); return(true); @@ -242,7 +256,7 @@ String gmdate(String format, u64 timestamp) u64 parse_time(String time_String) { - return(int_val(trim(shell_exec("date -u -d '"+time_String+"' +'%s'")))); + return(int_val(trim(shell_exec("date -u -d "+shell_escape(time_String)+" +'%s'")))); } u64 socket_connect(String host, short port) @@ -475,8 +489,8 @@ pid_t task(String key, std::function exec_after_spawn, u64 timeout) my_pid = getpid(); file_put_contents(status_file_name, std::to_string(my_pid)); - close(context->resources.fcgi_socket); - context->resources.fcgi_socket = 0; + close(context->resources.client_socket); + context->resources.client_socket = 0; //printf("(C) child procress started, PID:%i\n", my_pid); //prctl(PR_SET_PDEATHSIG, SIGHUP); exec_after_spawn(); @@ -534,13 +548,13 @@ StringMap make_server_settings() cfg["SETUP_TEMPLATE"] = "scripts/setup.h.template"; cfg["LIT_ESC"] = "3d5b5_1"; cfg["CONTENT_TYPE"] = "text/html; charset=utf-8"; - cfg["SOCKET_PATH"] = "/run/uce.sock"; + cfg["FCGI_SOCKET_PATH"] = "/run/uce.sock"; cfg["TMP_UPLOAD_PATH"] = "/tmp/uce/uploads"; cfg["SESSION_PATH"] = "/tmp/uce/sessions"; cfg["COMPILER_SYS_PATH"] = "."; cfg["PRECOMPILE_FILES_IN"] = "."; - cfg["LISTEN_PORT"] = std::to_string(9993); + cfg["HTTP_PORT"] = std::to_string(8080); cfg["SESSION_TIME"] = std::to_string(60*60*24*30); cfg["WORKER_COUNT"] = std::to_string(4); cfg["MAX_MEMORY"] = std::to_string(1024*1024*16); @@ -550,5 +564,15 @@ StringMap make_server_settings() cfg[it.first] = it.second; } + if(cfg["FCGI_SOCKET_PATH"] == "" && cfg["SOCKET_PATH"] != "") + cfg["FCGI_SOCKET_PATH"] = cfg["SOCKET_PATH"]; + if(cfg["SOCKET_PATH"] == "" && cfg["FCGI_SOCKET_PATH"] != "") + cfg["SOCKET_PATH"] = cfg["FCGI_SOCKET_PATH"]; + + if(cfg["FCGI_PORT"] == "" && cfg["LISTEN_PORT"] != "") + cfg["FCGI_PORT"] = cfg["LISTEN_PORT"]; + if(cfg["LISTEN_PORT"] == "" && cfg["FCGI_PORT"] != "") + cfg["LISTEN_PORT"] = cfg["FCGI_PORT"]; + return(cfg); } diff --git a/src/lib/types.cpp b/src/lib/types.cpp index ef97efa..e442f37 100644 --- a/src/lib/types.cpp +++ b/src/lib/types.cpp @@ -41,7 +41,7 @@ String nibble(String div, String& haystack) void Request::ob_start() { - ob_stack.push_back(new std::ostringstream()); + ob_stack.push_back(new ByteStream()); ob = ob_stack.back(); } diff --git a/src/lib/types.h b/src/lib/types.h index 99a4042..c34ab46 100644 --- a/src/lib/types.h +++ b/src/lib/types.h @@ -47,6 +47,7 @@ String operator+(String lhs, f32 rhs) { typedef std::map StringMap; typedef std::vector StringList; +typedef std::ostringstream ByteStream; struct Request; struct DTree; @@ -129,22 +130,27 @@ struct Request { String session_name = ""; std::vector uploaded_files; + String response_code = "HTTP/1.1 200 OK"; StringMap header; StringList set_cookies; u64 random_seed; u64 random_index; + std::vector ob_stack; + ByteStream* ob; + String in; - std::vector ob_stack; - std::ostringstream* ob; String out; String err; - bool is_finished = false; - struct Flags { bool log_request = true; + bool is_finished = false; + int status; + bool output_closed = false; + bool params_closed = false; + bool input_closed = false; } flags; struct Stats { @@ -152,20 +158,20 @@ struct Request { f64 time_init; f64 time_start; f64 time_end; - u64 mem_high; - u64 mem_alloc; + u64 mem_high = 0; + u64 mem_alloc = 0; u32 invoke_count = 0; } stats; + // OS-related resources struct Resources { std::vector sockets; std::vector mysql_connections; - u64 fcgi_socket = 0; + u64 client_socket = 0; + u64 server_socket = 0; + std::string params_buffer; } resources; - //void invoke(String file_name); - //void invoke(String file_name, DTree& call_param); - void ob_start(); ~Request(); @@ -184,10 +190,16 @@ void print(Ts... args) ((*context->ob << args), ...); } +template +void out(Ts... args) +{ + ((*context->ob << args), ...); +} + template String concat(Ts... args) { - std::stringstream out; + ByteStream out; ((out << args), ...); return(out.str()); } diff --git a/src/lib/uri.cpp b/src/lib/uri.cpp index 75a1572..f5737e6 100644 --- a/src/lib/uri.cpp +++ b/src/lib/uri.cpp @@ -101,42 +101,117 @@ String encode_query(StringMap map) return(result); } +String trim_wrapping_quotes(String raw) +{ + if(raw.length() >= 2 && raw[0] == '"' && raw[raw.length()-1] == '"') + return(raw.substr(1, raw.length()-2)); + return(raw); +} + +void parse_multipart_content_disposition( + String raw, + String& disposition_type, + String& field_name, + String& file_name) +{ + auto parts = split(raw, ";"); + if(parts.size() == 0) + return; + + disposition_type = to_lower(trim(parts[0])); + for(u32 i = 1; i < parts.size(); i++) + { + String part = trim(parts[i]); + String key = to_lower(trim(nibble(part, "="))); + String value = trim_wrapping_quotes(trim(part)); + if(key == "name") + field_name = value; + else if(key == "filename") + file_name = value; + } +} + +String make_upload_tmp_name() +{ + String upload_path = context->server->config["TMP_UPLOAD_PATH"]; + if(upload_path.length() > 0 && upload_path[upload_path.length()-1] != '/') + upload_path.append(1, '/'); + return(upload_path + make_session_id()); +} + StringMap parse_multipart(String q, String boundary, std::vector& uploaded_files) { StringMap result; - auto i = boundary.length(); + if(boundary == "") + return(result); + + u64 i = 0; while(i < q.length()) { - auto end_pos = q.find(boundary, i); - if(end_pos != String::npos) + auto start_pos = q.find(boundary, i); + if(start_pos == String::npos) + break; + + start_pos += boundary.length(); + if(q.substr(start_pos, 2) == "--") + break; + if(q.substr(start_pos, 2) == "\r\n") + start_pos += 2; + + auto header_end = q.find("\r\n\r\n", start_pos); + if(header_end == String::npos) + break; + + String header_block = q.substr(start_pos, header_end - start_pos); + auto end_pos = q.find(boundary, header_end + 4); + if(end_pos == String::npos) + break; + + String body = q.substr(header_end + 4, end_pos - (header_end + 4)); + if(body.length() >= 2 && body.substr(body.length()-2) == "\r\n") + body.resize(body.length()-2); + + String disposition_type; + String field_name; + String file_name; + for(auto header_line : split(header_block, "\r\n")) { - String field = q.substr(i, end_pos - i); - nibble(":", field); - String ftype = trim(nibble(";", field)); - if(ftype == "form-data") + String header_name = to_lower(trim(nibble(header_line, ":"))); + String header_value = trim(header_line); + if(header_name == "content-disposition") { - nibble("=\"", field); - String field_name = nibble("\"", field); - result[field_name] = field.substr(4, field.length()-6); + parse_multipart_content_disposition( + header_value, + disposition_type, + field_name, + file_name + ); } - else if(ftype == "attachment") + } + + if(field_name != "") + { + bool is_uploaded_file = + (disposition_type == "form-data" || disposition_type == "attachment") && + file_name != ""; + + if(is_uploaded_file) { - nibble("=\"", field); UploadedFile f; - f.tmp_name = context->server->config["TMP_UPLOAD_PATH"] + std::to_string(rand()); - f.file_name = nibble("\"", field); - String bin = field.substr(4, field.length()-6); - f.size = bin.length(); - file_put_contents(f.tmp_name, bin); + f.tmp_name = make_upload_tmp_name(); + f.file_name = file_name; + f.size = body.length(); + file_put_contents(f.tmp_name, body); uploaded_files.push_back(f); + result[field_name] = file_name; + } + else + { + result[field_name] = body; } } - else - { - // we're done - end_pos = q.length(); - } + i = end_pos + boundary.length(); } @@ -287,23 +362,56 @@ String make_session_id() return(to_hex(rand())+to_hex(rand())+to_hex(rand())+to_hex(rand())); } +bool is_valid_session_id(String session_id) +{ + if(session_id.length() < 16 || session_id.length() > 128) + return(false); + for(auto c : session_id) + { + if(!isxdigit(c)) + return(false); + } + return(true); +} + +String session_file_path(String session_id) +{ + if(!is_valid_session_id(session_id)) + return(""); + return(context->server->config["SESSION_PATH"] + "/" + session_id); +} + StringMap load_session_data(String session_id) { - return(parse_query(file_get_contents(context->server->config["SESSION_PATH"] + "/" + session_id))); + String session_path = session_file_path(session_id); + if(session_path == "") + return(StringMap()); + return(parse_query(file_get_contents(session_path))); } void save_session_data(String session_id, StringMap data) { - file_put_contents(context->server->config["SESSION_PATH"] + "/" + session_id, encode_query(data)); + String session_path = session_file_path(session_id); + if(session_path == "") + { + printf("(!) Refusing to save invalid session id\n"); + return; + } + file_put_contents(session_path, encode_query(data)); } String session_start(String session_name) { - if(context->cookies[session_name].length() == 0) + String session_id = context->cookies[session_name]; + if(!is_valid_session_id(session_id)) + session_id = ""; + + if(session_id.length() == 0) { - set_cookie(session_name, make_session_id(), time() + int_val(context->server->config["SESSION_TIME"])); + session_id = make_session_id(); + set_cookie(session_name, session_id, time() + int_val(context->server->config["SESSION_TIME"])); } - context->session_id = context->cookies[session_name]; + context->session_id = session_id; context->session_name = session_name; context->session = load_session_data(context->session_id); return(context->session_id); diff --git a/src/lib/uri.h b/src/lib/uri.h index 3ecbeab..58fbe83 100644 --- a/src/lib/uri.h +++ b/src/lib/uri.h @@ -17,3 +17,69 @@ StringMap load_session_data(String session_id); void save_session_data(String session_id, StringMap data); String session_start(String session_name = "uce-session"); void session_destroy(String session_name = "uce-session"); + +struct WSFrame { + + u8 opcode; + u8 is_final_fragment; + u8 mask_bit; + u64 payload_length; + u64 offset; + u8* payload; + + WSFrame(String& bufstr) + { + const char* buffer = bufstr.c_str(); + u64 buffer_size = bufstr.size(); + opcode = buffer[0] & 0x0F; + is_final_fragment = buffer[0] & 0x80; + mask_bit = buffer[1] & 0x80; + payload_length = buffer[1] & 0x7F; + + offset = 2; + if (payload_length == 126) { + payload_length = ((u64) buffer[2] << 8) | buffer[3]; + offset = 4; + } else if (payload_length == 127) { + payload_length = ((u64) buffer[2] << 56) | + ((u64) buffer[3] << 48) | + ((u64) buffer[4] << 40) | + ((u64) buffer[5] << 32) | + ((u64) buffer[6] << 24) | + ((u64) buffer[7] << 16) | + ((u64) buffer[8] << 8) | + buffer[9]; + offset = 10; + } + + uint8_t* maskingKey = nullptr; + if (mask_bit) { + maskingKey = reinterpret_cast(const_cast(buffer + offset)); + offset += 4; + } + + // Parse the frame payload + payload = reinterpret_cast(const_cast(buffer + offset)); + if (mask_bit) { + for (int i = 0; i < payload_length; i++) { + payload[i] = payload[i] ^ maskingKey[i % 4]; + } + } + + // Print the parsed frame + std::cout << "Parsed WebSocket frame:" << std::endl; + std::cout << " Opcode: " << (int) opcode << std::endl; + std::cout << " Is final fragment: " << (is_final_fragment ? "true" : "false") << std::endl; + std::cout << " Payload length: " << payload_length << std::endl; + if (mask_bit) { + std::cout << " Masking key: "; + for (int i = 0; i < 4; i++) { + std::cout << std::hex << (int) maskingKey[i] << " "; + } + std::cout << std::endl; + } + std::cout << " Payload: " << std::endl << std::string((const char*) payload, payload_length) << std::endl; + + } + +}; diff --git a/src/linux_fastcgi.cpp b/src/linux_fastcgi.cpp index eb59c7e..6631f3d 100644 --- a/src/linux_fastcgi.cpp +++ b/src/linux_fastcgi.cpp @@ -70,8 +70,8 @@ int handle_complete(FastCGIRequest& request) { } } - // printf("(i) request ready\n"); - render_file(request.params["SCRIPT_FILENAME"]); + DTree call_param; + compiler_invoke(&request, request.params["SCRIPT_FILENAME"], call_param); for( auto &f : request.uploaded_files) { @@ -86,6 +86,20 @@ int handle_complete(FastCGIRequest& request) { return 0; } +volatile bool termination_signal_received = false; + +void on_terminate(int sig) +{ + if(termination_signal_received) + return; + termination_signal_received = true; + if(getpid() != parent_pid) + exit(1); + printf("Terminating... PID %i:%i\n", getpid(), parent_pid); + server.shutdown(); + exit(1); +} + void listen_for_connections() { if(precompile_jobs.size() > 0) @@ -119,25 +133,27 @@ void init_base_process() server_state.config["COMPILE_SCRIPT"] = server_state.config["COMPILER_SYS_PATH"] + "/" + server_state.config["COMPILE_SCRIPT"]; - if(server_state.config["LISTEN_PORT"] != "") - server.listen(int_val(server_state.config["LISTEN_PORT"])); + + if(server_state.config["FCGI_PORT"] != "") + server.listen(int_val(server_state.config["FCGI_PORT"])); printf("%s\n", var_dump(server_state.config).c_str()); - if(server_state.config["SOCKET_PATH"] != "") + if(server_state.config["FCGI_SOCKET_PATH"] != "") { - server.listen(server_state.config["SOCKET_PATH"]); - chmod(server_state.config["SOCKET_PATH"].c_str(), S_IRWXU | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); + server.listen(server_state.config["FCGI_SOCKET_PATH"]); + chmod(server_state.config["FCGI_SOCKET_PATH"].c_str(), S_IRWXU | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); } - //dirname(server_state.config.COMPILER_SYS_PATH); - //basename(server_state.config.COMPILER_SYS_PATH); + if(server_state.config["HTTP_PORT"] != "") + server.listen_http(int_val(server_state.config["HTTP_PORT"])); mkdir(server_state.config["BIN_DIRECTORY"]); mkdir(server_state.config["TMP_UPLOAD_PATH"]); mkdir(server_state.config["SESSION_PATH"]); signal(SIGCHLD, on_child_exit); + signal(SIGINT, on_terminate); srand(time()); } @@ -187,7 +203,8 @@ int main(int argc, char** argv) } } } - spawn_subprocess(listen_for_connections); + if(!termination_signal_received) + spawn_subprocess(listen_for_connections); } sleep(1); } diff --git a/test/header.uce b/test/header.uce new file mode 100644 index 0000000..4427128 --- /dev/null +++ b/test/header.uce @@ -0,0 +1,25 @@ + + +RENDER() +{ + DTree t; + + <> + +

+ UCE Test: + Header +

+ + Response Headers +
header));
+
+		?>
+ + Params +
params) ?>
+ + +} diff --git a/test/index.uce b/test/index.uce index 371a829..b1e9d8d 100644 --- a/test/index.uce +++ b/test/index.uce @@ -33,6 +33,9 @@ RENDER()
  • Task repeat
  • UTF-8
  • call_file()
  • +
  • parse_time()
  • +
  • header
  • +
  • WebSockets
  • post["raw"], "today");
    +
    +	<>
    +		
    +		

    + UCE Test: + parse_time +

    +
    +
    + + +
    +
    + +
    +
    +
    +
    params) ?>
    + + +} diff --git a/test/websockets.ws.uce b/test/websockets.ws.uce new file mode 100644 index 0000000..f38b4b9 --- /dev/null +++ b/test/websockets.ws.uce @@ -0,0 +1,42 @@ + +RENDER() +{ + String raw = first(context->post["raw"], "today"); + String server_url = context->params["HTTP_HOST"]+context->params["DOCUMENT_URI"]; + <> + +

    + UCE Test: + WebSocket Test +

    +
    +
    + + +
    +
    + +
    +
    +
    
    +		
    +		
    params) ?>
    + +}