v0.8.2: build fixes, WebUI security and correctness
Build: <fcntl.h> in tui.c, format-truncation in http_srv.c and /web. WebUI: - refuse cross-site requests (Origin must be the page itself) in the default no-token mode: any page could POST approval=yolo and a prompt - session/workspace/undo/compact/attach/model changes are 409 while a turn runs (use-after-free, messages landing in the wrong session) - ask_user returned "dismissed" at once instead of waiting - cancel and shutdown wake a turn parked on a permission or question; SIGTERM mid-turn no longer hangs or crashes - a page opened while a question is pending still gets it; Last-Event-ID resumes the stream; event seq matches the SSE id - token mode: page assets load without the token, images via cookie - --web-open works, --web-port/--web-token are validated - frontend: gate keys ignored while typing, clear-images button, reasoning no longer wiped while streaming HTTP server: auth and size cap before reading the body, strict Content-Length, NUL in url_decode, 413 on keep-alive, idle timeout, absolute-form only without a leading '/', case-insensitive Connection, header deadline, 400 for long request lines, unlink before close, poll() instead of select(), SO_EXCLUSIVEADDRUSE on Windows. Markdown sanitizer escapes quotes in attribute values. /web: passes this session's provider/model/base/effort/approval (key via the environment), keeps output off the TUI, shows the real URL and keeps CURRENT on the TUI's session. Tests: scripts/e2e_webui_ask.py, scripts/e2e_webui_http.py (run from e2e_webui.sh), sanitizer quote test.
This commit is contained in:
@@ -12,6 +12,10 @@
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#if !defined(_WIN32_WINNT) || _WIN32_WINNT < 0x0600
|
||||
#undef _WIN32_WINNT
|
||||
#define _WIN32_WINNT 0x0600 /* WSAPoll */
|
||||
#endif
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#else
|
||||
@@ -21,8 +25,8 @@
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <poll.h>
|
||||
#include <signal.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
@@ -45,7 +49,6 @@ static void set_err_win(char *err, size_t err_sz, const char *what) {
|
||||
}
|
||||
#define SET_ERR(e, n, w) set_err_win(e, n, w)
|
||||
#define NET_FD(s) ((SOCKET)(s))
|
||||
#define NET_NFDS(s) (0) /* Winsock ignores nfds */
|
||||
static void net_close_raw(os_socket_t s) { closesocket((SOCKET)s); }
|
||||
static int net_last_would_block(void) {
|
||||
int e = WSAGetLastError();
|
||||
@@ -58,7 +61,6 @@ static int net_last_conn_reset(void) {
|
||||
#else
|
||||
#define SET_ERR(e, n, w) set_err(e, n, w)
|
||||
#define NET_FD(s) ((int)(s))
|
||||
#define NET_NFDS(s) ((int)((s) + 1))
|
||||
static void net_close_raw(os_socket_t s) { close(s); }
|
||||
static int net_last_would_block(void) { return errno == EINTR; }
|
||||
static int net_last_conn_reset(void) {
|
||||
@@ -84,15 +86,22 @@ void os_net_cleanup(void) {
|
||||
#endif
|
||||
}
|
||||
|
||||
/* poll, not select: FD_SET past FD_SETSIZE writes out of bounds, and a
|
||||
* failed connect shows up as an error event, not as writable, on Windows. */
|
||||
static int wait_io(os_socket_t s, int want_write, int timeout_ms) {
|
||||
fd_set fds;
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(NET_FD(s), &fds);
|
||||
struct timeval tv;
|
||||
tv.tv_sec = timeout_ms / 1000;
|
||||
tv.tv_usec = (timeout_ms % 1000) * 1000;
|
||||
int rc = select(NET_NFDS(s), want_write ? NULL : &fds,
|
||||
want_write ? &fds : NULL, NULL, timeout_ms < 0 ? NULL : &tv);
|
||||
#ifdef _WIN32
|
||||
WSAPOLLFD p;
|
||||
p.fd = NET_FD(s);
|
||||
p.events = want_write ? POLLWRNORM : POLLRDNORM;
|
||||
p.revents = 0;
|
||||
int rc = WSAPoll(&p, 1, timeout_ms < 0 ? -1 : timeout_ms);
|
||||
#else
|
||||
struct pollfd p;
|
||||
p.fd = NET_FD(s);
|
||||
p.events = want_write ? POLLOUT : POLLIN;
|
||||
p.revents = 0;
|
||||
int rc = poll(&p, 1, timeout_ms < 0 ? -1 : timeout_ms);
|
||||
#endif
|
||||
if (rc > 0)
|
||||
return 1;
|
||||
if (rc == 0)
|
||||
@@ -137,8 +146,15 @@ os_socket_t os_net_listen(const char *host, int port, char *err, size_t err_sz)
|
||||
return OS_SOCKET_BAD;
|
||||
}
|
||||
int one = 1;
|
||||
#ifdef _WIN32
|
||||
/* SO_REUSEADDR on Windows lets another process bind the same port and
|
||||
* take over our connections */
|
||||
setsockopt(NET_FD(s), SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (const char *)&one,
|
||||
sizeof(one));
|
||||
#else
|
||||
setsockopt(NET_FD(s), SOL_SOCKET, SO_REUSEADDR, (const char *)&one,
|
||||
sizeof(one));
|
||||
#endif
|
||||
#ifdef IPV6_V6ONLY
|
||||
if (sa.ss_family == AF_INET6)
|
||||
setsockopt(NET_FD(s), IPPROTO_IPV6, IPV6_V6ONLY, (const char *)&one,
|
||||
|
||||
Reference in New Issue
Block a user