Compare commits

...

3 Commits

6 changed files with 135 additions and 32 deletions

View File

@ -97,4 +97,16 @@ uint64_t lnm_digits(uint64_t num);
*/ */
bool lnm_is_ascii(const char *s); bool lnm_is_ascii(const char *s);
/**
* Find the first case-insensitive occurence of s2 in s1.
*
* @param s1 pointer to string to look in
* @param s1_len length of s1
* @param s2 string to search for in s1
* @param s2_len length of s2
* @return pointer to start of matched string if found, or NULL otherwise
*/
const char *lnm_stristr(const char *s1, size_t s1_len, const char *s2,
size_t s2_len);
#endif #endif

View File

@ -14,7 +14,11 @@
#define LNM_HTTP_MAX_REQ_HEADERS 32 #define LNM_HTTP_MAX_REQ_HEADERS 32
#define LNM_HTTP_MAX_REGEX_GROUPS 4 #define LNM_HTTP_MAX_REGEX_GROUPS 4
typedef struct lnm_http_req_header { /**
* Internal representation of a header in a request, defined using offsets
* relative to the full buffer.
*/
typedef struct lnm_http_req_ihdr {
struct { struct {
size_t o; size_t o;
size_t len; size_t len;
@ -23,7 +27,7 @@ typedef struct lnm_http_req_header {
size_t o; size_t o;
size_t len; size_t len;
} value; } value;
} lnm_http_req_header; } lnm_http_req_ihhr;
/** /**
* Represents the parsed HTTP request * Represents the parsed HTTP request
@ -46,7 +50,7 @@ typedef struct lnm_http_req {
size_t len; size_t len;
} query; } query;
struct { struct {
lnm_http_req_header arr[LNM_HTTP_MAX_REQ_HEADERS]; lnm_http_req_ihhr arr[LNM_HTTP_MAX_REQ_HEADERS];
size_t len; size_t len;
} headers; } headers;
struct { struct {
@ -82,32 +86,44 @@ lnm_http_parse_err lnm_http_req_parse(lnm_http_req *req, char *buf, size_t len);
void lnm_http_req_reset(lnm_http_req *req); void lnm_http_req_reset(lnm_http_req *req);
/** /**
* Retrieve a specific header from the request. * Represents an actual header value, with offsets already resolved.
*/
typedef struct lnm_http_req_hdr {
struct {
const char *s;
size_t len;
} name;
struct {
const char *s;
size_t len;
} value;
} lnm_http_req_hdr;
/**
* Retrieve a known type header from the request.
* *
* Pointers retrieved from this function should never be used between step * Pointers retrieved from this function should never be used between step
* functions; simply request the header again if you need to. * functions; simply request the header again if you need to.
* *
* @param out where to write pointer to header value * @param out where to store pointer to struct representing header
* @param out_len where to store length of out value
* @param req request to look for header in * @param req request to look for header in
* @param type type of header to look for * @param type type of header to look for
*/ */
lnm_err lnm_http_req_header_get(const char **out, size_t *out_len, lnm_err lnm_http_req_header_get(lnm_http_req_hdr *out, lnm_http_req *req,
lnm_http_req *req, lnm_http_header type); lnm_http_header type);
/** /**
* Retrieve a specific header from the request by specifying its name. * Retrieve a header from the request using a case-insensitive name.
* *
* Pointers retrieved from this function should never be used between step * Pointers retrieved from this function should never be used between step
* functions; simply request the header again if you need to. * functions; simply request the header again if you need to.
* *
* @param out where to write pointer to header value * @param out where to store pointer to struct representing header
* @param out_len where to store length of out value
* @param req request to look for header in * @param req request to look for header in
* @param name name of the header; matches case-insensitive * @param name name of the header; matches case-insensitive
*/ */
lnm_err lnm_http_req_header_get_s(const char **out, size_t *out_len, lnm_err lnm_http_req_header_get_s(lnm_http_req_hdr *out, lnm_http_req *req,
lnm_http_req *req, const char *name); const char *name);
/** /**
* Retrieve a named key segment from the matched route. * Retrieve a named key segment from the matched route.
@ -119,4 +135,18 @@ lnm_err lnm_http_req_header_get_s(const char **out, size_t *out_len,
size_t lnm_http_req_route_segment(const char **out, lnm_http_req *req, size_t lnm_http_req_route_segment(const char **out, lnm_http_req *req,
const char *key); const char *key);
/**
* Retrieve a specific key-value parameter from a header.
*
* Pointers retrieved from this function should never be used between step
* functions; simply request the header again if you need to.
*
* @param out output struct
* @param header header to look in
* @param key name of the parameter to return from the header
*/
size_t lnm_http_req_header_param(const char **out,
const lnm_http_req_hdr *header,
const char *key);
#endif #endif

View File

@ -79,11 +79,10 @@ void lnm_http_loop_process_parse_headers(lnm_http_conn *conn) {
lnm_http_loop_ctx *ctx = conn->ctx; lnm_http_loop_ctx *ctx = conn->ctx;
lnm_http_req *req = &ctx->req; lnm_http_req *req = &ctx->req;
const char *value; lnm_http_req_hdr header;
size_t value_len; if (lnm_http_req_header_get(&header, req, lnm_http_header_content_length) ==
if (lnm_http_req_header_get(&value, &value_len, req, lnm_err_ok) {
lnm_http_header_content_length) == lnm_err_ok) { req->body.expected_len = lnm_atoi(header.value.s, header.value.len);
req->body.expected_len = lnm_atoi(value, value_len);
} }
ctx->state = lnm_http_loop_state_steps; ctx->state = lnm_http_loop_state_steps;

View File

@ -30,13 +30,13 @@ lnm_http_step_err lnm_http_loop_step_auth(lnm_http_conn *conn) {
// If there's no API key, requests are always authorized // If there's no API key, requests are always authorized
bool authorized = ctx->g->api_key == NULL; bool authorized = ctx->g->api_key == NULL;
const char *value; lnm_http_req_hdr header;
size_t value_len;
if (!authorized && lnm_http_req_header_get_s(&value, &value_len, &ctx->req, if (!authorized && lnm_http_req_header_get_s(&header, &ctx->req,
"X-Api-Key") == lnm_err_ok) { "X-Api-Key") == lnm_err_ok) {
authorized = (value_len == strlen(ctx->g->api_key)) && authorized =
(memcmp(value, ctx->g->api_key, value_len) == 0); (header.value.len == strlen(ctx->g->api_key)) &&
(memcmp(header.value.s, ctx->g->api_key, header.value.len) == 0);
} }
ctx->res.status = authorized ? ctx->res.status : lnm_http_status_unauthorized; ctx->res.status = authorized ? ctx->res.status : lnm_http_status_unauthorized;

View File

@ -88,23 +88,24 @@ void lnm_http_req_reset(lnm_http_req *req) {
memset(req, 0, sizeof(lnm_http_req)); memset(req, 0, sizeof(lnm_http_req));
} }
lnm_err lnm_http_req_header_get(const char **out, size_t *out_len, lnm_err lnm_http_req_header_get(lnm_http_req_hdr *out, lnm_http_req *req,
lnm_http_req *req, lnm_http_header type) { lnm_http_header type) {
return lnm_http_req_header_get_s(out, out_len, req, return lnm_http_req_header_get_s(out, req, lnm_http_header_names[type]);
lnm_http_header_names[type]);
} }
lnm_err lnm_http_req_header_get_s(const char **out, size_t *out_len, lnm_err lnm_http_req_header_get_s(lnm_http_req_hdr *out, lnm_http_req *req,
lnm_http_req *req, const char *name) { const char *name) {
size_t name_len = strlen(name); size_t name_len = strlen(name);
for (size_t i = 0; i < req->headers.len; i++) { for (size_t i = 0; i < req->headers.len; i++) {
const lnm_http_req_header *header = &req->headers.arr[i]; const lnm_http_req_ihhr *header = &req->headers.arr[i];
if (lnm_strnieq(req->buf.s + header->name.o, header->name.len, name, if (lnm_strnieq(req->buf.s + header->name.o, header->name.len, name,
name_len)) { name_len)) {
*out = req->buf.s + header->value.o; out->name.s = req->buf.s + header->name.o;
*out_len = header->value.len; out->name.len = header->name.len;
out->value.s = req->buf.s + header->value.o;
out->value.len = header->value.len;
return lnm_err_ok; return lnm_err_ok;
} }
@ -128,3 +129,50 @@ size_t lnm_http_req_route_segment(const char **out, lnm_http_req *req,
return segment->len; return segment->len;
} }
size_t lnm_http_req_header_param(const char **out,
const lnm_http_req_hdr *header,
const char *key) {
size_t key_len = strlen(key);
size_t remaining_len = header->value.len;
// The -1 ensures there's still space for an equals sign after the match
const char *s = lnm_stristr(header->value.s, remaining_len - 1, key, key_len);
// Skip any accidental matches of the key inside another part of the value
while (s != NULL && s[key_len] != '=') {
remaining_len -= key_len;
s = lnm_stristr(s + key_len, remaining_len - 1, key, key_len);
};
// Edge case where empty value is at the end of the string
if (s == NULL || s + 2 == header->value.s + header->value.len) {
return 0;
}
*out = s + 2;
const char *value_end = s + 2;
// Handle a quoted string
if (**out == '"') {
value_end++;
while (value_end < header->value.s + header->value.len &&
(*value_end != '"')) {
value_end++;
}
// If we found a matching quote we trim the quotes, otherwise the quote is
// part of the value
if (value_end < header->value.s + header->value.len) {
// We skip the initial quote
(*out)++;
}
} else {
while (value_end < header->value.s + header->value.len &&
(*value_end != ',') && (*value_end != ';')) {
value_end++;
}
}
return value_end - *out;
}

View File

@ -67,3 +67,17 @@ bool lnm_is_ascii(const char *s) {
return valid; return valid;
} }
const char *lnm_stristr(const char *s1, size_t s1_len, const char *s2,
size_t s2_len) {
while (s1_len >= s2_len) {
if (lnm_strnieq(s1, s1_len, s2, s2_len)) {
return s1;
}
s1_len--;
s1++;
}
return NULL;
}