#include "request.h" #include #include #include // request_new creates a new request. request_t* request_new(arena_t *arena) { request_t *req = arena_alloc(arena, sizeof(request_t)); if (!req) return NULL; memset(req, 0, sizeof(request_t)); req->arena = arena; return req; } // parse_request_line parses the first line of HTTP request. static int parse_request_line(request_t *req, const char *line, size_t len) { const char *p = line; const char *end = line + len; // Find method const char *method_end = memchr(p, ' ', end - p); if (!method_end) return -1; req->method = arena_strdup(req->arena, p, method_end - p); p = method_end + 1; // Find path (may include query string) const char *path_end = memchr(p, ' ', end - p); if (!path_end) return -1; // Check for query string const char *query = memchr(p, '?', path_end - p); if (query) { req->path = arena_strdup(req->arena, p, query - p); req->query_string = arena_strdup(req->arena, query + 1, path_end - query - 1); } else { req->path = arena_strdup(req->arena, p, path_end - p); } p = path_end + 1; // Find version const char *version_end = end; if (version_end > p && *(version_end - 1) == '\r') version_end--; req->version = arena_strdup(req->arena, p, version_end - p); return 0; } // add_header adds a header to the request. static void add_header(request_t *req, const char *name, size_t name_len, const char *value, size_t value_len) { header_t *h = arena_alloc(req->arena, sizeof(header_t)); if (!h) return; h->name = arena_strdup(req->arena, name, name_len); h->value = arena_strdup(req->arena, value, value_len); h->next = req->headers; req->headers = h; req->header_count++; } // request_parse parses an HTTP request. int request_parse(request_t *req, const char *buf, size_t len) { const char *p = buf; const char *end = buf + len; // Find end of request line const char *line_end = memchr(p, '\n', end - p); if (!line_end) return -1; // Parse request line if (parse_request_line(req, p, line_end - p) < 0) return -1; p = line_end + 1; // Parse headers while (p < end) { // Check for end of headers if (*p == '\r' && p + 1 < end && *(p + 1) == '\n') { p += 2; break; } else if (*p == '\n') { p++; break; } // Find header name const char *colon = memchr(p, ':', end - p); if (!colon) break; const char *name_end = colon; while (name_end > p && isspace(*(name_end - 1))) name_end--; // Find header value const char *value_start = colon + 1; while (value_start < end && isspace(*value_start)) value_start++; const char *value_end = memchr(value_start, '\n', end - value_start); if (!value_end) value_end = end; if (value_end > value_start && *(value_end - 1) == '\r') value_end--; // Add header add_header(req, p, name_end - p, value_start, value_end - value_start); p = value_end; if (p < end && *p == '\r') p++; if (p < end && *p == '\n') p++; } // Store body if (p < end) { req->body_len = end - p; req->body = arena_strdup(req->arena, p, req->body_len); } return 0; } // request_header returns a header value. const char* request_header(request_t *req, const char *name) { header_t *h = req->headers; while (h) { if (strcasecmp(h->name, name) == 0) { return h->value; } h = h->next; } return NULL; } // request_has_header checks if header exists. int request_has_header(request_t *req, const char *name) { return request_header(req, name) != NULL; } // request_set_params sets route parameters. void request_set_params(request_t *req, char **names, char **values, int count) { req->param_names = names; req->param_values = values; req->param_count = count; } // request_param returns a route parameter. const char* request_param(request_t *req, const char *name) { for (int i = 0; i < req->param_count; i++) { if (req->param_names[i] && strcmp(req->param_names[i], name) == 0) { return req->param_values[i]; } } return NULL; } // parse_url_encoded parses application/x-www-form-urlencoded data. static void parse_url_encoded(request_t *req, const char *data, size_t len, char ***keys, char ***values, int *count) { if (!data || len == 0) return; // Count parameters int n = 1; for (size_t i = 0; i < len; i++) { if (data[i] == '&') n++; } *keys = arena_alloc(req->arena, n * sizeof(char*)); *values = arena_alloc(req->arena, n * sizeof(char*)); *count = 0; const char *p = data; const char *end = data + len; while (p < end) { // Find key const char *eq = memchr(p, '=', end - p); const char *amp = memchr(p, '&', end - p); if (!amp) amp = end; if (eq && eq < amp) { // Key=value pair (*keys)[*count] = arena_strdup(req->arena, p, eq - p); (*values)[*count] = arena_strdup(req->arena, eq + 1, amp - eq - 1); (*count)++; } else { // Key only (*keys)[*count] = arena_strdup(req->arena, p, amp - p); (*values)[*count] = ""; (*count)++; } p = amp + 1; } } // request_parse_query parses query parameters. int request_parse_query(request_t *req) { if (req->query_count > 0) return 0; // already parsed parse_url_encoded(req, req->query_string, req->query_string ? strlen(req->query_string) : 0, &req->query_keys, &req->query_values, &req->query_count); return 0; } // request_query returns a query parameter. const char* request_query(request_t *req, const char *name) { request_parse_query(req); for (int i = 0; i < req->query_count; i++) { if (strcmp(req->query_keys[i], name) == 0) { return req->query_values[i]; } } return NULL; } // request_parse_cookies parses cookies from Cookie header. int request_parse_cookies(request_t *req) { if (req->cookie_count > 0) return 0; // already parsed const char *cookies = request_header(req, "Cookie"); if (!cookies) return 0; // Count cookies int n = 1; for (const char *p = cookies; *p; p++) { if (*p == ';') n++; } req->cookie_names = arena_alloc(req->arena, n * sizeof(char*)); req->cookie_values = arena_alloc(req->arena, n * sizeof(char*)); req->cookie_count = 0; const char *p = cookies; while (*p) { // Skip whitespace while (*p && isspace(*p)) p++; if (!*p) break; // Find name const char *eq = strchr(p, '='); const char *semi = strchr(p, ';'); if (!semi) semi = p + strlen(p); if (eq && eq < semi) { req->cookie_names[req->cookie_count] = arena_strdup(req->arena, p, eq - p); req->cookie_values[req->cookie_count] = arena_strdup(req->arena, eq + 1, semi - eq - 1); req->cookie_count++; } p = semi; if (*p == ';') p++; } return 0; } // request_cookie returns a cookie value. const char* request_cookie(request_t *req, const char *name) { request_parse_cookies(req); for (int i = 0; i < req->cookie_count; i++) { if (strcmp(req->cookie_names[i], name) == 0) { return req->cookie_values[i]; } } return NULL; } // request_body returns the request body. const char* request_body(request_t *req) { return req->body; } // request_body_len returns the body length. size_t request_body_len(request_t *req) { return req->body_len; } // request_content_type returns the content type. const char* request_content_type(request_t *req) { const char *ct = request_header(req, "Content-Type"); if (!ct) return NULL; // Return content type without charset static char type[128]; const char *semi = strchr(ct, ';'); if (semi) { size_t len = semi - ct; if (len >= sizeof(type)) len = sizeof(type) - 1; memcpy(type, ct, len); type[len] = '\0'; return type; } return ct; } // request_is_json checks if request has JSON content. int request_is_json(request_t *req) { const char *ct = request_content_type(req); return ct && strcmp(ct, "application/json") == 0; } // request_is_form checks if request has form data. int request_is_form(request_t *req) { const char *ct = request_content_type(req); return ct && strcmp(ct, "application/x-www-form-urlencoded") == 0; } // request_expects_json checks if client expects JSON response. int request_expects_json(request_t *req) { const char *accept = request_header(req, "Accept"); if (accept && strstr(accept, "application/json")) return 1; const char *xhr = request_header(req, "X-Requested-With"); if (xhr && strcmp(xhr, "XMLHttpRequest") == 0) return 1; return 0; }