From 2adb923d328d7a9a5fb6618269cd42d2cd692a84 Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Tue, 30 Jun 2026 10:40:53 +0300 Subject: sock: read full request across segments (fix POST body behind proxy) handle_client_read did a single read() and assumed the whole request (headers + POST body) arrived in one packet. Behind a reverse proxy (Traefik) the body is forwarded in a separate TCP segment, so the form params (event/nick/tmpid/...) were never parsed: logins silently no-op'd, the frameset was served with tmpid= (empty), and every frame hit 'Session: Could not find session' -> 502 in all frames. Now accumulate into c_buf across re-arms of the read event (track i_buf_len in the context), drain non-blocking until EAGAIN, and for POST wait until the headers AND the full Content-Length body are present before parsing. EOF/empty and hard read errors are handled without crashing. --- ychat/src/sock/context.cpp | 2 + ychat/src/sock/context.h | 1 + ychat/src/sock/sock.cpp | 93 +++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 91 insertions(+), 5 deletions(-) diff --git a/ychat/src/sock/context.cpp b/ychat/src/sock/context.cpp index be46a07..ac25e95 100644 --- a/ychat/src/sock/context.cpp +++ b/ychat/src/sock/context.cpp @@ -35,6 +35,8 @@ context::context(sock *p_sock, struct event *p_event, int i_fd) this->p_sock = p_sock; this->p_event = p_event; this->i_fd = i_fd; + this->i_buf_len = 0; + this->c_buf[0] = '\0'; } context::~context() diff --git a/ychat/src/sock/context.h b/ychat/src/sock/context.h index a604cda..5f88d4f 100644 --- a/ychat/src/sock/context.h +++ b/ychat/src/sock/context.h @@ -43,6 +43,7 @@ struct context sock *p_sock; struct event* p_event; char c_buf[READSOCK+1]; + int i_buf_len; // accumulated bytes in c_buf (request may arrive in several reads) //string s_request; map *p_map_params; int i_fd; diff --git a/ychat/src/sock/sock.cpp b/ychat/src/sock/sock.cpp index 0830d31..070d540 100644 --- a/ychat/src/sock/sock.cpp +++ b/ychat/src/sock/sock.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include "sock.h" #include "../tool/tool.h" @@ -239,19 +240,101 @@ sock::handle_client_read(int i_fd, short event, void *p_arg) static int i_size = READSOCK * sizeof(char); context *p_context = static_cast(p_arg); - if (-1 == read(i_fd, p_context->c_buf, i_size)) + // A request (especially a POST body) can arrive split across several TCP + // segments — e.g. when a reverse proxy (Traefik) forwards headers and body + // separately. Accumulate into c_buf across re-arms of this event until we + // have the full request, then process it. + bool b_got_data = false; + for (;;) { - switch (errno) + if ( p_context->i_buf_len >= READSOCK ) + break; // buffer full + + ssize_t n = read(i_fd, p_context->c_buf + p_context->i_buf_len, + i_size - p_context->i_buf_len); + + if (n > 0) { - case EAGAIN: - case EINTR: + p_context->i_buf_len += n; + b_got_data = true; + continue; // drain whatever else is currently buffered + } + + if (n == 0) + { + // EOF: client closed before a complete request. Drop it. + if (!b_got_data || p_context->i_buf_len == 0) + { + p_context->del_event(); + delete p_context; + return; + } + break; // process whatever we did get + } + + // n < 0 + if (errno == EAGAIN || errno == EINTR) + { + if (!b_got_data) + { + // Nothing new this invocation; wait for more. + event_add(p_context->p_event, NULL); + return; + } + break; // we have some data; check below whether the request is complete + } + + // Hard read error. + p_context->del_event(); + delete p_context; + return; + } + + p_context->c_buf[p_context->i_buf_len] = '\0'; + string s_buf(p_context->c_buf); + + // For POST, wait until we have the full body (per Content-Length) before + // parsing — otherwise the form params (event/nick/tmpid/...) are missing + // and logins silently no-op. + if ( strncmp("POST", p_context->c_buf, 4) == 0 ) + { + size_t i_hdr_end = s_buf.find("\r\n\r\n"); + size_t i_body_off = string::npos; + if (i_hdr_end != string::npos) + i_body_off = i_hdr_end + 4; + else + { + i_hdr_end = s_buf.find("\n\n"); + if (i_hdr_end != string::npos) + i_body_off = i_hdr_end + 2; + } + + if (i_hdr_end == string::npos) + { + // Headers not fully received yet; wait for more. event_add(p_context->p_event, NULL); return; } + + size_t i_cl = s_buf.find("Content-Length:"); + if (i_cl != string::npos) + { + size_t v = i_cl + 16; + size_t ve = s_buf.find_first_of("\r\n", v); + if (ve == string::npos) ve = s_buf.size(); + int i_content_len = atoi(s_buf.substr(v, ve - v).c_str()); + int i_have = (int)s_buf.size() - (int)i_body_off; + if (i_have < i_content_len) + { + // Body still incomplete; wait for the rest. + event_add(p_context->p_event, NULL); + return; + } + } } + p_context->del_event(); - string s_buf(p_context->c_buf); string s_query(""); bool b_is_post_request; -- cgit v1.2.3