diff seobeo/tests/seobeo_http_content_length_test.c @ 221:ce7f4400c2de hg-web

[hg-web] Harden forge and add changeset UI
author MrJuneJune <me@mrjunejune.com>
date Sun, 02 Aug 2026 09:01:24 -0700
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/seobeo/tests/seobeo_http_content_length_test.c	Sun Aug 02 09:01:24 2026 -0700
@@ -0,0 +1,91 @@
+#include "seobeo/seobeo.h"
+
+#include <arpa/inet.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+
+typedef struct {
+  int listener;
+} Content_Length_Server;
+
+static int64_t monotonic_milliseconds(void)
+{
+  struct timespec now;
+  clock_gettime(CLOCK_MONOTONIC, &now);
+  return (int64_t)now.tv_sec * 1000 + now.tv_nsec / 1000000;
+}
+
+static void *serve_lowercase_content_length(void *argument)
+{
+  Content_Length_Server *server = argument;
+  int client = accept(server->listener, NULL, NULL);
+  if (client >= 0) {
+    char request[1024];
+    (void)read(client, request, sizeof(request));
+    const char response[] =
+        "HTTP/1.1 200 OK\r\n"
+        "content-length: 4\r\n"
+        "Connection: keep-alive\r\n"
+        "\r\n"
+        "done";
+    (void)write(client, response, sizeof(response) - 1);
+    usleep(500000);
+    close(client);
+  }
+  return NULL;
+}
+
+int main(void)
+{
+  int listener = socket(AF_INET, SOCK_STREAM, 0);
+  if (listener < 0)
+    return 1;
+
+  struct sockaddr_in address = {
+    .sin_family = AF_INET,
+    .sin_addr.s_addr = htonl(INADDR_LOOPBACK),
+    .sin_port = 0,
+  };
+  if (bind(listener, (struct sockaddr *)&address, sizeof(address)) != 0 ||
+      listen(listener, 1) != 0) {
+    close(listener);
+    return 1;
+  }
+
+  socklen_t address_length = sizeof(address);
+  if (getsockname(listener, (struct sockaddr *)&address, &address_length) != 0) {
+    close(listener);
+    return 1;
+  }
+
+  Content_Length_Server server = {.listener = listener};
+  pthread_t thread;
+  if (pthread_create(&thread, NULL, serve_lowercase_content_length, &server) != 0) {
+    close(listener);
+    return 1;
+  }
+
+  char url[128];
+  snprintf(url, sizeof(url), "http://127.0.0.1:%u/", ntohs(address.sin_port));
+  Seobeo_Client_Request *request = Seobeo_Client_Request_Create(url);
+  Seobeo_Client_Request_Set_Timeout_Milliseconds(request, 1000);
+  int64_t started = monotonic_milliseconds();
+  Seobeo_Client_Response *response = Seobeo_Client_Request_Execute(request);
+  int64_t elapsed = monotonic_milliseconds() - started;
+
+  int failed = !response ||
+               response->body_length != 4 ||
+               memcmp(response->body, "done", 4) != 0 ||
+               elapsed > 300;
+  if (failed)
+    fprintf(stderr, "Case-insensitive Content-Length was not honored (%lldms)\n",
+            (long long)elapsed);
+
+  Seobeo_Client_Response_Destroy(response);
+  Seobeo_Client_Request_Destroy(request);
+  pthread_join(thread, NULL);
+  close(listener);
+  return failed;
+}