560 lines
21 KiB
C
560 lines
21 KiB
C
// testHttpdLua.c -- the httpd-as-a-script (examples/httpd.lua). A Lua context loads the script (which
|
|
// implements HTTP/1.1 + routing + keep-alive + WebSocket purely in Lua over calog's tcp* transport and
|
|
// crypto natives) and serves on a port; this test drives real requests over a loopback socket: routing,
|
|
// a 404, a response-map custom status, HTTP/1.1 keep-alive (two requests on one connection), and a full
|
|
// WebSocket handshake + echo. There is no C HTTP code under test -- the point is that the protocol
|
|
// lives in the script.
|
|
|
|
#define _GNU_SOURCE // strcasestr
|
|
|
|
#include "calog.h"
|
|
#include "calogCrypto.h"
|
|
#include "calogNet.h"
|
|
|
|
#include <openssl/ssl.h>
|
|
|
|
#include <arpa/inet.h>
|
|
#include <netinet/in.h>
|
|
#include <stdatomic.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/socket.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
#define PORT 38910
|
|
#define HTTPS_PORT 38911
|
|
#define PUMP_LIMIT 4000
|
|
|
|
static CalogT *calog = NULL;
|
|
static _Atomic bool serving = true;
|
|
static _Atomic bool isReady = false;
|
|
static int32_t testsRun = 0;
|
|
static int32_t testsFailed = 0;
|
|
|
|
#define CHECK(cond, msg) checkImpl((cond), (msg), __LINE__)
|
|
|
|
static void checkImpl(bool condition, const char *message, int32_t line);
|
|
static bool genCert(const char *certPath, const char *keyPath);
|
|
static int clientConnect(int port);
|
|
static bool httpsGet(int port, const char *path, int *statusOut, char *body, size_t bodyCap);
|
|
static char *loadScript(const char *path);
|
|
static int32_t nativeKeepServing(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
|
static int32_t nativeReady(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
|
static bool readResponse(int fd, int *statusOut, char *body, size_t bodyCap);
|
|
|
|
|
|
static void checkImpl(bool condition, const char *message, int32_t line) {
|
|
testsRun++;
|
|
if (!condition) {
|
|
testsFailed++;
|
|
printf("FAIL testHttpdLua.c:%d %s\n", line, message);
|
|
}
|
|
}
|
|
|
|
|
|
static int clientConnect(int port) {
|
|
struct sockaddr_in addr;
|
|
int fd;
|
|
|
|
fd = socket(AF_INET, SOCK_STREAM, 0);
|
|
if (fd < 0) {
|
|
return -1;
|
|
}
|
|
memset(&addr, 0, sizeof(addr));
|
|
addr.sin_family = AF_INET;
|
|
addr.sin_port = htons((uint16_t)port);
|
|
inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);
|
|
if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
return fd;
|
|
}
|
|
|
|
|
|
// Read up to one full HTTP response (headers + Content-Length body) from fd. Parses the status and
|
|
// copies the body out. Returns false on a closed/short read. Consumes EXACTLY one response (headers
|
|
// byte-by-byte to the CRLFCRLF, then precisely Content-Length body bytes) so a following pipelined
|
|
// response is left in the socket for the next call -- a bulk recv could coalesce two responses and
|
|
// make the second read block forever.
|
|
static bool readResponse(int fd, int *statusOut, char *body, size_t bodyCap) {
|
|
char response[8192];
|
|
char *clHeader;
|
|
int total;
|
|
int headerLen;
|
|
int contentLen;
|
|
int bodyRead;
|
|
|
|
total = 0;
|
|
headerLen = -1;
|
|
while (total < (int)sizeof(response) - 1) {
|
|
ssize_t got;
|
|
got = recv(fd, response + total, 1, 0);
|
|
if (got <= 0) {
|
|
break;
|
|
}
|
|
total += (int)got;
|
|
if (total >= 4 && memcmp(response + total - 4, "\r\n\r\n", 4) == 0) {
|
|
headerLen = total;
|
|
break;
|
|
}
|
|
}
|
|
if (headerLen < 0) {
|
|
return false;
|
|
}
|
|
response[total] = '\0';
|
|
contentLen = 0;
|
|
clHeader = strcasestr(response, "Content-Length:");
|
|
if (clHeader != NULL) {
|
|
contentLen = atoi(clHeader + 15);
|
|
}
|
|
bodyRead = 0;
|
|
while (bodyRead < contentLen && headerLen + bodyRead < (int)sizeof(response) - 1) {
|
|
ssize_t got;
|
|
got = recv(fd, response + headerLen + bodyRead, 1, 0);
|
|
if (got <= 0) {
|
|
break;
|
|
}
|
|
bodyRead += (int)got;
|
|
}
|
|
*statusOut = 0;
|
|
if (strncmp(response, "HTTP/1.1 ", 9) == 0) {
|
|
*statusOut = atoi(response + 9);
|
|
}
|
|
response[headerLen + bodyRead] = '\0';
|
|
snprintf(body, bodyCap, "%s", response + headerLen);
|
|
return true;
|
|
}
|
|
|
|
|
|
static char *loadScript(const char *path) {
|
|
FILE *f;
|
|
char *buffer;
|
|
long size;
|
|
size_t got;
|
|
|
|
f = fopen(path, "rb");
|
|
if (f == NULL) {
|
|
return NULL;
|
|
}
|
|
fseek(f, 0, SEEK_END);
|
|
size = ftell(f);
|
|
fseek(f, 0, SEEK_SET);
|
|
if (size < 0) {
|
|
fclose(f);
|
|
return NULL;
|
|
}
|
|
buffer = (char *)malloc((size_t)size + 1);
|
|
if (buffer == NULL) {
|
|
fclose(f);
|
|
return NULL;
|
|
}
|
|
got = fread(buffer, 1, (size_t)size, f);
|
|
buffer[got] = '\0';
|
|
fclose(f);
|
|
return buffer;
|
|
}
|
|
|
|
|
|
// Generate a throwaway self-signed cert + key via the system openssl CLI, for the HTTPS test.
|
|
static bool genCert(const char *certPath, const char *keyPath) {
|
|
char cmd[512];
|
|
|
|
snprintf(cmd, sizeof(cmd),
|
|
"openssl req -x509 -newkey rsa:2048 -keyout %s -out %s -days 1 -nodes -subj /CN=localhost >/dev/null 2>&1",
|
|
keyPath, certPath);
|
|
return system(cmd) == 0;
|
|
}
|
|
|
|
|
|
// Minimal HTTPS client: connect, TLS handshake (no cert verification -- self-signed test cert), GET
|
|
// the path, parse the status + body. Exercises the tcp transport's tls option end to end.
|
|
static bool httpsGet(int port, const char *path, int *statusOut, char *body, size_t bodyCap) {
|
|
SSL_CTX *ctx;
|
|
SSL *ssl;
|
|
char request[256];
|
|
char response[8192];
|
|
char *bodyStart;
|
|
int fd;
|
|
int total;
|
|
int requestLen;
|
|
bool ok;
|
|
|
|
ctx = SSL_CTX_new(TLS_client_method());
|
|
if (ctx == NULL) {
|
|
return false;
|
|
}
|
|
fd = clientConnect(port);
|
|
if (fd < 0) {
|
|
SSL_CTX_free(ctx);
|
|
return false;
|
|
}
|
|
ssl = SSL_new(ctx);
|
|
if (ssl == NULL) {
|
|
close(fd);
|
|
SSL_CTX_free(ctx);
|
|
return false;
|
|
}
|
|
SSL_set_fd(ssl, fd);
|
|
ok = SSL_connect(ssl) == 1;
|
|
if (ok) {
|
|
requestLen = snprintf(request, sizeof(request), "GET %s HTTP/1.1\r\nHost: x\r\nConnection: close\r\n\r\n", path);
|
|
SSL_write(ssl, request, requestLen);
|
|
total = 0;
|
|
for (;;) {
|
|
int got;
|
|
got = SSL_read(ssl, response + total, (int)sizeof(response) - 1 - total);
|
|
if (got <= 0) {
|
|
break;
|
|
}
|
|
total += got;
|
|
if (total >= (int)sizeof(response) - 1) {
|
|
break;
|
|
}
|
|
}
|
|
response[total > 0 ? total : 0] = '\0';
|
|
*statusOut = 0;
|
|
if (strncmp(response, "HTTP/1.1 ", 9) == 0) {
|
|
*statusOut = atoi(response + 9);
|
|
}
|
|
bodyStart = strstr(response, "\r\n\r\n");
|
|
body[0] = '\0';
|
|
if (bodyStart != NULL) {
|
|
snprintf(body, bodyCap, "%s", bodyStart + 4);
|
|
}
|
|
ok = total > 0;
|
|
}
|
|
if (ssl != NULL) {
|
|
SSL_shutdown(ssl);
|
|
SSL_free(ssl);
|
|
}
|
|
close(fd);
|
|
SSL_CTX_free(ctx);
|
|
return ok;
|
|
}
|
|
|
|
|
|
static int32_t nativeKeepServing(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
|
|
(void)args;
|
|
(void)argCount;
|
|
(void)userData;
|
|
calogValueBool(result, atomic_load(&serving));
|
|
return calogOkE;
|
|
}
|
|
|
|
|
|
static int32_t nativeReady(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
|
|
(void)args;
|
|
(void)argCount;
|
|
(void)userData;
|
|
atomic_store(&isReady, true);
|
|
calogValueNil(result);
|
|
return calogOkE;
|
|
}
|
|
|
|
|
|
int main(void) {
|
|
CalogContextT *lua;
|
|
CalogContextT *tls;
|
|
char *script;
|
|
char *source;
|
|
char body[8192];
|
|
const char *setup;
|
|
int i;
|
|
int status;
|
|
int fd;
|
|
|
|
calog = calogCreate();
|
|
if (calog == NULL) {
|
|
printf("calog create failed\n");
|
|
return 1;
|
|
}
|
|
calogNetRegister(calog);
|
|
calogCryptoRegister(calog);
|
|
calogRegisterInline(calog, "keepServing", nativeKeepServing, NULL);
|
|
calogRegisterInline(calog, "ready", nativeReady, NULL);
|
|
|
|
script = loadScript("examples/httpd.lua");
|
|
if (script == NULL) {
|
|
printf("could not load examples/httpd.lua\n");
|
|
return 1;
|
|
}
|
|
// Wrap the module (it ends in `return httpd`) so it binds to a global, then register routes and
|
|
// serve. keepServing lets this test stop the accept loop; ready fires once the socket is bound.
|
|
setup =
|
|
"\nlocal s = httpd.new()\n"
|
|
"s:route('GET', '/hi', function(req) return 'hello ' .. req.path end)\n"
|
|
"s:route('GET', '/made', function(req) return { status = 201, body = 'created' } end)\n"
|
|
"s:route('GET', '/boom', function(req) return nil .. 'x' end)\n" /* runtime error -> 500 */
|
|
"s:websocket('/ws', function(msg) return 'echo: ' .. msg.message end)\n"
|
|
"s:serve(38910, { keep = keepServing, onReady = ready })\n";
|
|
source = (char *)malloc(strlen(script) + strlen(setup) + 64);
|
|
if (source == NULL) {
|
|
return 1;
|
|
}
|
|
sprintf(source, "httpd = (function()\n%s\nend)()\n%s", script, setup);
|
|
free(script);
|
|
|
|
lua = calogContextOpen(calog, &calogLuaEngine);
|
|
calogContextEval(lua, source);
|
|
free(source);
|
|
|
|
// Wait until the script reports the socket is listening (it runs on its own context thread).
|
|
for (i = 0; i < PUMP_LIMIT && !atomic_load(&isReady); i++) {
|
|
struct timespec ts = { 0, 500000 };
|
|
calogPump(calog);
|
|
nanosleep(&ts, NULL);
|
|
}
|
|
CHECK(atomic_load(&isReady), "the script httpd bound and is listening");
|
|
|
|
// --- plain HTTP routing (Connection: close) ---
|
|
fd = clientConnect(PORT);
|
|
if (fd >= 0) {
|
|
const char *req = "GET /hi HTTP/1.1\r\nHost: x\r\nConnection: close\r\n\r\n";
|
|
send(fd, req, strlen(req), 0);
|
|
CHECK(readResponse(fd, &status, body, sizeof(body)) && status == 200 && strcmp(body, "hello /hi") == 0, "GET route serves and sees the path");
|
|
close(fd);
|
|
} else {
|
|
CHECK(false, "connect for GET /hi");
|
|
}
|
|
|
|
fd = clientConnect(PORT);
|
|
if (fd >= 0) {
|
|
const char *req = "GET /made HTTP/1.1\r\nHost: x\r\nConnection: close\r\n\r\n";
|
|
send(fd, req, strlen(req), 0);
|
|
CHECK(readResponse(fd, &status, body, sizeof(body)) && status == 201 && strcmp(body, "created") == 0, "response map sets a custom status");
|
|
close(fd);
|
|
}
|
|
|
|
fd = clientConnect(PORT);
|
|
if (fd >= 0) {
|
|
const char *req = "GET /nope HTTP/1.1\r\nHost: x\r\nConnection: close\r\n\r\n";
|
|
send(fd, req, strlen(req), 0);
|
|
CHECK(readResponse(fd, &status, body, sizeof(body)) && status == 404, "an unmatched route is 404");
|
|
close(fd);
|
|
}
|
|
|
|
// --- HTTP/1.1 keep-alive: two requests on ONE connection ---
|
|
fd = clientConnect(PORT);
|
|
if (fd >= 0) {
|
|
const char *req = "GET /hi HTTP/1.1\r\nHost: x\r\nConnection: keep-alive\r\n\r\n";
|
|
bool first;
|
|
bool second;
|
|
send(fd, req, strlen(req), 0);
|
|
first = readResponse(fd, &status, body, sizeof(body)) && status == 200 && strcmp(body, "hello /hi") == 0;
|
|
send(fd, req, strlen(req), 0);
|
|
second = readResponse(fd, &status, body, sizeof(body)) && status == 200 && strcmp(body, "hello /hi") == 0;
|
|
CHECK(first && second, "keep-alive serves two requests on one connection");
|
|
close(fd);
|
|
}
|
|
|
|
// --- WebSocket: handshake (RFC 6455 sample key -> known accept) + a masked echo round-trip ---
|
|
fd = clientConnect(PORT);
|
|
if (fd >= 0) {
|
|
const char *req = "GET /ws HTTP/1.1\r\nHost: x\r\nUpgrade: websocket\r\nConnection: Upgrade\r\n"
|
|
"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\nSec-WebSocket-Version: 13\r\n\r\n";
|
|
unsigned char frame[10];
|
|
unsigned char reply[64];
|
|
char handshake[1024];
|
|
ssize_t got;
|
|
ssize_t rgot;
|
|
|
|
send(fd, req, strlen(req), 0);
|
|
got = recv(fd, handshake, sizeof(handshake) - 1, 0);
|
|
handshake[got > 0 ? got : 0] = '\0';
|
|
CHECK(got > 0 && strstr(handshake, "101 Switching Protocols") != NULL &&
|
|
strstr(handshake, "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=") != NULL,
|
|
"WebSocket handshake returns the RFC 6455 accept value");
|
|
|
|
// One masked text frame carrying "ping": FIN|text, MASK|len=4, mask 01020304, "ping" XOR mask.
|
|
frame[0] = 0x81;
|
|
frame[1] = 0x80 | 4;
|
|
frame[2] = 0x01; frame[3] = 0x02; frame[4] = 0x03; frame[5] = 0x04;
|
|
frame[6] = (unsigned char)('p' ^ 0x01);
|
|
frame[7] = (unsigned char)('i' ^ 0x02);
|
|
frame[8] = (unsigned char)('n' ^ 0x03);
|
|
frame[9] = (unsigned char)('g' ^ 0x04);
|
|
send(fd, frame, sizeof(frame), 0);
|
|
|
|
rgot = recv(fd, reply, sizeof(reply), 0);
|
|
// Server frame: 0x81, len=10 (unmasked), "echo: ping".
|
|
CHECK(rgot >= 12 && reply[0] == 0x81 && reply[1] == 10 && memcmp(reply + 2, "echo: ping", 10) == 0,
|
|
"WebSocket echoes a masked text frame back through the Lua handler");
|
|
close(fd);
|
|
}
|
|
|
|
// --- WebSocket FRAGMENTATION: "hello" split across a text frame (FIN=0) + a continuation (FIN=1) ---
|
|
fd = clientConnect(PORT);
|
|
if (fd >= 0) {
|
|
const char *req = "GET /ws HTTP/1.1\r\nHost: x\r\nUpgrade: websocket\r\nConnection: Upgrade\r\n"
|
|
"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\nSec-WebSocket-Version: 13\r\n\r\n";
|
|
unsigned char frames[16];
|
|
unsigned char reply[64];
|
|
char handshake[512];
|
|
ssize_t rgot;
|
|
|
|
send(fd, req, strlen(req), 0);
|
|
recv(fd, handshake, sizeof(handshake) - 1, 0); // consume the 101
|
|
// Frame 1: opcode 0x1 (text) FIN=0, masked, len 3 "hel" ^ {1,2,3,4}
|
|
frames[0] = 0x01; frames[1] = 0x83;
|
|
frames[2] = 1; frames[3] = 2; frames[4] = 3; frames[5] = 4;
|
|
frames[6] = (unsigned char)('h' ^ 1); frames[7] = (unsigned char)('e' ^ 2); frames[8] = (unsigned char)('l' ^ 3);
|
|
// Frame 2: opcode 0x0 (continuation) FIN=1, masked, len 2 "lo" ^ {1,2,3,4}
|
|
frames[9] = 0x80; frames[10] = 0x82;
|
|
frames[11] = 1; frames[12] = 2; frames[13] = 3; frames[14] = 4;
|
|
frames[15] = (unsigned char)('l' ^ 1);
|
|
send(fd, frames, sizeof(frames), 0);
|
|
send(fd, (const unsigned char[]){ (unsigned char)('o' ^ 2) }, 1, 0); // last masked byte
|
|
rgot = recv(fd, reply, sizeof(reply), 0);
|
|
// Server reply frame: 0x81, len 11, "echo: hello" -- the two fragments were reassembled.
|
|
CHECK(rgot >= 13 && reply[0] == 0x81 && reply[1] == 11 && memcmp(reply + 2, "echo: hello", 11) == 0,
|
|
"WebSocket reassembles a fragmented text message");
|
|
close(fd);
|
|
}
|
|
|
|
// --- A handler that ERRORS returns 500, and the server keeps serving afterward (pcall isolation) ---
|
|
fd = clientConnect(PORT);
|
|
if (fd >= 0) {
|
|
const char *req = "GET /boom HTTP/1.1\r\nHost: x\r\nConnection: close\r\n\r\n";
|
|
send(fd, req, strlen(req), 0);
|
|
CHECK(readResponse(fd, &status, body, sizeof(body)) && status == 500, "a handler runtime error becomes a 500");
|
|
close(fd);
|
|
}
|
|
|
|
// --- An abrupt RST mid-request must NOT kill the server (native I/O raises; handle's pcall catches) ---
|
|
fd = clientConnect(PORT);
|
|
if (fd >= 0) {
|
|
struct linger sl;
|
|
const char *partial = "GET /hi HTTP/1.1\r\nHost: x\r\n"; // no terminating CRLF -> server waits, then sees RST
|
|
sl.l_onoff = 1;
|
|
sl.l_linger = 0; // close() sends RST, not FIN
|
|
setsockopt(fd, SOL_SOCKET, SO_LINGER, &sl, sizeof(sl));
|
|
send(fd, partial, strlen(partial), 0);
|
|
close(fd);
|
|
}
|
|
{
|
|
struct timespec ts = { 0, 20000000 };
|
|
nanosleep(&ts, NULL); // let the server process the RST
|
|
}
|
|
fd = clientConnect(PORT);
|
|
if (fd >= 0) {
|
|
const char *req = "GET /hi HTTP/1.1\r\nHost: x\r\nConnection: close\r\n\r\n";
|
|
send(fd, req, strlen(req), 0);
|
|
CHECK(readResponse(fd, &status, body, sizeof(body)) && status == 200 && strcmp(body, "hello /hi") == 0,
|
|
"server survives a client RST mid-request and keeps serving");
|
|
close(fd);
|
|
}
|
|
|
|
// --- HTTP/1.1 PIPELINING: two requests in one write; the leftover buffer must carry the second ---
|
|
fd = clientConnect(PORT);
|
|
if (fd >= 0) {
|
|
const char *reqs = "GET /hi HTTP/1.1\r\nHost: x\r\nConnection: keep-alive\r\n\r\n"
|
|
"GET /made HTTP/1.1\r\nHost: x\r\nConnection: keep-alive\r\n\r\n";
|
|
bool one;
|
|
bool two;
|
|
send(fd, reqs, strlen(reqs), 0);
|
|
one = readResponse(fd, &status, body, sizeof(body)) && status == 200 && strcmp(body, "hello /hi") == 0;
|
|
two = readResponse(fd, &status, body, sizeof(body)) && status == 201 && strcmp(body, "created") == 0;
|
|
CHECK(one && two, "pipelined second request (buffered past the first) is served, not lost");
|
|
close(fd);
|
|
}
|
|
|
|
// --- SMUGGLING: a Transfer-Encoding request must be REJECTED (its trailing bytes must not be
|
|
// re-parsed as a smuggled pipelined request via the leftover buffer) ---
|
|
fd = clientConnect(PORT);
|
|
if (fd >= 0) {
|
|
const char *req = "POST /made HTTP/1.1\r\nHost: x\r\nTransfer-Encoding: chunked\r\nConnection: close\r\n\r\n"
|
|
"0\r\n\r\nGET /made HTTP/1.1\r\nHost: x\r\n\r\n";
|
|
bool served;
|
|
send(fd, req, strlen(req), 0);
|
|
served = readResponse(fd, &status, body, sizeof(body));
|
|
CHECK(!served || status != 201, "Transfer-Encoding request rejected; the smuggled GET /made is not served");
|
|
close(fd);
|
|
}
|
|
|
|
// --- SMUGGLING: conflicting duplicate Content-Length must be REJECTED (CL.CL desync) ---
|
|
fd = clientConnect(PORT);
|
|
if (fd >= 0) {
|
|
const char *req = "GET /hi HTTP/1.1\r\nHost: x\r\nContent-Length: 0\r\nContent-Length: 5\r\nConnection: close\r\n\r\nhello";
|
|
bool served;
|
|
send(fd, req, strlen(req), 0);
|
|
served = readResponse(fd, &status, body, sizeof(body));
|
|
CHECK(!served || status != 200, "conflicting duplicate Content-Length is rejected");
|
|
close(fd);
|
|
}
|
|
|
|
// --- HTTPS: a second context serves TLS with a generated self-signed cert (tcp tls option) ---
|
|
tls = NULL;
|
|
{
|
|
const char *certPath = "/tmp/calogHttpdTest-cert.pem";
|
|
const char *keyPath = "/tmp/calogHttpdTest-key.pem";
|
|
if (genCert(certPath, keyPath)) {
|
|
char *tlsScript;
|
|
char *tlsSource;
|
|
char tlsSetup[512];
|
|
atomic_store(&isReady, false);
|
|
tlsScript = loadScript("examples/httpd.lua");
|
|
snprintf(tlsSetup, sizeof(tlsSetup),
|
|
"\nlocal s = httpd.new()\n"
|
|
"s:route('GET', '/hi', function(req) return 'secure ' .. req.path end)\n"
|
|
"s:serve(38911, { tls = true, cert = '%s', key = '%s', keep = keepServing, onReady = ready })\n",
|
|
certPath, keyPath);
|
|
tlsSource = tlsScript != NULL ? (char *)malloc(strlen(tlsScript) + strlen(tlsSetup) + 64) : NULL;
|
|
if (tlsSource != NULL) {
|
|
sprintf(tlsSource, "httpd = (function()\n%s\nend)()\n%s", tlsScript, tlsSetup);
|
|
tls = calogContextOpen(calog, &calogLuaEngine);
|
|
calogContextEval(tls, tlsSource);
|
|
for (i = 0; i < PUMP_LIMIT && !atomic_load(&isReady); i++) {
|
|
struct timespec ts = { 0, 500000 };
|
|
calogPump(calog);
|
|
nanosleep(&ts, NULL);
|
|
}
|
|
CHECK(httpsGet(HTTPS_PORT, "/hi", &status, body, sizeof(body)) && status == 200 && strcmp(body, "secure /hi") == 0,
|
|
"HTTPS: the tcp transport's tls option serves an encrypted request");
|
|
|
|
// A bad handshake (plain HTTP to the TLS port) makes tcpAccept raise; the accept loop
|
|
// must survive it. Fire one, then confirm the TLS server still serves.
|
|
{
|
|
int bad;
|
|
bad = clientConnect(HTTPS_PORT);
|
|
if (bad >= 0) {
|
|
const char *probe = "GET / HTTP/1.1\r\nHost: x\r\n\r\n"; // not a TLS ClientHello
|
|
send(bad, probe, strlen(probe), 0);
|
|
close(bad);
|
|
}
|
|
{
|
|
struct timespec ts = { 0, 20000000 };
|
|
nanosleep(&ts, NULL);
|
|
}
|
|
CHECK(httpsGet(HTTPS_PORT, "/hi", &status, body, sizeof(body)) && status == 200,
|
|
"TLS server survives a bad/plain-HTTP handshake probe and keeps serving");
|
|
}
|
|
}
|
|
free(tlsScript);
|
|
free(tlsSource);
|
|
unlink(certPath);
|
|
unlink(keyPath);
|
|
} else {
|
|
printf("SKIP: openssl CLI could not generate a test cert (HTTPS check skipped)\n");
|
|
}
|
|
}
|
|
|
|
// Stop the accept loops and let the context threads unwind before teardown.
|
|
atomic_store(&serving, false);
|
|
for (i = 0; i < PUMP_LIMIT; i++) {
|
|
struct timespec ts = { 0, 500000 };
|
|
calogPump(calog);
|
|
nanosleep(&ts, NULL);
|
|
}
|
|
calogContextClose(lua);
|
|
if (tls != NULL) {
|
|
calogContextClose(tls);
|
|
}
|
|
calogDestroy(calog);
|
|
|
|
printf("\n%d checks, %d failed\n", testsRun, testsFailed);
|
|
fflush(stdout);
|
|
return testsFailed == 0 ? 0 : 1;
|
|
}
|