527 lines
9.2 KiB
C
527 lines
9.2 KiB
C
/**
|
|
* @file http/msg.c HTTP Message decode
|
|
*
|
|
* Copyright (C) 2010 Creytiv.com
|
|
*/
|
|
#include <re_types.h>
|
|
#include <re_mem.h>
|
|
#include <re_mbuf.h>
|
|
#include <re_sa.h>
|
|
#include <re_list.h>
|
|
#include <re_hash.h>
|
|
#include <re_fmt.h>
|
|
#include <re_http.h>
|
|
|
|
|
|
enum {
|
|
STARTLINE_MAX = 8192,
|
|
};
|
|
|
|
|
|
static void hdr_destructor(void *arg)
|
|
{
|
|
struct http_hdr *hdr = arg;
|
|
|
|
list_unlink(&hdr->le);
|
|
}
|
|
|
|
|
|
static void destructor(void *arg)
|
|
{
|
|
struct http_msg *msg = arg;
|
|
|
|
list_flush(&msg->hdrl);
|
|
mem_deref(msg->mb);
|
|
}
|
|
|
|
|
|
static enum http_hdrid hdr_hash(const struct pl *name)
|
|
{
|
|
if (!name->l)
|
|
return HTTP_HDR_NONE;
|
|
|
|
switch (name->p[0]) {
|
|
|
|
case 'x':
|
|
case 'X':
|
|
if (name->l > 1 && name->p[1] == '-')
|
|
return HTTP_HDR_NONE;
|
|
|
|
break;
|
|
}
|
|
|
|
return (enum http_hdrid)(hash_joaat_ci(name->p, name->l) & 0xfff);
|
|
}
|
|
|
|
|
|
static inline bool hdr_comma_separated(enum http_hdrid id)
|
|
{
|
|
switch (id) {
|
|
|
|
case HTTP_HDR_ACCEPT:
|
|
case HTTP_HDR_ACCEPT_CHARSET:
|
|
case HTTP_HDR_ACCEPT_ENCODING:
|
|
case HTTP_HDR_ACCEPT_LANGUAGE:
|
|
case HTTP_HDR_ACCEPT_RANGES:
|
|
case HTTP_HDR_ALLOW:
|
|
case HTTP_HDR_CACHE_CONTROL:
|
|
case HTTP_HDR_CONNECTION:
|
|
case HTTP_HDR_CONTENT_ENCODING:
|
|
case HTTP_HDR_CONTENT_LANGUAGE:
|
|
case HTTP_HDR_EXPECT:
|
|
case HTTP_HDR_IF_MATCH:
|
|
case HTTP_HDR_IF_NONE_MATCH:
|
|
case HTTP_HDR_PRAGMA:
|
|
case HTTP_HDR_SEC_WEBSOCKET_EXTENSIONS:
|
|
case HTTP_HDR_SEC_WEBSOCKET_PROTOCOL:
|
|
case HTTP_HDR_SEC_WEBSOCKET_VERSION:
|
|
case HTTP_HDR_TE:
|
|
case HTTP_HDR_TRAILER:
|
|
case HTTP_HDR_TRANSFER_ENCODING:
|
|
case HTTP_HDR_UPGRADE:
|
|
case HTTP_HDR_VARY:
|
|
case HTTP_HDR_VIA:
|
|
case HTTP_HDR_WARNING:
|
|
return true;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
static inline int hdr_add(struct http_msg *msg, const struct pl *name,
|
|
enum http_hdrid id, const char *p, ssize_t l)
|
|
{
|
|
struct http_hdr *hdr;
|
|
|
|
hdr = mem_zalloc(sizeof(*hdr), hdr_destructor);
|
|
if (!hdr)
|
|
return ENOMEM;
|
|
|
|
hdr->name = *name;
|
|
hdr->val.p = p;
|
|
hdr->val.l = MAX(l, 0);
|
|
hdr->id = id;
|
|
|
|
list_append(&msg->hdrl, &hdr->le, hdr);
|
|
|
|
/* parse common headers */
|
|
switch (id) {
|
|
|
|
case HTTP_HDR_CONTENT_TYPE:
|
|
msg->ctype = hdr->val;
|
|
break;
|
|
|
|
case HTTP_HDR_CONTENT_LENGTH:
|
|
msg->clen = pl_u32(&hdr->val);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* Decode a HTTP message
|
|
*
|
|
* @param msgp Pointer to allocated HTTP Message
|
|
* @param mb Buffer containing HTTP Message
|
|
* @param req True for request, false for response
|
|
*
|
|
* @return 0 if success, otherwise errorcode
|
|
*/
|
|
int http_msg_decode(struct http_msg **msgp, struct mbuf *mb, bool req)
|
|
{
|
|
struct pl b, s, e, name, scode;
|
|
const char *p, *cv;
|
|
struct http_msg *msg;
|
|
bool comsep, quote;
|
|
enum http_hdrid id = HTTP_HDR_NONE;
|
|
uint32_t ws, lf;
|
|
size_t l;
|
|
int err;
|
|
|
|
if (!msgp || !mb)
|
|
return EINVAL;
|
|
|
|
p = (const char *)mbuf_buf(mb);
|
|
l = mbuf_get_left(mb);
|
|
|
|
if (re_regex(p, l, "[\r\n]*[^\r\n]+[\r]*[\n]1", &b, &s, NULL, &e))
|
|
return (l > STARTLINE_MAX) ? EBADMSG : ENODATA;
|
|
|
|
msg = mem_zalloc(sizeof(*msg), destructor);
|
|
if (!msg)
|
|
return ENOMEM;
|
|
|
|
msg->mb = mem_ref(mb);
|
|
|
|
if (req) {
|
|
if (re_regex(s.p, s.l, "[a-z]+ [^? ]+[^ ]* HTTP/[0-9.]+",
|
|
&msg->met, &msg->path, &msg->prm, &msg->ver) ||
|
|
msg->met.p != s.p) {
|
|
err = EBADMSG;
|
|
goto out;
|
|
}
|
|
}
|
|
else {
|
|
if (re_regex(s.p, s.l, "HTTP/[0-9.]+ [0-9]+ [^]*",
|
|
&msg->ver, &scode, &msg->reason) ||
|
|
msg->ver.p != s.p + 5) {
|
|
err = EBADMSG;
|
|
goto out;
|
|
}
|
|
|
|
msg->scode = pl_u32(&scode);
|
|
}
|
|
|
|
l -= e.p + e.l - p;
|
|
p = e.p + e.l;
|
|
|
|
name.p = cv = NULL;
|
|
name.l = ws = lf = 0;
|
|
comsep = false;
|
|
quote = false;
|
|
|
|
for (; l > 0; p++, l--) {
|
|
|
|
switch (*p) {
|
|
|
|
case ' ':
|
|
case '\t':
|
|
lf = 0; /* folding */
|
|
++ws;
|
|
break;
|
|
|
|
case '\r':
|
|
++ws;
|
|
break;
|
|
|
|
case '\n':
|
|
++ws;
|
|
|
|
if (!name.p) {
|
|
++p; --l; /* no headers */
|
|
err = 0;
|
|
goto out;
|
|
}
|
|
|
|
if (!lf++)
|
|
break;
|
|
|
|
++p; --l; /* eoh */
|
|
|
|
/*@fallthrough@*/
|
|
|
|
default:
|
|
if (lf || (*p == ',' && comsep && !quote)) {
|
|
|
|
if (!name.l) {
|
|
err = EBADMSG;
|
|
goto out;
|
|
}
|
|
|
|
err = hdr_add(msg, &name, id, cv ? cv : p,
|
|
cv ? p - cv - ws : 0);
|
|
if (err)
|
|
goto out;
|
|
|
|
if (!lf) { /* comma separated */
|
|
cv = NULL;
|
|
break;
|
|
}
|
|
|
|
if (lf > 1) { /* eoh */
|
|
err = 0;
|
|
goto out;
|
|
}
|
|
|
|
comsep = false;
|
|
name.p = NULL;
|
|
cv = NULL;
|
|
lf = 0;
|
|
}
|
|
|
|
if (!name.p) {
|
|
name.p = p;
|
|
name.l = 0;
|
|
ws = 0;
|
|
}
|
|
|
|
if (!name.l) {
|
|
if (*p != ':') {
|
|
ws = 0;
|
|
break;
|
|
}
|
|
|
|
name.l = MAX((int)(p - name.p - ws), 0);
|
|
if (!name.l) {
|
|
err = EBADMSG;
|
|
goto out;
|
|
}
|
|
|
|
id = hdr_hash(&name);
|
|
comsep = hdr_comma_separated(id);
|
|
break;
|
|
}
|
|
|
|
if (!cv) {
|
|
quote = false;
|
|
cv = p;
|
|
}
|
|
|
|
if (*p == '"')
|
|
quote = !quote;
|
|
|
|
ws = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
err = ENODATA;
|
|
|
|
out:
|
|
if (err)
|
|
mem_deref(msg);
|
|
else {
|
|
*msgp = msg;
|
|
mb->pos = mb->end - l;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
/**
|
|
* Get a HTTP Header from a HTTP Message
|
|
*
|
|
* @param msg HTTP Message
|
|
* @param id HTTP Header ID
|
|
*
|
|
* @return HTTP Header if found, NULL if not found
|
|
*/
|
|
const struct http_hdr *http_msg_hdr(const struct http_msg *msg,
|
|
enum http_hdrid id)
|
|
{
|
|
return http_msg_hdr_apply(msg, true, id, NULL, NULL);
|
|
}
|
|
|
|
|
|
/**
|
|
* Apply a function handler to certain HTTP Headers
|
|
*
|
|
* @param msg HTTP Message
|
|
* @param fwd True to traverse forwards, false to traverse backwards
|
|
* @param id HTTP Header ID
|
|
* @param h Function handler
|
|
* @param arg Handler argument
|
|
*
|
|
* @return HTTP Header if handler returns true, otherwise NULL
|
|
*/
|
|
const struct http_hdr *http_msg_hdr_apply(const struct http_msg *msg,
|
|
bool fwd, enum http_hdrid id,
|
|
http_hdr_h *h, void *arg)
|
|
{
|
|
struct le *le;
|
|
|
|
if (!msg)
|
|
return NULL;
|
|
|
|
le = fwd ? msg->hdrl.head : msg->hdrl.tail;
|
|
|
|
while (le) {
|
|
const struct http_hdr *hdr = le->data;
|
|
|
|
le = fwd ? le->next : le->prev;
|
|
|
|
if (hdr->id != id)
|
|
continue;
|
|
|
|
if (!h || h(hdr, arg))
|
|
return hdr;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/**
|
|
* Get an unknown HTTP Header from a HTTP Message
|
|
*
|
|
* @param msg HTTP Message
|
|
* @param name Header name
|
|
*
|
|
* @return HTTP Header if found, NULL if not found
|
|
*/
|
|
const struct http_hdr *http_msg_xhdr(const struct http_msg *msg,
|
|
const char *name)
|
|
{
|
|
return http_msg_xhdr_apply(msg, true, name, NULL, NULL);
|
|
}
|
|
|
|
|
|
/**
|
|
* Apply a function handler to certain unknown HTTP Headers
|
|
*
|
|
* @param msg HTTP Message
|
|
* @param fwd True to traverse forwards, false to traverse backwards
|
|
* @param name HTTP Header name
|
|
* @param h Function handler
|
|
* @param arg Handler argument
|
|
*
|
|
* @return HTTP Header if handler returns true, otherwise NULL
|
|
*/
|
|
const struct http_hdr *http_msg_xhdr_apply(const struct http_msg *msg,
|
|
bool fwd, const char *name,
|
|
http_hdr_h *h, void *arg)
|
|
{
|
|
struct le *le;
|
|
struct pl pl;
|
|
|
|
if (!msg || !name)
|
|
return NULL;
|
|
|
|
pl_set_str(&pl, name);
|
|
|
|
le = fwd ? msg->hdrl.head : msg->hdrl.tail;
|
|
|
|
while (le) {
|
|
const struct http_hdr *hdr = le->data;
|
|
|
|
le = fwd ? le->next : le->prev;
|
|
|
|
if (pl_casecmp(&hdr->name, &pl))
|
|
continue;
|
|
|
|
if (!h || h(hdr, arg))
|
|
return hdr;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static bool count_handler(const struct http_hdr *hdr, void *arg)
|
|
{
|
|
uint32_t *n = arg;
|
|
(void)hdr;
|
|
|
|
++(*n);
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* Count the number of HTTP Headers
|
|
*
|
|
* @param msg HTTP Message
|
|
* @param id HTTP Header ID
|
|
*
|
|
* @return Number of HTTP Headers
|
|
*/
|
|
uint32_t http_msg_hdr_count(const struct http_msg *msg, enum http_hdrid id)
|
|
{
|
|
uint32_t n = 0;
|
|
|
|
http_msg_hdr_apply(msg, true, id, count_handler, &n);
|
|
|
|
return n;
|
|
}
|
|
|
|
|
|
/**
|
|
* Count the number of unknown HTTP Headers
|
|
*
|
|
* @param msg HTTP Message
|
|
* @param name HTTP Header name
|
|
*
|
|
* @return Number of HTTP Headers
|
|
*/
|
|
uint32_t http_msg_xhdr_count(const struct http_msg *msg, const char *name)
|
|
{
|
|
uint32_t n = 0;
|
|
|
|
http_msg_xhdr_apply(msg, true, name, count_handler, &n);
|
|
|
|
return n;
|
|
}
|
|
|
|
|
|
static bool value_handler(const struct http_hdr *hdr, void *arg)
|
|
{
|
|
return 0 == pl_strcasecmp(&hdr->val, (const char *)arg);
|
|
}
|
|
|
|
|
|
/**
|
|
* Check if a HTTP Header matches a certain value
|
|
*
|
|
* @param msg HTTP Message
|
|
* @param id HTTP Header ID
|
|
* @param value Header value to check
|
|
*
|
|
* @return True if value matches, false if not
|
|
*/
|
|
bool http_msg_hdr_has_value(const struct http_msg *msg, enum http_hdrid id,
|
|
const char *value)
|
|
{
|
|
return NULL != http_msg_hdr_apply(msg, true, id, value_handler,
|
|
(void *)value);
|
|
}
|
|
|
|
|
|
/**
|
|
* Check if an unknown HTTP Header matches a certain value
|
|
*
|
|
* @param msg HTTP Message
|
|
* @param name HTTP Header name
|
|
* @param value Header value to check
|
|
*
|
|
* @return True if value matches, false if not
|
|
*/
|
|
bool http_msg_xhdr_has_value(const struct http_msg *msg, const char *name,
|
|
const char *value)
|
|
{
|
|
return NULL != http_msg_xhdr_apply(msg, true, name, value_handler,
|
|
(void *)value);
|
|
}
|
|
|
|
|
|
/**
|
|
* Print a HTTP Message
|
|
*
|
|
* @param pf Print function for output
|
|
* @param msg HTTP Message
|
|
*
|
|
* @return 0 if success, otherwise errorcode
|
|
*/
|
|
int http_msg_print(struct re_printf *pf, const struct http_msg *msg)
|
|
{
|
|
struct le *le;
|
|
int err;
|
|
|
|
if (!msg)
|
|
return 0;
|
|
|
|
if (pl_isset(&msg->met))
|
|
err = re_hprintf(pf, "%r %r%r HTTP/%r\n", &msg->met,
|
|
&msg->path, &msg->prm, &msg->ver);
|
|
else
|
|
err = re_hprintf(pf, "HTTP/%r %u %r\n", &msg->ver, msg->scode,
|
|
&msg->reason);
|
|
|
|
for (le=msg->hdrl.head; le; le=le->next) {
|
|
|
|
const struct http_hdr *hdr = le->data;
|
|
|
|
err |= re_hprintf(pf, "%r: %r (%i)\n", &hdr->name, &hdr->val,
|
|
hdr->id);
|
|
}
|
|
|
|
return err;
|
|
}
|