| /** |
| * @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_msg.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); |
| 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; |
| int err = 0; |
| |
| 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: |
| err = msg_ctype_decode(&msg->ctyp, &hdr->val); |
| break; |
| |
| case HTTP_HDR_CONTENT_LENGTH: |
| msg->clen = pl_u32(&hdr->val); |
| break; |
| |
| default: |
| break; |
| } |
| |
| if (err) |
| mem_deref(hdr); |
| |
| return err; |
| } |
| |
| |
| /** |
| * 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); |
| |
| msg->mb = mbuf_alloc(8192); |
| if (!msg->mb) { |
| err = ENOMEM; |
| goto out; |
| } |
| |
| 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, NULL, &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; |
| } |