Web/response.c
2025-09-15 12:57:54 -05:00

233 lines
5.7 KiB
C

#include "response.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
// Status text lookup
static const char* get_status_text(int code) {
switch (code) {
case 200: return "OK";
case 201: return "Created";
case 204: return "No Content";
case 301: return "Moved Permanently";
case 302: return "Found";
case 304: return "Not Modified";
case 400: return "Bad Request";
case 401: return "Unauthorized";
case 403: return "Forbidden";
case 404: return "Not Found";
case 405: return "Method Not Allowed";
case 500: return "Internal Server Error";
case 502: return "Bad Gateway";
case 503: return "Service Unavailable";
default: return "Unknown";
}
}
// response_new creates a new response.
response_t* response_new(void) {
response_t *res = calloc(1, sizeof(response_t));
if (!res) return NULL;
res->status_code = 200;
res->body_cap = 8192;
res->body = malloc(res->body_cap);
return res;
}
// response_free destroys a response.
void response_free(response_t *res) {
if (!res) return;
response_header_t *h = res->headers;
while (h) {
response_header_t *next = h->next;
free(h->name);
free(h->value);
free(h);
h = next;
}
free(res->body);
free(res);
}
// response_reset clears the response.
void response_reset(response_t *res) {
res->status_code = 200;
res->status_text = NULL;
// Clear headers
response_header_t *h = res->headers;
while (h) {
response_header_t *next = h->next;
free(h->name);
free(h->value);
free(h);
h = next;
}
res->headers = NULL;
res->body_len = 0;
res->headers_sent = 0;
}
// response_status sets the status code.
response_t* response_status(response_t *res, int code) {
res->status_code = code;
return res;
}
// response_header adds a header.
response_t* response_header(response_t *res, const char *name, const char *value) {
response_header_t *h = malloc(sizeof(response_header_t));
if (!h) return res;
h->name = strdup(name);
h->value = strdup(value);
h->next = res->headers;
res->headers = h;
return res;
}
// response_content_type sets Content-Type header.
response_t* response_content_type(response_t *res, const char *type) {
return response_header(res, "Content-Type", type);
}
// response_content_length sets Content-Length header.
response_t* response_content_length(response_t *res, size_t len) {
char buf[32];
snprintf(buf, sizeof(buf), "%zu", len);
return response_header(res, "Content-Length", buf);
}
// ensure_capacity ensures body buffer has enough space.
static int ensure_capacity(response_t *res, size_t needed) {
if (res->body_len + needed > res->body_cap) {
size_t new_cap = res->body_cap * 2;
while (new_cap < res->body_len + needed) new_cap *= 2;
char *new_body = realloc(res->body, new_cap);
if (!new_body) return -1;
res->body = new_body;
res->body_cap = new_cap;
}
return 0;
}
// response_body sets the body.
response_t* response_body(response_t *res, const char *data, size_t len) {
if (ensure_capacity(res, len) < 0) return res;
memcpy(res->body, data, len);
res->body_len = len;
return res;
}
// response_write appends to body.
response_t* response_write(response_t *res, const char *data, size_t len) {
if (ensure_capacity(res, len) < 0) return res;
memcpy(res->body + res->body_len, data, len);
res->body_len += len;
return res;
}
// response_text sends plain text.
response_t* response_text(response_t *res, int status, const char *text) {
response_status(res, status);
response_content_type(res, "text/plain");
response_body(res, text, strlen(text));
return res;
}
// response_html sends HTML.
response_t* response_html(response_t *res, int status, const char *html) {
response_status(res, status);
response_content_type(res, "text/html; charset=utf-8");
response_body(res, html, strlen(html));
return res;
}
// response_json sends JSON.
response_t* response_json(response_t *res, int status, const char *json) {
response_status(res, status);
response_content_type(res, "application/json");
response_body(res, json, strlen(json));
return res;
}
// response_redirect sends a redirect.
response_t* response_redirect(response_t *res, const char *url, int status) {
if (status == 0) status = 302;
response_status(res, status);
response_header(res, "Location", url);
return res;
}
// response_cookie sets a cookie.
response_t* response_cookie(response_t *res, const char *name, const char *value,
const char *path, int max_age, int http_only, int secure) {
char cookie[1024];
int len = snprintf(cookie, sizeof(cookie), "%s=%s", name, value);
if (path) {
len += snprintf(cookie + len, sizeof(cookie) - len, "; Path=%s", path);
}
if (max_age > 0) {
len += snprintf(cookie + len, sizeof(cookie) - len, "; Max-Age=%d", max_age);
}
if (http_only) {
len += snprintf(cookie + len, sizeof(cookie) - len, "; HttpOnly");
}
if (secure) {
len += snprintf(cookie + len, sizeof(cookie) - len, "; Secure");
}
len += snprintf(cookie + len, sizeof(cookie) - len, "; SameSite=Lax");
return response_header(res, "Set-Cookie", cookie);
}
// response_serialize writes response to buffer.
int response_serialize(response_t *res, char *buf, size_t size) {
int len = 0;
// Status line
len = snprintf(buf, size, "HTTP/1.1 %d %s\r\n",
res->status_code, get_status_text(res->status_code));
// Headers
response_header_t *h = res->headers;
while (h) {
len += snprintf(buf + len, size - len, "%s: %s\r\n", h->name, h->value);
h = h->next;
}
// Content-Length if body exists
if (res->body_len > 0) {
len += snprintf(buf + len, size - len, "Content-Length: %zu\r\n", res->body_len);
}
// End headers
len += snprintf(buf + len, size - len, "\r\n");
// Body
if (res->body_len > 0 && len + res->body_len < size) {
memcpy(buf + len, res->body, res->body_len);
len += res->body_len;
}
return len;
}