From bd9ff7c4b9afa28b456dcee983e16250d54f52a5 Mon Sep 17 00:00:00 2001 From: Diogo Martins Date: Sat, 15 Aug 2026 19:08:22 +0100 Subject: [PATCH] Add uWebSockets (C++) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Requested by the maintainer in uNetworking/uWebSockets.js#1298: the C++ library rather than the Node binding, built from master so their fixes show up in these results as they land. Implements the four probe endpoints on top of upstream's own EchoBody.cpp pattern โ€” any() routes with the wildcard last, range-for over the request for /echo, and onData/onAborted for the body echo. Responses inside onData are already corked by uWS, so no explicit cork is needed. Built as a two-stage image: uSockets via its own make, then g++ with the flags build.c uses, minus -march=native so the binary does not depend on the builder's CPU. The clone is deliberately unpinned, which is the point of the request, and only the uSockets submodule is initialised โ€” the rest are fuzzing corpora and test suites. Scores 119/159 locally, with a verdict identical to uWebSockets.js on all 213 tests. --- docs/content/servers/uwebsockets-cpp.md | 158 ++++++++++++++++++++ src/Servers/UWebSocketsCppServer/Dockerfile | 19 +++ src/Servers/UWebSocketsCppServer/probe.json | 1 + src/Servers/UWebSocketsCppServer/server.cpp | 83 ++++++++++ 4 files changed, 261 insertions(+) create mode 100644 docs/content/servers/uwebsockets-cpp.md create mode 100644 src/Servers/UWebSocketsCppServer/Dockerfile create mode 100644 src/Servers/UWebSocketsCppServer/probe.json create mode 100644 src/Servers/UWebSocketsCppServer/server.cpp diff --git a/docs/content/servers/uwebsockets-cpp.md b/docs/content/servers/uwebsockets-cpp.md new file mode 100644 index 0000000..1d5c5c5 --- /dev/null +++ b/docs/content/servers/uwebsockets-cpp.md @@ -0,0 +1,158 @@ +--- +title: "uWebSockets" +description: "uWebSockets (C++) tested against RFC 9110/9112 for HTTP/1.1 compliance, request smuggling resistance, and malformed input handling." +toc: true +breadcrumbs: false +--- + +**Language:** C++ ยท [View source on GitHub](https://github.com/MDA2AV/Http11Probe/tree/main/src/Servers/UWebSocketsCppServer) + +The C++ library, built from `master` rather than a pinned release so upstream fixes show up in these results as they land. `uWebSockets.js` is the Node.js binding over this same core. + +## Dockerfile + +```dockerfile +FROM debian:trixie-slim AS build +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates g++ git make zlib1g-dev \ + && rm -rf /var/lib/apt/lists/* +WORKDIR /src +# Deliberately unpinned: upstream asked that results track master so their +# fixes show up here. Rebuilding picks up whatever master is at that moment. +RUN git clone --depth 1 https://github.com/uNetworking/uWebSockets.git . \ + && git submodule update --init --depth 1 uSockets +RUN make -C uSockets +COPY src/Servers/UWebSocketsCppServer/server.cpp . +RUN g++ -std=c++2b -O3 -Isrc -IuSockets/src server.cpp uSockets/*.o -lz -o /uws-server + +FROM debian:trixie-slim +RUN apt-get update && apt-get install -y --no-install-recommends zlib1g \ + && rm -rf /var/lib/apt/lists/* +COPY --from=build /uws-server /usr/local/bin/uws-server +USER nobody +ENTRYPOINT ["uws-server", "8080"] +``` + +## Source โ€” `server.cpp` + +```cpp +#include "App.h" + +#include +#include +#include +#include +#include +#include + +/* uWS invalidates req as soon as the handler returns, so everything needed + * later is read out synchronously. Only the body echo is async. */ + +static std::string parseCookies(std::string_view raw) { + std::string out; + size_t pos = 0; + while (pos < raw.size()) { + size_t semi = raw.find(';', pos); + std::string_view pair = raw.substr(pos, semi == std::string_view::npos ? raw.size() - pos : semi - pos); + size_t start = pair.find_first_not_of(" \t"); + if (start != std::string_view::npos) { + pair.remove_prefix(start); + size_t eq = pair.find('='); + if (eq != std::string_view::npos && eq > 0) { + out.append(pair.substr(0, eq)); + out.push_back('='); + out.append(pair.substr(eq + 1)); + out.push_back('\n'); + } + } + if (semi == std::string_view::npos) { + break; + } + pos = semi + 1; + } + return out; +} + +int main(int argc, char **argv) { + int port = 8080; + if (argc > 1) { + std::string_view arg(argv[1]); + std::from_chars(arg.data(), arg.data() + arg.size(), port); + } + + uWS::App().any("/cookie", [](auto *res, auto *req) { + std::string body = parseCookies(req->getHeader("cookie")); + res->writeHeader("Content-Type", "text/plain"); + res->end(body); + }).any("/echo", [](auto *res, auto *req) { + std::string body; + for (auto [key, value] : *req) { + body.append(key); + body.append(": "); + body.append(value); + body.push_back('\n'); + } + res->writeHeader("Content-Type", "text/plain"); + res->end(body); + /* Wildcards must be registered last. */ + }).any("/*", [](auto *res, auto *req) { + if (req->getMethod() != "post") { + res->writeHeader("Content-Type", "text/plain"); + res->end("OK"); + return; + } + + /* Already inside the socket callback, so uWS corks these writes for us. */ + auto buffer = std::make_shared(); + res->onData([res, buffer](std::string_view chunk, bool isFin) { + buffer->append(chunk); + if (isFin) { + res->writeHeader("Content-Type", "text/plain"); + res->end(*buffer); + } + }); + res->onAborted([]() {}); + }).listen("0.0.0.0", port, [port](auto *listen_socket) { + if (!listen_socket) { + std::cerr << "Failed to listen on port " << port << std::endl; + std::exit(1); + } + }).run(); +} +``` + +## Test Results + +

Loading results...

+ +### Compliance + +
+ +### Smuggling + +
+ +### Malformed Input + +
+ +### Caching + +
+ +### Cookies + +
+ + + + diff --git a/src/Servers/UWebSocketsCppServer/Dockerfile b/src/Servers/UWebSocketsCppServer/Dockerfile new file mode 100644 index 0000000..d262aba --- /dev/null +++ b/src/Servers/UWebSocketsCppServer/Dockerfile @@ -0,0 +1,19 @@ +FROM debian:trixie-slim AS build +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates g++ git make zlib1g-dev \ + && rm -rf /var/lib/apt/lists/* +WORKDIR /src +# Deliberately unpinned: upstream asked that results track master so their +# fixes show up here. Rebuilding picks up whatever master is at that moment. +RUN git clone --depth 1 https://github.com/uNetworking/uWebSockets.git . \ + && git submodule update --init --depth 1 uSockets +RUN make -C uSockets +COPY src/Servers/UWebSocketsCppServer/server.cpp . +RUN g++ -std=c++2b -O3 -Isrc -IuSockets/src server.cpp uSockets/*.o -lz -o /uws-server + +FROM debian:trixie-slim +RUN apt-get update && apt-get install -y --no-install-recommends zlib1g \ + && rm -rf /var/lib/apt/lists/* +COPY --from=build /uws-server /usr/local/bin/uws-server +USER nobody +ENTRYPOINT ["uws-server", "8080"] diff --git a/src/Servers/UWebSocketsCppServer/probe.json b/src/Servers/UWebSocketsCppServer/probe.json new file mode 100644 index 0000000..23a783a --- /dev/null +++ b/src/Servers/UWebSocketsCppServer/probe.json @@ -0,0 +1 @@ +{"name": "uWebSockets", "language": "C++", "repository": "https://github.com/uNetworking/uWebSockets"} diff --git a/src/Servers/UWebSocketsCppServer/server.cpp b/src/Servers/UWebSocketsCppServer/server.cpp new file mode 100644 index 0000000..2be6fb1 --- /dev/null +++ b/src/Servers/UWebSocketsCppServer/server.cpp @@ -0,0 +1,83 @@ +#include "App.h" + +#include +#include +#include +#include +#include +#include + +/* uWS invalidates req as soon as the handler returns, so everything needed + * later is read out synchronously. Only the body echo is async. */ + +static std::string parseCookies(std::string_view raw) { + std::string out; + size_t pos = 0; + while (pos < raw.size()) { + size_t semi = raw.find(';', pos); + std::string_view pair = raw.substr(pos, semi == std::string_view::npos ? raw.size() - pos : semi - pos); + size_t start = pair.find_first_not_of(" \t"); + if (start != std::string_view::npos) { + pair.remove_prefix(start); + size_t eq = pair.find('='); + if (eq != std::string_view::npos && eq > 0) { + out.append(pair.substr(0, eq)); + out.push_back('='); + out.append(pair.substr(eq + 1)); + out.push_back('\n'); + } + } + if (semi == std::string_view::npos) { + break; + } + pos = semi + 1; + } + return out; +} + +int main(int argc, char **argv) { + int port = 8080; + if (argc > 1) { + std::string_view arg(argv[1]); + std::from_chars(arg.data(), arg.data() + arg.size(), port); + } + + uWS::App().any("/cookie", [](auto *res, auto *req) { + std::string body = parseCookies(req->getHeader("cookie")); + res->writeHeader("Content-Type", "text/plain"); + res->end(body); + }).any("/echo", [](auto *res, auto *req) { + std::string body; + for (auto [key, value] : *req) { + body.append(key); + body.append(": "); + body.append(value); + body.push_back('\n'); + } + res->writeHeader("Content-Type", "text/plain"); + res->end(body); + /* Wildcards must be registered last. */ + }).any("/*", [](auto *res, auto *req) { + if (req->getMethod() != "post") { + res->writeHeader("Content-Type", "text/plain"); + res->end("OK"); + return; + } + + /* Already inside the socket callback, so uWS corks these writes for us. */ + auto buffer = std::make_shared(); + res->onData([res, buffer](std::string_view chunk, bool isFin) { + buffer->append(chunk); + if (isFin) { + res->writeHeader("Content-Type", "text/plain"); + res->end(*buffer); + } + }); + res->onAborted([]() {}); + }).listen("0.0.0.0", port, [port](auto *listen_socket) { + if (!listen_socket) { + std::cerr << "Failed to listen on port " << port << std::endl; + std::exit(1); + } + }).run(); +}