244 lines
6.3 KiB
C
244 lines
6.3 KiB
C
#include "context.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <stdarg.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <sys/stat.h>
|
|
|
|
// context_new creates a new context for a connection.
|
|
context_t* context_new(conn_t *conn) {
|
|
// Use connection's arena for all allocations
|
|
arena_t *arena = conn->arena;
|
|
|
|
context_t *ctx = arena_alloc(arena, sizeof(context_t));
|
|
if (!ctx) return NULL;
|
|
|
|
ctx->conn = conn;
|
|
ctx->arena = arena;
|
|
ctx->data = NULL;
|
|
|
|
// Create request
|
|
ctx->request = request_new(arena);
|
|
if (!ctx->request) return NULL;
|
|
|
|
// Parse request from connection buffer
|
|
if (request_parse(ctx->request, conn->rbuf, conn->rlen) < 0) {
|
|
return NULL;
|
|
}
|
|
|
|
// Create response
|
|
ctx->response = response_new();
|
|
if (!ctx->response) return NULL;
|
|
|
|
return ctx;
|
|
}
|
|
|
|
// context_free frees the context.
|
|
void context_free(context_t *ctx) {
|
|
if (!ctx) return;
|
|
|
|
// Response is malloced separately
|
|
response_free(ctx->response);
|
|
|
|
// Everything else is in arena and will be freed with connection
|
|
}
|
|
|
|
// context_set stores data in context.
|
|
void context_set(context_t *ctx, const char *key, void *value) {
|
|
context_data_t *d = arena_alloc(ctx->arena, sizeof(context_data_t));
|
|
if (!d) return;
|
|
|
|
d->key = arena_strdup(ctx->arena, key, strlen(key));
|
|
d->value = value;
|
|
d->next = ctx->data;
|
|
ctx->data = d;
|
|
}
|
|
|
|
// context_get retrieves data from context.
|
|
void* context_get(context_t *ctx, const char *key) {
|
|
context_data_t *d = ctx->data;
|
|
while (d) {
|
|
if (strcmp(d->key, key) == 0) {
|
|
return d->value;
|
|
}
|
|
d = d->next;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
// Request helpers
|
|
const char* ctx_param(context_t *ctx, const char *name) {
|
|
return request_param(ctx->request, name);
|
|
}
|
|
|
|
const char* ctx_query(context_t *ctx, const char *name) {
|
|
return request_query(ctx->request, name);
|
|
}
|
|
|
|
const char* ctx_header(context_t *ctx, const char *name) {
|
|
return request_header(ctx->request, name);
|
|
}
|
|
|
|
const char* ctx_cookie(context_t *ctx, const char *name) {
|
|
return request_cookie(ctx->request, name);
|
|
}
|
|
|
|
const char* ctx_body(context_t *ctx) {
|
|
return request_body(ctx->request);
|
|
}
|
|
|
|
// Response helpers
|
|
void ctx_status(context_t *ctx, int code) {
|
|
response_status(ctx->response, code);
|
|
}
|
|
|
|
void ctx_header_set(context_t *ctx, const char *name, const char *value) {
|
|
response_header(ctx->response, name, value);
|
|
}
|
|
|
|
// ctx_text sends plain text response.
|
|
void ctx_text(context_t *ctx, int status, const char *text) {
|
|
response_text(ctx->response, status, text);
|
|
ctx_send(ctx);
|
|
}
|
|
|
|
// ctx_html sends HTML response.
|
|
void ctx_html(context_t *ctx, int status, const char *html) {
|
|
response_html(ctx->response, status, html);
|
|
ctx_send(ctx);
|
|
}
|
|
|
|
// ctx_json sends JSON response.
|
|
void ctx_json(context_t *ctx, int status, const char *json) {
|
|
response_json(ctx->response, status, json);
|
|
ctx_send(ctx);
|
|
}
|
|
|
|
// ctx_redirect sends redirect response.
|
|
void ctx_redirect(context_t *ctx, const char *url) {
|
|
response_redirect(ctx->response, url, 302);
|
|
ctx_send(ctx);
|
|
}
|
|
|
|
// ctx_error sends error response.
|
|
void ctx_error(context_t *ctx, int status, const char *message) {
|
|
// Check if client expects JSON
|
|
if (request_expects_json(ctx->request)) {
|
|
char json[256];
|
|
snprintf(json, sizeof(json), "{\"error\":\"%s\"}",
|
|
message ? message : "Unknown error");
|
|
response_json(ctx->response, status, json);
|
|
} else {
|
|
response_text(ctx->response, status,
|
|
message ? message : "Error");
|
|
}
|
|
ctx_send(ctx);
|
|
}
|
|
|
|
// ctx_send sends the response to client.
|
|
int ctx_send(context_t *ctx) {
|
|
conn_t *c = ctx->conn;
|
|
|
|
// Serialize response to connection write buffer
|
|
c->wlen = response_serialize(ctx->response, c->wbuf, c->wsize);
|
|
c->wpos = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
// ctx_file sends a file as response.
|
|
void ctx_file(context_t *ctx, const char *path) {
|
|
int fd = open(path, O_RDONLY);
|
|
if (fd < 0) {
|
|
ctx_error(ctx, 404, "File not found");
|
|
return;
|
|
}
|
|
|
|
struct stat st;
|
|
if (fstat(fd, &st) < 0) {
|
|
close(fd);
|
|
ctx_error(ctx, 500, "Cannot stat file");
|
|
return;
|
|
}
|
|
|
|
// Determine content type from extension
|
|
const char *content_type = "application/octet-stream";
|
|
const char *ext = strrchr(path, '.');
|
|
if (ext) {
|
|
if (strcmp(ext, ".html") == 0) content_type = "text/html";
|
|
else if (strcmp(ext, ".css") == 0) content_type = "text/css";
|
|
else if (strcmp(ext, ".js") == 0) content_type = "application/javascript";
|
|
else if (strcmp(ext, ".json") == 0) content_type = "application/json";
|
|
else if (strcmp(ext, ".png") == 0) content_type = "image/png";
|
|
else if (strcmp(ext, ".jpg") == 0 || strcmp(ext, ".jpeg") == 0) content_type = "image/jpeg";
|
|
else if (strcmp(ext, ".gif") == 0) content_type = "image/gif";
|
|
else if (strcmp(ext, ".svg") == 0) content_type = "image/svg+xml";
|
|
else if (strcmp(ext, ".txt") == 0) content_type = "text/plain";
|
|
else if (strcmp(ext, ".xml") == 0) content_type = "application/xml";
|
|
}
|
|
|
|
// For small files, read and send directly
|
|
if (st.st_size < 1024 * 1024) { // 1MB threshold
|
|
char *buf = malloc(st.st_size);
|
|
if (buf) {
|
|
if (read(fd, buf, st.st_size) == st.st_size) {
|
|
ctx_status(ctx, 200);
|
|
ctx_header_set(ctx, "Content-Type", content_type);
|
|
ctx_write(ctx, buf, st.st_size);
|
|
ctx_send(ctx);
|
|
}
|
|
free(buf);
|
|
}
|
|
} else {
|
|
// File too large - send error for now
|
|
ctx_error(ctx, 413, "File too large");
|
|
}
|
|
|
|
close(fd);
|
|
}
|
|
|
|
// ctx_write writes data to response body.
|
|
void ctx_write(context_t *ctx, const char *data, size_t len) {
|
|
if (!ctx->response) return;
|
|
|
|
// If no body yet, allocate
|
|
if (!ctx->response->body) {
|
|
ctx->response->body = malloc(len);
|
|
if (ctx->response->body) {
|
|
memcpy(ctx->response->body, data, len);
|
|
ctx->response->body_len = len;
|
|
}
|
|
} else {
|
|
// Append to existing body
|
|
char *new_body = realloc(ctx->response->body, ctx->response->body_len + len);
|
|
if (new_body) {
|
|
memcpy(new_body + ctx->response->body_len, data, len);
|
|
ctx->response->body = new_body;
|
|
ctx->response->body_len += len;
|
|
}
|
|
}
|
|
}
|
|
|
|
// ctx_printf writes formatted data to response body.
|
|
void ctx_printf(context_t *ctx, const char *fmt, ...) {
|
|
char buf[4096];
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
int len = vsnprintf(buf, sizeof(buf), fmt, args);
|
|
va_end(args);
|
|
|
|
if (len > 0 && len < (int)sizeof(buf)) {
|
|
ctx_write(ctx, buf, len);
|
|
}
|
|
}
|
|
|
|
// ctx_send_chunk sends a chunk of data (for streaming).
|
|
int ctx_send_chunk(context_t *ctx, const char *data, size_t len) {
|
|
// For now, just accumulate and send
|
|
ctx_write(ctx, data, len);
|
|
return 0;
|
|
}
|