233 lines
5.7 KiB
C
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;
|
|
}
|