326 lines
8.2 KiB
C
326 lines
8.2 KiB
C
#include "request.h"
|
|
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <ctype.h>
|
|
|
|
// 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;
|
|
}
|