freeswitch/libs/sofia-sip/libsofia-sip-ua/msg/msg_parser.c

3043 lines
72 KiB
C
Raw Blame History

/*
* This file is part of the Sofia-SIP package
*
* Copyright (C) 2005 Nokia Corporation.
*
* Contact: Pekka Pessi <pekka.pessi@nokia.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*
*/
/**@ingroup msg_parser
* @CFILE msg_parser.c
*
* HTTP-like message parser engine.
*
* @author Pekka Pessi <Pekka.Pessi@nokia.com>
*
* @date Created: Thu Oct 5 14:01:24 2000 ppessi
*
*/
/*#define NDEBUG*/
#include "config.h"
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <assert.h>
#include <limits.h>
#include <errno.h>
#include <stdarg.h>
#include <sofia-sip/su_tagarg.h>
#include <sofia-sip/su.h>
#include <sofia-sip/su_alloc.h>
#include "msg_internal.h"
#include "sofia-sip/msg_header.h"
#include "sofia-sip/bnf.h"
#include "sofia-sip/msg_parser.h"
#include "sofia-sip/msg_mclass.h"
#include "sofia-sip/msg_mclass_hash.h"
#include "sofia-sip/msg_mime.h"
#if HAVE_FUNC
#elif HAVE_FUNCTION
#define __func__ __FUNCTION__
#else
static char const __func__[] = "msg_parser";
#endif
static int _msg_header_add_dup_as(msg_t *msg,
msg_pub_t *pub,
msg_hclass_t *hc,
msg_header_t const *src);
static void msg_insert_chain(msg_t *msg, msg_pub_t *pub, int prepend,
msg_header_t **head, msg_header_t *h);
static void msg_insert_here_in_chain(msg_t *msg,
msg_header_t **prev,
msg_header_t *h);
static inline msg_header_t *msg_chain_remove(msg_t *msg, msg_header_t *h);
#ifndef NDEBUG
static int msg_chain_loop(msg_header_t const *h);
static int msg_chain_errors(msg_header_t const *h);
#endif
/* ====================================================================== */
/* Message properties */
/** Get message flags. */
unsigned msg_get_flags(msg_t const *msg, unsigned mask)
{
return msg ? msg->m_object->msg_flags & mask : 0;
}
/** Set message flags. */
unsigned msg_set_flags(msg_t *msg, unsigned mask)
{
return msg ? msg->m_object->msg_flags |= mask : 0;
}
/** Clear message flags. */
unsigned msg_zap_flags(msg_t *msg, unsigned mask)
{
return msg ? msg->m_object->msg_flags &= ~mask : 0;
}
/** Test if streaming is in progress. */
int msg_is_streaming(msg_t const *msg)
{
return msg && msg->m_streaming != 0;
}
/** Enable/disable streaming */
void msg_set_streaming(msg_t *msg, enum msg_streaming_status what)
{
if (msg)
msg->m_streaming = what != 0;
}
/* ---------------------------------------------------------------------- */
/** Test if header is not in the chain */
#define msg_header_is_removed(h) ((h)->sh_prev == NULL)
static inline int msg_is_request(msg_header_t const *h)
{
return h->sh_class->hc_hash == msg_request_hash;
}
static inline int msg_is_status(msg_header_t const *h)
{
return h->sh_class->hc_hash == msg_status_hash;
}
/* ====================================================================== */
/* Message buffer management */
/** Allocate a buffer of @a size octets, with slack of #msg_min_size. */
void *msg_buf_alloc(msg_t *msg, usize_t size)
{
struct msg_mbuffer_s *mb = msg->m_buffer;
size_t room = mb->mb_size - mb->mb_commit - mb->mb_used;
size_t target_size;
if (mb->mb_data && room >= (unsigned)size)
return mb->mb_data + mb->mb_used + mb->mb_commit;
target_size =
msg_min_size * ((size + mb->mb_commit) / msg_min_size + 1) - mb->mb_commit;
return msg_buf_exact(msg, target_size);
}
/** Allocate a buffer exactly of @a size octets, without any slack. */
void *msg_buf_exact(msg_t *msg, usize_t size)
{
struct msg_mbuffer_s *mb = msg->m_buffer;
size_t room = mb->mb_size - mb->mb_commit - mb->mb_used;
char *buffer;
int realloc;
if (mb->mb_data && room >= (unsigned)size)
return mb->mb_data + mb->mb_used + mb->mb_commit;
size += mb->mb_commit;
if (msg->m_maxsize && msg->m_size + size > msg->m_maxsize + 1) {
msg->m_object->msg_flags |= MSG_FLG_TOOLARGE;
errno = msg->m_errno = ENOBUFS;
return NULL;
}
realloc = !mb->mb_used && !msg->m_set_buffer;
if (realloc)
buffer = su_realloc(msg->m_home, mb->mb_data, size);
else
buffer = su_alloc(msg->m_home, size);
if (!buffer)
return NULL;
if (!realloc && mb->mb_commit && mb->mb_data)
memcpy(buffer, mb->mb_data + mb->mb_used, mb->mb_commit);
msg->m_set_buffer = 0;
mb->mb_data = buffer;
mb->mb_size = size;
mb->mb_used = 0;
return buffer + mb->mb_commit;
}
/** Commit data into buffer. */
usize_t msg_buf_commit(msg_t *msg, usize_t size, int eos)
{
if (msg) {
struct msg_mbuffer_s *mb = msg->m_buffer;
assert(mb->mb_used + mb->mb_commit + size <= mb->mb_size);
mb->mb_commit += size;
mb->mb_eos = eos;
if (mb->mb_used == 0 && !msg->m_chunk && !msg->m_set_buffer) {
size_t slack = mb->mb_size - mb->mb_commit;
if (eos || slack >= msg_min_size) {
/* realloc and cut down buffer */
size_t new_size;
void *new_data;
if (eos)
new_size = mb->mb_commit + 1;
else
new_size = mb->mb_commit + msg_min_size;
new_data = su_realloc(msg->m_home, mb->mb_data, new_size);
if (new_data) {
mb->mb_data = new_data, mb->mb_size = new_size;
}
}
}
}
return 0;
}
/** Get length of committed data */
usize_t msg_buf_committed(msg_t const *msg)
{
if (msg)
return msg->m_buffer->mb_commit;
else
return 0;
}
/** Get committed data */
void *msg_buf_committed_data(msg_t const *msg)
{
return msg && msg->m_buffer->mb_data ?
msg->m_buffer->mb_data + msg->m_buffer->mb_used
: NULL;
}
usize_t msg_buf_size(msg_t const *msg)
{
assert(msg);
if (msg) {
struct msg_mbuffer_s const *mb = msg->m_buffer;
return mb->mb_size - mb->mb_commit - mb->mb_used;
}
else
return 0;
}
static inline
void msg_buf_used(msg_t *msg, usize_t used)
{
msg->m_size += used;
msg->m_buffer->mb_used += used;
if (msg->m_buffer->mb_commit > used)
msg->m_buffer->mb_commit -= used;
else
msg->m_buffer->mb_commit = 0;
}
/** Set buffer. */
void msg_buf_set(msg_t *msg, void *b, usize_t size)
{
if (msg) {
struct msg_mbuffer_s *mb = msg->m_buffer;
assert(!msg->m_set_buffer); /* This can be set only once */
mb->mb_data = b;
mb->mb_size = size;
mb->mb_used = 0;
mb->mb_commit = 0;
mb->mb_eos = 0;
msg->m_set_buffer = 1;
}
}
/** Move unparsed data from src to dst */
void *msg_buf_move(msg_t *dst, msg_t const *src)
{
void *retval;
struct msg_mbuffer_s *db = dst->m_buffer;
struct msg_mbuffer_s const *sb = src->m_buffer;
if (!dst || !src)
return NULL;
if (sb->mb_eos)
retval = msg_buf_exact(dst, sb->mb_commit + 1);
else
retval = msg_buf_alloc(dst, sb->mb_commit + 1);
if (retval == NULL)
return NULL;
memcpy(retval, sb->mb_data + sb->mb_used, sb->mb_commit);
db->mb_commit += sb->mb_commit;
db->mb_eos = sb->mb_eos;
return retval;
}
/**Obtain I/O vector for receiving the data.
*
* @relatesalso msg_s
*
* Allocate buffers for receiving @a n bytes
* of data available from network. Function returns the buffers in the I/O vector
* @a vec. The @a vec is allocated by the caller, the available length is
* given as @a veclen. If the protocol is message-oriented like UDP or SCTP
* and the available data ends at message boundary, the caller should set
* the @a exact as 1. Otherwise some extra buffer (known as @em slack) is
* allocated).
*
* Currently, the msg_recv_iovec() allocates receive buffers in at most two
* blocks, so the caller should allocate at least two elements for the I/O
* vector @a vec.
*
* @param[in] msg message object
* @param[out] vec I/O vector
* @param[in] veclen available length of @a vec
* @param[in] n number of possibly available bytes<65>
* @param[in] exact true if data ends at message boundary
*
* @return
* The length of I/O vector to
* receive data, 0 if there are not enough buffers, or -1 upon an error.
*
* @sa msg_iovec(), su_vrecv()
*/
issize_t msg_recv_iovec(msg_t *msg, msg_iovec_t vec[], isize_t veclen,
usize_t n, int exact)
{
size_t i = 0;
size_t len = 0;
msg_payload_t *chunk;
char *buf;
if (n == 0)
return 0;
if (veclen == 0)
vec = NULL;
for (chunk = msg->m_chunk; chunk; chunk = MSG_CHUNK_NEXT(chunk)) {
buf = MSG_CHUNK_BUFFER(chunk);
len = MSG_CHUNK_AVAIL(chunk);
if (len == 0)
continue;
if (!buf)
break;
#if SU_HAVE_WINSOCK
/* WSABUF has u_long */
if (len > SU_IOVECLEN_MAX)
len = SU_IOVECLEN_MAX;
#endif
if (len > n)
len = n;
if (vec)
vec[i].mv_base = buf, vec[i].mv_len = (su_ioveclen_t)len;
i++;
if (len == n)
return i;
if (i == veclen)
vec = NULL;
n -= len;
}
if (!chunk && msg->m_chunk && msg_get_flags(msg, MSG_FLG_FRAGS)) {
/*
* If the m_chunk is the last fragment for this message,
* receive rest of the data to the next message
*/
if (msg->m_next == NULL)
msg->m_next = msg_create(msg->m_class, msg->m_oflags);
if (msg->m_next) {
msg->m_next->m_maxsize = msg->m_maxsize;
msg_addr_copy(msg->m_next, msg);
}
msg = msg->m_next;
if (msg == NULL)
return 0;
}
if (exact)
buf = msg_buf_exact(msg, n + 1), len = n;
else if (chunk && len > n && !msg_get_flags(msg, MSG_FLG_CHUNKING))
buf = msg_buf_exact(msg, len + 1);
else
buf = msg_buf_alloc(msg, n + 1), len = msg_buf_size(msg);
if (buf == NULL)
return -1;
if (vec)
vec[i].mv_base = buf, vec[i].mv_len = (su_ioveclen_t)n;
if (chunk) {
assert(chunk->pl_data == NULL); assert(chunk->pl_common->h_len == 0);
chunk->pl_common->h_data = chunk->pl_data = buf;
if (len < MSG_CHUNK_AVAIL(chunk)) {
msg_header_t *h = (void*)chunk;
h->sh_succ = msg_header_alloc(msg_home(msg), h->sh_class, 0);
if (!h->sh_succ)
return -1;
h->sh_succ->sh_prev = &h->sh_succ;
chunk->pl_next = (msg_payload_t *)h->sh_succ;
chunk->pl_next->pl_len = chunk->pl_len - len;
chunk->pl_len = len;
}
else if (len > MSG_CHUNK_AVAIL(chunk)) {
len = MSG_CHUNK_AVAIL(chunk);
}
msg_buf_used(msg, len);
}
return i + 1;
#if 0
if ((msg->m_ssize || msg->m_stream)
/* && msg_get_flags(msg, MSG_FLG_BODY) */) {
/* Streaming */
msg_buffer_t *b, *b0;
/* Calculate available size of current buffers */
for (b = msg->m_stream, len = 0; b && n > len; b = b->b_next)
len += b->b_avail - b->b_size;
/* Allocate new buffers */
if (n > len && msg_buf_external(msg, n, 0) < 0)
return -1;
for (b0 = msg->m_stream; b0; b0 = b0->b_next)
if (b0->b_avail != b0->b_size)
break;
for (b = b0; b && n > 0; i++, b = b->b_next) {
len = b->b_size - b->b_avail;
len = n < len ? n : len;
if (vec && i < veclen)
vec[i].mv_base = b->b_data + b->b_avail, vec[i].mv_len = len;
else
vec = NULL;
n -= len;
}
return i + 1;
}
#endif
}
/** Obtain a buffer for receiving data.
*
* @relatesalso msg_s
*/
issize_t msg_recv_buffer(msg_t *msg, void **return_buffer)
{
void *buffer;
if (!msg)
return -1;
if (return_buffer == NULL)
return_buffer = &buffer;
if (msg->m_chunk) {
msg_payload_t *pl;
for (pl = msg->m_chunk; pl; pl = pl->pl_next) {
size_t n = MSG_CHUNK_AVAIL(pl);
if (n) {
*return_buffer = MSG_CHUNK_BUFFER(pl);
return n;
}
}
return 0;
}
if (msg_get_flags(msg, MSG_FLG_FRAGS)) {
/* Message is complete */
return 0;
}
else if ((*return_buffer = msg_buf_alloc(msg, 2))) {
return msg_buf_size(msg) - 1;
}
else {
return -1;
}
}
/**Commit @a n bytes of buffers.
*
* @relatesalso msg_s
*
* The function msg_recv_commit() is called after @a n bytes of data has
* been received to the message buffers and the parser can extract the
* received data.
*
* @param msg pointer to message object
* @param n number of bytes received
* @param eos true if stream is complete
*
* @note The @a eos should be always true for message-based transports. It
* should also be true when a stram oin stream-based transport ends, for
* instance, when TCP FIN is received.
*
* @retval 0 when successful
* @retval -1 upon an error.
*/
isize_t msg_recv_commit(msg_t *msg, usize_t n, int eos)
{
msg_payload_t *pl;
if (eos)
msg->m_buffer->mb_eos = 1;
for (pl = msg->m_chunk; pl; pl = pl->pl_next) {
size_t len = MSG_CHUNK_AVAIL(pl);
if (n <= len)
len = n;
pl->pl_common->h_len += len;
n -= len;
if (n == 0)
return 0;
}
if (msg->m_chunk && msg->m_next)
msg = msg->m_next;
return msg_buf_commit(msg, n, eos);
}
/**Get a next message of the stream.
*
* @relatesalso msg_s
*
* When parsing a transport stream, only the first message in the stream is
* created with msg_create(). The rest of the messages should be created
* with msg_next() after previous message has been completely received and
* parsed.
*
*/
msg_t *msg_next(msg_t *msg)
{
msg_t *next;
usize_t n;
if (msg && msg->m_next) {
next = msg->m_next;
msg->m_next = NULL;
return next;
}
if ((n = msg_buf_committed(msg))) {
if (msg_buf_move(next = msg_create(msg->m_class, msg->m_oflags), msg)) {
msg_addr_copy(next, msg);
return next;
}
/* How to indicate error? */
msg_destroy(next);
}
return NULL;
}
/** Set next message of the stream.
*
* @relatesalso msg_s
*/
int msg_set_next(msg_t *msg, msg_t *next)
{
if (!msg || (next && next->m_next))
return -1;
if (msg->m_next && next)
next->m_next = msg->m_next;
msg->m_next = next;
return 0;
}
/** Clear committed data.
*
* @relatesalso msg_s
*/
void msg_clear_committed(msg_t *msg)
{
if (msg) {
usize_t n = msg_buf_committed(msg);
if (n)
msg_buf_used(msg, n);
}
}
#if 0
struct sigcomp_udvm;
struct sigcomp_udvm *msg_get_udvm(msg_t *msg);
struct sigcomp_udvm *msg_set_udvm(msg_t *msg, struct sigcomp_udvm *);
/** Save UDVM. */
struct sigcomp_udvm *msg_set_udvm(msg_t *msg, struct sigcomp_udvm *udvm)
{
struct sigcomp_udvm *prev = NULL;
if (msg) {
prev = msg->m_udvm;
msg->m_udvm = udvm;
}
return prev;
}
/** Get saved UDVM */
struct sigcomp_udvm *msg_get_udvm(msg_t *msg)
{
return msg ? msg->m_udvm : NULL;
}
#endif
/** Mark message as complete.
*
* @relatesalso msg_s
*/
unsigned msg_mark_as_complete(msg_t *msg, unsigned mask)
{
if (msg) {
msg->m_streaming = 0;
return msg->m_object->msg_flags |= mask | MSG_FLG_COMPLETE;
}
else {
return 0;
}
}
/** Return true if message is complete.
*
* @relatesalso msg_s
*/
int msg_is_complete(msg_t const *msg)
{
return msg && MSG_IS_COMPLETE(msg->m_object);
}
/** Return true if message has parsing errors.
*
* @relatesalso msg_s
*/
int msg_has_error(msg_t const *msg)
{
return msg->m_object->msg_flags & MSG_FLG_ERROR;
}
/**Total size of message.
*
* @relatesalso msg_s
*/
usize_t msg_size(msg_t const *msg)
{
return msg ? msg->m_size : 0;
}
/** Set the maximum size of a message.
*
* @relatesalso msg_s
*
* The function msg_maxsize() sets the maximum buffer size of a message. It
* returns the previous maximum size. If the @a maxsize is 0, maximum size
* is not set, but the current maximum size is returned.
*
* If the message size exceeds maxsize, msg_errno() returns ENOBUFS,
* MSG_FLG_TOOLARGE and MSG_FLG_ERROR flags are set.
*/
usize_t msg_maxsize(msg_t *msg, usize_t maxsize)
{
usize_t retval = 0;
if (msg) {
retval = msg->m_maxsize;
if (maxsize)
msg->m_maxsize = maxsize;
}
return retval;
}
/**Set the size of next fragment.
*
* @relatesalso msg_s
*
* The function msg_streaming_size() sets the size of the message body for
* streaming.
*/
int msg_streaming_size(msg_t *msg, usize_t ssize)
{
if (!msg)
return -1;
msg->m_ssize = ssize;
return 0;
}
/**Allocate a list of external buffers.
*
* @relatesalso msg_s
*
* The function msg_buf_external() allocates at most msg_n_fragments
* external buffers for the message body.
*
* @return The function msg_buf_external() returns number of allocated
* buffers, or -1 upon an error.
*/
issize_t msg_buf_external(msg_t *msg,
usize_t N,
usize_t blocksize)
{
msg_buffer_t *ext, *b, **bb;
size_t i, I;
assert(N <= 128 * 1024);
if (msg == NULL)
return -1;
if (blocksize == 0)
blocksize = msg_min_block;
if (N == 0)
N = blocksize;
if (N > blocksize * msg_n_fragments)
N = blocksize * msg_n_fragments;
if (N > msg->m_ssize)
N = msg->m_ssize;
I = (N + blocksize - 1) / blocksize; assert(I <= msg_n_fragments);
for (i = 0, bb = &ext; i < I; i++) {
*bb = su_zalloc(msg_home(msg), sizeof **bb);
if (!*bb)
break;
bb = &(*bb)->b_next;
}
if (i == I)
for (b = ext, i = 0; b; b = b->b_next, i++) {
b->b_data = su_alloc(msg_home(msg), b->b_size = blocksize);
if (!b->b_data)
break;
}
if (i == I) {
/* Successful return */
for (bb = &msg->m_stream; *bb; bb = &(*bb)->b_next)
;
*bb = ext;
if (msg->m_ssize != MSG_SSIZE_MAX)
for (b = ext; b; b = b->b_next) {
if (msg->m_ssize < b->b_size) {
b->b_size = msg->m_ssize;
}
msg->m_ssize -= b->b_size;
}
return i;
}
for (b = ext; b; b = ext) {
ext = b->b_next;
su_free(msg_home(msg), b->b_data);
su_free(msg_home(msg), b);
}
return -1;
}
int msg_unref_external(msg_t *msg, msg_buffer_t *b)
{
if (msg && b) {
su_free(msg_home(msg), b->b_data);
su_free(msg_home(msg), b);
return 0;
}
errno = EINVAL;
return -1;
}
/* ====================================================================== */
/* Parsing messages */
static inline int extract_incomplete_chunks(msg_t *, int eos);
static issize_t extract_first(msg_t *, msg_pub_t *,
char b[], isize_t bsiz, int eos);
static inline issize_t extract_next(msg_t *, msg_pub_t *, char *, isize_t bsiz,
int eos, int copy);
static issize_t extract_header(msg_t *, msg_pub_t*,
char b[], isize_t bsiz, int eos, int copy);
static msg_header_t *header_parse(msg_t *, msg_pub_t *, msg_href_t const *,
char s[], isize_t slen, int copy_buffer);
static msg_header_t *error_header_parse(msg_t *msg, msg_pub_t *mo,
msg_href_t const *hr);
static inline issize_t
extract_trailers(msg_t *msg, msg_pub_t *mo,
char *b, isize_t bsiz, int eos, int copy);
/** Calculate length of line ending (0, 1 or 2). @internal */
#define CRLF_TEST(b) ((b)[0] == '\r' ? ((b)[1] == '\n') + 1 : (b)[0] =='\n')
static inline void
append_parsed(msg_t *msg, msg_pub_t *mo, msg_href_t const *hr, msg_header_t *h,
int always_into_chain);
/**Extract and parse a message from internal buffer.
*
* @relatesalso msg_s
*
* This function parses the internal buffer and adds the parsed fragments to
* the message object. It marks the successfully parsed data as extracted.
*
* @param msg message to be parsed
*
* @retval positive if a complete message was parsed
* @retval 0 if message was incomplete
* @retval negative if an error occurred
*/
int msg_extract(msg_t *msg)
{
msg_pub_t *mo = msg_object(msg);
msg_mclass_t const *mc;
char *b;
ssize_t m;
size_t bsiz;
int eos;
if (!msg || !msg->m_buffer->mb_data)
return -1;
assert(mo);
mc = msg->m_class;
mo = msg->m_object;
eos = msg->m_buffer->mb_eos;
if (msg->m_chunk) {
int incomplete = extract_incomplete_chunks(msg, eos);
if (incomplete < 1 || MSG_IS_COMPLETE(mo))
return incomplete;
}
if (mo->msg_flags & MSG_FLG_TRAILERS)
msg_set_streaming(msg, 0);
if (msg->m_buffer->mb_used + msg->m_buffer->mb_commit ==
msg->m_buffer->mb_size)
/* Why? When? */
return 0;
assert(msg->m_buffer->mb_used + msg->m_buffer->mb_commit <
msg->m_buffer->mb_size);
m = 0;
b = msg->m_buffer->mb_data + msg->m_buffer->mb_used;
bsiz = msg->m_buffer->mb_commit;
b[bsiz] = '\0';
while (msg->m_buffer->mb_commit > 0) {
int flags = mo->msg_flags;
int copy = MSG_IS_EXTRACT_COPY(flags);
if (flags & MSG_FLG_COMPLETE)
break;
if (flags & MSG_FLG_TRAILERS)
m = extract_trailers(msg, mo, b, bsiz, eos, copy);
else if (flags & MSG_FLG_BODY)
m = mc->mc_extract_body(msg, mo, b, bsiz, eos);
else if (flags & MSG_FLG_HEADERS)
m = extract_next(msg, mo, b, bsiz, eos, copy);
else
m = extract_first(msg, mo, b, bsiz, eos);
if (m <= 0 || msg->m_chunk)
break;
b += m;
bsiz -= m;
msg_buf_used(msg, (size_t)m);
}
if (eos && bsiz == 0)
msg_mark_as_complete(msg, 0);
if (m < 0 || (mo->msg_flags & MSG_FLG_ERROR)) {
msg_mark_as_complete(msg, MSG_FLG_ERROR);
return -1;
}
else if (!MSG_IS_COMPLETE(mo))
return 0;
else if (!(mo->msg_flags & MSG_FLG_HEADERS)) {
msg_mark_as_complete(msg, MSG_FLG_ERROR);
return -1;
}
else
return 1;
}
static
issize_t extract_first(msg_t *msg, msg_pub_t *mo, char b[], isize_t bsiz, int eos)
{
/* First line */
size_t k, l, m, n, xtra;
int crlf;
msg_header_t *h;
msg_href_t const *hr;
msg_mclass_t const *mc = msg->m_class;
for (k = 0; IS_LWS(b[k]); k++) /* Skip whitespace */
;
if (!b[k]) return k;
/* If first token contains no /, this is request, otherwise status line */
l = span_token(b + k) + k;
if (b[l] != '/')
hr = mc->mc_request;
else
hr = mc->mc_status;
n = span_non_crlf(b + l) + l;
if (!b[n])
return eos ? -1 : 0;
crlf = CRLF_TEST(b + n);
for (m = n + crlf; IS_WS(b[m]); m++)
;
/* In order to skip possible whitespace after first line, we don't parse
first line until first non-ws char from next one has been received */
if (!b[m] && !eos)
return 0;
xtra = MSG_IS_EXTRACT_COPY(mo->msg_flags) ? n + 1 - k : 0;
if (!(h = msg_header_alloc(msg_home(msg), hr->hr_class, xtra)))
return -1;
if (xtra) {
char *bb = memcpy(MSG_HEADER_DATA(h), b, xtra - 1);
h->sh_data = b, h->sh_len = n + crlf;
b = bb; n = xtra - 1;
}
else {
b = b + k; n = n - k;
}
b[n] = 0;
if (hr->hr_class->hc_parse(msg_home(msg), h, b, n) < 0)
return -1;
assert(hr->hr_offset);
append_parsed(msg, mo, hr, h, 1);
mo->msg_flags |= MSG_FLG_HEADERS;
return m;
}
/* Extract header or message body */
static inline issize_t
extract_next(msg_t *msg, msg_pub_t *mo, char *b, isize_t bsiz,
int eos, int copy)
{
if (IS_CRLF(b[0]))
return msg->m_class->mc_extract_body(msg, mo, b, bsiz, eos);
else
return extract_header(msg, mo, b, bsiz, eos, copy);
}
/** Extract a header. */
issize_t msg_extract_header(msg_t *msg, msg_pub_t *mo,
char b[], isize_t bsiz, int eos)
{
return extract_header(msg, mo, b, bsiz, eos, 0);
}
/** Extract a header from buffer @a b.
*/
static
issize_t
extract_header(msg_t *msg, msg_pub_t *mo, char *b, isize_t bsiz, int eos,
int copy_buffer)
{
size_t len, m;
size_t name_len = 0, xtra;
isize_t n = 0;
int crlf = 0, name_len_set = 0;
int error = 0;
msg_header_t *h;
msg_href_t const *hr;
msg_mclass_t const *mc = msg->m_class;
hr = msg_find_hclass(mc, b, &n); /* Get header name */
error = n == 0;
if (hr == NULL) /* Panic */
return -1;
xtra = span_ws(b + n);
/* Find next crlf which is not followed by whitespace */
do {
n += xtra + crlf;
if (!eos && bsiz == n)
return 0;
m = span_non_crlf(b + n);
if (!name_len_set && m)
name_len = n, name_len_set = 1; /* First non-ws after COLON */
n += m;
crlf = CRLF_TEST(b + n);
xtra = span_ws(b + n + crlf);
}
while (xtra);
if (!eos && bsiz == n + crlf)
return 0;
if (hr->hr_class->hc_hash == msg_unknown_hash)
name_len = 0, name_len_set = 1;
if (error) {
msg->m_extract_err |= hr->hr_flags;
if (hr->hr_class->hc_critical)
mo->msg_flags |= MSG_FLG_ERROR;
hr = mc->mc_error;
copy_buffer = 1;
h = error_header_parse(msg, mo, hr);
}
else {
if (!name_len_set)
/* Empty header - nothing but name, COLON and LWS */
name_len = n;
else
/* Strip extra whitespace at the end of header */
while (n > name_len && IS_LWS(b[n - 1]))
n--, crlf++;
h = header_parse(msg, mo, hr, b + name_len, n - name_len, copy_buffer);
}
if (h == NULL)
return -1;
len = n + crlf;
/*
* If the header contains multiple header fields, set the pointer to the
* encodeded data correctly
*/
while (h) {
if (copy_buffer)
h->sh_data = b, h->sh_len = len;
b += len, len = 0;
if (h->sh_succ)
assert(&h->sh_succ == h->sh_succ->sh_prev);
h = h->sh_next;
}
return n + crlf;
}
static
msg_header_t *header_parse(msg_t *msg, msg_pub_t *mo,
msg_href_t const *hr,
char s[], isize_t slen,
int copy_buffer)
{
su_home_t *home = msg_home(msg);
msg_header_t *h, **hh;
msg_hclass_t *hc = hr->hr_class;
int n;
int add_to_list, clear = 0;
hh = (msg_header_t **)((char *)mo + hr->hr_offset);
add_to_list = (hc->hc_kind == msg_kind_list && !copy_buffer && *hh);
if (add_to_list)
h = *hh;
else
h = msg_header_alloc(home, hc, copy_buffer ? slen + 1 : 0);
if (!h)
return NULL;
if (copy_buffer)
s = memcpy(MSG_HEADER_DATA(h), s, slen);
s[slen] = '\0';
if (hc->hc_kind == msg_kind_list && *hh) {
n = hc->hc_parse(home, *hh, s, slen);
/* Clear if adding new header disturbs existing headers */
clear = *hh != h && !copy_buffer;
if (clear)
msg_fragment_clear((*hh)->sh_common);
}
else
n = hc->hc_parse(home, h, s, slen);
if (n < 0) {
msg->m_extract_err |= hr->hr_flags;
if (hc->hc_critical)
mo->msg_flags |= MSG_FLG_ERROR;
clear = 0;
if (!add_to_list) {
/* XXX - This should be done by msg_header_free_all() */
msg_header_t *h_next;
msg_param_t *h_params;
while (h) {
h_next = h->sh_next;
if (hc->hc_params) {
h_params = *(msg_param_t **)((char *)h + hc->hc_params);
if (h_params)
su_free(home, h_params);
}
su_free(home, h);
h = h_next;
}
/* XXX - This should be done by msg_header_free_all() */
hr = msg->m_class->mc_error;
h = msg_header_alloc(home, hr->hr_class, 0);
if (!h)
return h;
h->sh_error->er_name = hc->hc_name;
hh = (msg_header_t **)((char *)mo + hr->hr_offset);
}
}
if (clear)
for (hh = &(*hh)->sh_next; *hh; *hh = (*hh)->sh_next)
msg_chain_remove(msg, *hh);
else if (h != *hh)
append_parsed(msg, mo, hr, h, 0);
return h;
}
static
msg_header_t *error_header_parse(msg_t *msg, msg_pub_t *mo,
msg_href_t const *hr)
{
msg_header_t *h;
h = msg_header_alloc(msg_home(msg), hr->hr_class, 0);
if (h)
append_parsed(msg, mo, hr, h, 0);
return h;
}
/** Complete this header field and parse next header field.
*
* This function completes parsing a multi-field header like @Accept,
* @Contact, @Via or @Warning. It scans for the next header field and
* if one is found, it calls the parsing function recursively.
*
* @param home memory home used ot allocate
* new header structures and parameter lists
* @param prev pointer to header structure already parsed
* @param s header content to parse; should point to the area after
* current header field (either end of line or to a comma
* separating header fields)
* @param slen ignored
*
* @since New in @VERSION_1_12_4.
*
* @retval >= 0 when successful
* @retval -1 upon an error
*/
issize_t msg_parse_next_field(su_home_t *home, msg_header_t *prev,
char *s, isize_t slen)
{
msg_hclass_t *hc = prev->sh_class;
msg_header_t *h;
char *end = s + slen;
if (*s && *s != ',')
return -1;
if (msg_header_update_params(prev->sh_common, 0) < 0)
return -1;
while (*s == ',') /* Skip comma and following whitespace */
*s = '\0', s += span_lws(s + 1) + 1;
if (*s == 0)
return 0;
h = msg_header_alloc(home, hc, 0);
if (!h)
return -1;
prev->sh_succ = h, h->sh_prev = &prev->sh_succ;
prev->sh_next = h;
return hc->hc_parse(home, h, s, end - s);
}
/** Decode a message header. */
msg_header_t *msg_header_d(su_home_t *home, msg_t const *msg, char const *b)
{
msg_mclass_t const *mc = msg->m_class;
msg_href_t const *hr = mc->mc_unknown;
isize_t n; /* Length of header contents */
isize_t name_len, xtra;
msg_header_t *h;
char *bb;
n = strlen(b);
hr = msg_find_hclass(mc, b, &name_len);
if (hr == NULL)
return NULL;
/* Strip extra whitespace at the end and begin of header */
while (n > name_len && IS_LWS(b[n - 1]))
n--;
if (name_len < n && IS_LWS(b[name_len]))
name_len++;
xtra = (n - name_len);
if (!(h = msg_header_alloc(home, hr->hr_class, xtra + 1)))
return NULL;
bb = memcpy(MSG_HEADER_DATA(h), b + name_len, xtra), bb[xtra] = 0;
if (hr->hr_class->hc_parse(home, h, bb, xtra) >= 0)
return h;
hr = mc->mc_unknown;
su_free(home, h);
if (!(h = msg_header_alloc(home, hr->hr_class, n + 1)))
return NULL;
bb = memcpy(MSG_HEADER_DATA(h), b, n), bb[n] = 0;
if (hr->hr_class->hc_parse(home, h, bb, n) < 0)
su_free(home, h), h = NULL;
return h;
}
/** Extract a separator line */
issize_t msg_extract_separator(msg_t *msg, msg_pub_t *mo,
char b[], isize_t bsiz, int eos)
{
msg_mclass_t const *mc = msg->m_class;
msg_href_t const *hr = mc->mc_separator;
int l = CRLF_TEST(b); /* Separator length */
msg_header_t *h;
/* Even if a single CR *may* be a payload separator we cannot be sure */
if (l == 0 || (!eos && bsiz == 1 && b[0] == '\r'))
return 0;
/* Separator */
if (!(h = msg_header_alloc(msg_home(msg), hr->hr_class, 0)))
return -1;
if (hr->hr_class->hc_parse(msg_home(msg), h, b, l) < 0)
return -1;
h->sh_data = b, h->sh_len = l;
append_parsed(msg, mo, hr, h, 0);
return l;
}
static inline msg_header_t **msg_chain_tail(msg_t const *msg);
/** Extract a message body of @a body_len bytes.
*/
issize_t msg_extract_payload(msg_t *msg, msg_pub_t *mo,
msg_header_t **return_payload,
usize_t body_len,
char b[], isize_t bsiz,
int eos)
{
msg_mclass_t const *mc = msg->m_class;
msg_href_t const *hr = mc->mc_payload;
msg_header_t *h, *h0;
msg_payload_t *pl;
char *x;
if (msg == NULL || mo == NULL)
return -1;
assert(!msg->m_chunk);
if (return_payload == NULL)
return_payload = &h0;
*return_payload = NULL;
assert(body_len > 0);
/* Allocate header structure for payload */
if (!(h = msg_header_alloc(msg_home(msg), hr->hr_class, 0)))
return -1;
append_parsed(msg, mo, hr, h, 0);
pl = h->sh_payload;
*return_payload = h;
if (bsiz >= body_len) {
/* We have a complete body. */
h->sh_data = b, h->sh_len = body_len;
pl->pl_data = b, pl->pl_len = body_len;
return body_len;
}
if (msg->m_maxsize != 0 && body_len > msg->m_maxsize) {
mo->msg_flags |= MSG_FLG_TOOLARGE;
return -1;
}
assert(msg->m_buffer->mb_commit == bsiz);
assert(b == msg->m_buffer->mb_data + msg->m_buffer->mb_used);
if (msg->m_buffer->mb_used + body_len <= msg->m_buffer->mb_size) {
/* We don't have a complete body, but we have big enough buffer for it. */
msg->m_chunk = pl;
h->sh_data = b, h->sh_len = bsiz;
pl->pl_data = b, pl->pl_len = body_len;
if (msg->m_buffer->mb_used + body_len < msg->m_buffer->mb_size)
/* NUL-terminate payload */
b[body_len++] = '\0';
/* Mark the rest of the body as used in the buffer */
/* msg_buf_commit(msg, body_len - bsiz, eos); */
msg_buf_used(msg, body_len);
return bsiz;
}
/* We don't have big enough buffer for body. */
if (msg_get_flags(msg, MSG_FLG_CHUNKING)) {
/* Application supports chunking, use multiple chunks for payload */
usize_t current, rest;
current = msg->m_buffer->mb_size - msg->m_buffer->mb_used;
rest = body_len - current;
/* Use all the data from our current buffer */
msg_buf_used(msg, current);
msg->m_chunk = pl;
h->sh_data = b, h->sh_len = bsiz;
pl->pl_data = b, pl->pl_len = current;
for (;current < body_len; current += rest) {
msg_header_t *h0 = h;
/* Allocate header structure for next payload chunk */
if (!(h = msg_header_alloc(msg_home(msg), hr->hr_class, 0)))
return -1;
if (msg->m_chain)
msg_insert_here_in_chain(msg, msg_chain_tail(msg), h);
h0->sh_next = h;
rest = body_len - current;
if (!msg->m_streaming) {
x = msg_buf_exact(msg, rest);
if (x == NULL) {
mo->msg_flags |= MSG_FLG_TOOLARGE;
return -1;
}
}
else {
x = NULL;
}
if (x) {
/* Mark the just-allocated buffer as used */
rest = msg->m_buffer->mb_size - msg->m_buffer->mb_used;
msg_buf_used(msg, rest);
}
pl = h->sh_payload;
h->sh_len = 0, pl->pl_len = rest;
h->sh_data = x, pl->pl_data = x;
}
}
else {
/* No chunking.
*
* Allocate a single buffer that contains enough free space for body.
*
* msg_buf_exact() also copies committed but un-used data
* from the old buffer (b[0] .. b[bsiz])
* to the new buffer (x[-bsiz-1]..b[-1])
*/
if (!(x = msg_buf_exact(msg, body_len - bsiz + 1))) {
if (mo->msg_flags & MSG_FLG_TOOLARGE) {
msg_mark_as_complete(msg, MSG_FLG_TRUNC);
return bsiz;
}
return -1;
}
/* Fake un-received data as already received and then use it */
/* msg_buf_commit(msg, body_len - bsiz + 1, eos); */
msg_buf_used(msg, body_len + 1);
msg->m_chunk = h->sh_payload;
x -= bsiz; /* Start of un-used data */
x[body_len] = '\0';
h->sh_data = x, h->sh_len = bsiz;
pl->pl_data = x, pl->pl_len = body_len;
assert(MSG_CHUNK_AVAIL(pl) == body_len - bsiz);
}
return bsiz;
}
/** Extract incomplete chunks.
*/
static inline
int extract_incomplete_chunks(msg_t *msg, int eos)
{
msg_payload_t *chunk;
for (chunk = msg->m_chunk; chunk; chunk = MSG_CHUNK_NEXT(chunk)) {
if (MSG_CHUNK_AVAIL(chunk) != 0)
break;
/* The incomplete payload fragment is now complete */
assert(MSG_CHUNK_BUFFER(chunk) == chunk->pl_data + chunk->pl_len);
msg->m_size += chunk->pl_common->h_len;
}
msg->m_chunk = chunk;
if (chunk) {
if (eos) {
msg_mark_as_complete(msg, MSG_FLG_TRUNC);
return 1;
}
}
else {
if (msg_get_flags(msg, MSG_FLG_FRAGS))
msg_mark_as_complete(msg, 0);
}
/**@retval 1 when message is complete
* @retval 0 when message is incomplete
* @retval -1 upon an error
*/
return chunk == NULL;
}
/* Extract trailers */
static inline issize_t
extract_trailers(msg_t *msg, msg_pub_t *mo,
char *b, isize_t bsiz, int eos, int copy)
{
if (IS_CRLF(b[0])) {
msg_mark_as_complete(msg, MSG_FLG_COMPLETE);
return CRLF_TEST(b);
}
else
return extract_header(msg, mo, b, bsiz, eos, copy);
}
/* ====================================================================== */
/* Preparing (printing/encoding) a message structure for sending */
/* Internal prototypes */
static inline size_t
msg_header_name_e(char b[], size_t bsiz, msg_header_t const *h, int flags);
static size_t msg_header_prepare(msg_mclass_t const *, int flags,
msg_header_t *h, msg_header_t **return_next,
char *b, size_t bsiz);
/**Encode all message fragments.
*
* @relatesalso msg_s
*
* The function msg_prepare() prepares a message for sending. It encodes all
* serialized fragments in the message. You have to call msg_serialize()
* before calling msg_headers_prepare() in order to make sure that all the
* heades and other message fragments are included in the chain.
*
* After encoding, the msg_common_s::h_data field will point to the encoding
* result of size msg_common_s::h_len bytes in in each fragment.
*
* When multiple header fields are represented as a comma-separated list
* within a single header line, the first fragment in the header will
* contain all the text belonging to the header. The rest of the header
* fields will have zero-length encoding with msg_common_s::h_data that
* points to the end of the line.
*
* @return Total size of the encoded message in bytes, or -1 upon an error.
*
* @sa msg_extract(), msg_serialize()
*/
int msg_prepare(msg_t *msg)
{
int total;
assert(msg->m_chain);
assert(msg_chain_errors(msg->m_chain) == 0);
/* Get rid of data that was received but not yet used (parsed) */
msg_clear_committed(msg);
total = msg_headers_prepare(msg, msg->m_chain, msg_object(msg)->msg_flags);
if (total != -1) {
msg->m_size = total;
msg->m_prepared = 1;
}
return total;
}
/** Clear 'prepared' flag. */
void msg_unprepare(msg_t *msg)
{
if (msg) msg->m_prepared = 0;
}
/** Return true if message is prepared. */
int msg_is_prepared(msg_t const *msg)
{
return msg && msg->m_prepared;
}
/**Encode headers in chain.
*
* The function msg_headers_prepare() encodes all the headers in the header
* chain. You have to call msg_serialize() before calling
* msg_headers_prepare() in order to make sure that all the heades and other
* message fragments are included in the chain.
*
* @return
* The size of all the headers in chain, or -1 upon an error.
*/
issize_t msg_headers_prepare(msg_t *msg, msg_header_t *headers, int flags)
{
msg_mclass_t const *mc = msg->m_class;
msg_header_t *h, *next;
ssize_t n = 0;
size_t bsiz = 0, used = 0;
char *b;
size_t total = 0;
b = msg_buf_alloc(msg, msg_min_size);
bsiz = msg_buf_size(msg);
if (!b)
return -1;
for (h = headers; h;) {
if (h->sh_data) {
total += h->sh_len;
h = h->sh_succ;
continue;
}
for (next = h->sh_succ; next; next = next->sh_succ)
if (next->sh_class != h->sh_class || next->sh_data)
break;
n = msg_header_prepare(mc, flags, h, &next, b, bsiz - used);
if (n == (ssize_t)-1) {
errno = EINVAL;
return -1;
}
if (used + n >= bsiz) {
/* Allocate next buffer */
if ((b = msg_buf_alloc(msg, n + 1)) == NULL)
return -1;
bsiz = msg_buf_size(msg); used = 0;
continue;
}
h->sh_data = b, h->sh_len = n;
for (h = h->sh_succ; h != next; h = h->sh_succ)
h->sh_data = b + n, h->sh_len = 0;
msg_buf_used(msg, n);
total += n;
used += n;
b += n;
}
return total;
}
/** Encode a header or a list of headers */
static
size_t msg_header_prepare(msg_mclass_t const *mc, int flags,
msg_header_t *h, msg_header_t **return_next,
char *b, size_t bsiz)
{
msg_header_t *h0, *next;
msg_hclass_t *hc;
char const *s;
size_t n; ssize_t m;
int compact, one_line_list, comma_list;
assert(h); assert(h->sh_class);
hc = h->sh_class;
compact = MSG_IS_COMPACT(flags);
one_line_list = hc->hc_kind == msg_kind_apndlist;
comma_list = compact || one_line_list || MSG_IS_COMMA_LISTS(flags);
for (h0 = h, n = 0; ; h = next) {
next = h->sh_succ;
if (h == h0 && hc->hc_name && hc->hc_name[0])
n += msg_header_name_e(b + n, bsiz >= n ? bsiz - n : 0, h, flags);
if ((m = hc->hc_print(b + n, bsiz >= n ? bsiz - n : 0, h, flags)) == -1) {
if (bsiz >= n + 64)
m = 2 * (bsiz - n);
else
m = 128;
}
n += m;
if (hc->hc_name) {
if (!comma_list || !next || next == *return_next)
s = CRLF, m = 2;
/* Else encode continuation */
else if (compact)
s = ",", m = 1;
else if (one_line_list)
s = ", ", m = 2;
else
s = "," CRLF "\t", m = 4;
if (bsiz > n + m)
memcpy(b + n, s, m);
n += m;
}
if (!comma_list || !next || next == *return_next)
break;
}
*return_next = next;
return n;
}
/** Encode a header.
*
* The function msg_header_e() encodes a header field in the buffer @a
* b[]. The encoding includes its name and trailing CRLF. The function
* returns the length of the encoding in bytes, excluding the final @c NUL.
* The buffer @a b must be large enough for whole encoding, including the
* final @c NUL.
*
* The @a flags parameter define how the encoding is done. If the flags
* specify @c MSG_DO_COMPACT, the encoding is compact (short form with
* minimal whitespace).
*/
issize_t msg_header_e(char b[], isize_t bsiz, msg_header_t const *h, int flags)
{
size_t n, m;
assert(h); assert(h->sh_class);
if (h == NULL || h->sh_class == NULL)
return -1;
n = msg_header_name_e(b, bsiz, h, flags);
m = h->sh_class->hc_print(b + n, bsiz > n ? bsiz - n : 0, h, flags);
if (h->sh_class->hc_name) {
/* Ordinary header */
if (bsiz > n + m + strlen(CRLF))
strcpy(b + n + m, CRLF);
return n + m + strlen(CRLF);
}
else
return m;
}
/** Encode header name */
static inline
size_t
msg_header_name_e(char b[], size_t bsiz, msg_header_t const *h, int flags)
{
int compact = MSG_IS_COMPACT(flags);
char const *name;
size_t n, n2;
if (compact && h->sh_class->hc_short[0])
name = h->sh_class->hc_short, n = 1;
else
name = h->sh_class->hc_name, n = h->sh_class->hc_len;
if (!name || !name[0])
return 0;
n2 = compact ? n + 1 : n + 2;
if (n2 < bsiz) {
memcpy(b, name, n);
b[n++] = ':';
if (!compact)
b[n++] = ' ';
b[n++] = '\0';
}
return n2;
}
/** Convert a message to a string.
*
* A message is encoded and the encoding result is returned as a string.
* Because the message may contain binary payload (or NUL in headers), the
* message length is returned separately in @a *return_len, too.
*
* Note that the message is serialized as a side effect.
*
* @param home memory home used to allocate the string
* @param msg message to encode
* @param pub message object to encode (may be NULL)
* @param flags flags used when encoding
* @param return_len return-value parameter for encoded message length
*
* @return Encoding result as a C string.
*
* @since New in @VERSION_1_12_4
*
* @sa msg_make(), msg_prepare(), msg_serialize().
*/
char *msg_as_string(su_home_t *home, msg_t *msg, msg_pub_t *pub, int flags,
size_t *return_len)
{
msg_mclass_t const *mc = msg->m_class;
msg_header_t *h, *next;
ssize_t n = 0;
size_t bsiz = 0, used = 0;
char *b, *b2;
if (pub == NULL)
pub = msg->m_object;
if (msg_serialize(msg, pub) < 0)
return NULL;
if (return_len == NULL)
return_len = &used;
b = su_alloc(home, bsiz = msg_min_size);
if (!b)
return NULL;
if (pub == msg->m_object)
h = msg->m_chain;
else
h = pub->msg_common->h_succ;
while (h) {
for (next = h->sh_succ; next; next = next->sh_succ)
if (next->sh_class != h->sh_class)
break;
n = msg_header_prepare(mc, flags, h, &next, b + used, bsiz - used);
if (n == -1) {
errno = EINVAL;
su_free(home, b);
return NULL;
}
if (bsiz > used + n) {
used += n;
h = next;
}
else {
/* Realloc */
if (h->sh_succ)
bsiz = (used + n + msg_min_size) / msg_min_size * msg_min_size;
else
bsiz = used + n + 1;
b2 = su_realloc(home, b, bsiz);
if (b2 == NULL || bsiz < msg_min_size) {
errno = ENOMEM;
su_free(home, b);
return NULL;
}
continue;
}
}
*return_len = used;
b[used] = '\0'; /* NUL terminate */
return su_realloc(home, b, used + 1);
}
/* ====================================================================== */
/* Handling header chain */
static inline void serialize_first(msg_t *msg, msg_header_t *h);
static msg_header_t **serialize_one(msg_t *msg, msg_header_t *h,
msg_header_t **prev);
/** Return head of the fragment chain */
msg_header_t **msg_chain_head(msg_t const *msg)
{
return msg ? (msg_header_t **)&msg->m_chain : NULL;
}
static inline msg_header_t **_msg_chain_head(msg_t const *msg)
{
return msg ? (msg_header_t **)&msg->m_chain : NULL;
}
/** Return tail of the fragment chain */
static inline msg_header_t **msg_chain_tail(msg_t const *msg)
{
return msg ? msg->m_tail : NULL;
}
/** Serialize headers into the fragment chain.
*
* The msg_serialize() collects the headers and other message components in
* the fragment chain. It should be called before msg_prepare().
*
* @relatesalso msg_s
*
* @param msg pointer to message object
* @param pub public message structure
*
* @retval 0 when successful
* @retval -1 upon an error
*/
int msg_serialize(msg_t *msg, msg_pub_t *pub)
{
msg_header_t *h, **hh, **end;
msg_header_t **separator;
msg_header_t **payload;
msg_header_t **multipart;
msg_mclass_t const *mc = msg->m_class;
msg_header_t **tail, ***ptail;
if (!msg)
return errno = EINVAL, -1;
if (pub == NULL)
pub = msg->m_object;
/* There must be a first line */
if (pub->msg_request)
h = pub->msg_request;
else if (pub->msg_status)
h = pub->msg_status;
else
return errno = EINVAL, -1;
serialize_first(msg, h);
separator = (msg_header_t **)((char *)pub + mc->mc_separator->hr_offset);
payload = (msg_header_t **)((char *)pub + mc->mc_payload->hr_offset);
if (mc->mc_multipart->hr_class)
multipart = (msg_header_t **)((char *)pub + mc->mc_multipart->hr_offset);
else
multipart = NULL;
/* Find place to insert headers: before separator, payload and multipart */
if (*separator && !msg_header_is_removed(*separator))
ptail = &(*separator)->sh_prev;
else if (*payload && !msg_header_is_removed(*payload))
ptail = &(*payload)->sh_prev;
else if (multipart && *multipart && !msg_header_is_removed(*multipart))
ptail = &(*multipart)->sh_prev;
else
ptail = &msg->m_tail;
tail = *ptail;
end = (msg_header_t **)((char *)pub + pub->msg_size);
for (hh = pub->msg_headers; hh < end; hh++) {
if (!*hh)
continue;
if (hh == separator || hh == payload || hh == multipart)
continue;
tail = serialize_one(msg, *hh, tail);
}
/* Serialize separator, payload and multipart last */
if (*separator)
tail = serialize_one(msg, *separator, tail);
*ptail = tail;
/* Payload comes after separator but before multipart */
if (ptail != &(*separator)->sh_prev)
;
else if (*payload && !msg_header_is_removed(*payload))
ptail = &(*payload)->sh_prev;
else if (multipart && *multipart && !msg_header_is_removed(*multipart))
ptail = &(*multipart)->sh_prev;
else
ptail = &msg->m_tail;
tail = *ptail;
if (*payload) {
tail = serialize_one(msg, *payload, tail);
*ptail = tail;
}
if (multipart && *multipart) {
msg_header_t *last;
last = msg_multipart_serialize(tail, (msg_multipart_t *)*multipart);
msg->m_tail = &last->sh_succ;
}
assert(msg->m_chain && msg_chain_errors(msg->m_chain) == 0);
return 0;
}
static inline
void serialize_first(msg_t *msg, msg_header_t *h)
{
if (msg_header_is_removed(h)) {
if ((h->sh_succ = msg->m_chain))
h->sh_succ->sh_prev = &h->sh_succ;
else
msg->m_tail = &h->sh_succ;
*(h->sh_prev = &msg->m_chain) = h;
}
}
static
msg_header_t **serialize_one(msg_t *msg, msg_header_t *h, msg_header_t **prev)
{
msg_header_t *last;
msg_header_t *succ = *prev;
if (msg_header_is_removed(h)) {
/* Add the first header in the list to the chain */
*prev = h; h->sh_prev = prev;
for (last = h; last->sh_succ; last = last->sh_succ) {
/* Ensure that chain is connected */
assert(last->sh_next == last->sh_succ);
assert(last->sh_succ->sh_prev = &last->sh_succ);
}
prev = &last->sh_succ;
}
if ((h = h->sh_next)) {
assert(!msg_is_single(h));
if (msg_is_single(h)) {
for (; h; h = h->sh_next)
if (!msg_header_is_removed(h))
msg_chain_remove(msg, h);
}
/* Add the rest of the headers in the list to the chain */
else for (; h; h = h->sh_next) {
if (msg_header_is_removed(h)) {
*prev = h; h->sh_prev = prev;
for (;h->sh_succ; h = h->sh_succ)
assert(h->sh_succ == h->sh_next);
prev = &h->sh_succ;
}
}
}
*prev = succ;
return prev;
}
/**Fill an I/O vector with message contents.
*
* @relatesalso msg_s
*
* Calculate number of entries in the I/O vector
* required to send a message @a msg. It also fills in the I/O vector array,
* if it is provided by the caller and it is large enough.
*
* @param msg pointer to message object
* @param vec I/O vector (may be NULL)
* @param veclen length of I/O vector in @a vec
*
* @return
* Number of entries of I/O
* vector required by @a msg, or 0 upon an error.
*
* @note The caller should check that the I/O vector @a vec has enough
* entries. If the @a vec is too short, it should allocate big enough
* vector and re-invoke msg_iovec().
*
* @sa msg_recv_iovec(), su_vsend()
*/
isize_t msg_iovec(msg_t *msg, msg_iovec_t vec[], isize_t veclen)
{
size_t len = 0, n = 0;
char const *p = NULL;
msg_header_t *h;
size_t total = 0;
if (veclen <= 0)
veclen = 0;
for (h = msg->m_chain; h; h = h->sh_succ) {
if (h->sh_data != p) {
p = h->sh_data; len = h->sh_len;
if (p == NULL)
return 0;
if (vec && n != veclen)
/* new iovec entry */
vec[n].mv_base = (void *)p, vec[n].mv_len = (su_ioveclen_t)len;
else
vec = NULL;
p += len; n++;
}
else {
/* extend old entry */
len = h->sh_len;
if (vec)
vec[n-1].mv_len += (su_ioveclen_t)len;
p += len;
}
total += len;
}
msg->m_size = total;
return n;
}
/** Insert a header to existing header chain.
*
* Headers are either inserted just before the payload, or after the first
* line, depending on their type.
*
* @param[in] msg message object
* @param[in,out] pub public message structure
* @param prepend if true, add before same type of headers (instead after them)
* @param head head of chain
* @param h header to insert
*
*/
static
void msg_insert_chain(msg_t *msg,
msg_pub_t *pub,
int prepend,
msg_header_t **head,
msg_header_t *h)
{
msg_mclass_t const *mc = msg->m_class;
msg_header_t **hh;
msg_header_t **separator;
msg_header_t **payload;
assert(msg && pub && head && h);
separator = (msg_header_t **)((char *)pub + mc->mc_separator->hr_offset);
payload = (msg_header_t **)((char *)pub + mc->mc_payload->hr_offset);
if (msg_is_request(h)) {
if (pub->msg_status)
pub->msg_status = NULL;
hh = head;
}
else if (msg_is_status(h)) {
if (pub->msg_request)
pub->msg_request = NULL;
hh = head;
}
else if (msg_is_payload(h)) {
/* Append */
hh = msg_chain_tail(msg);
}
else if (prepend) {
if (!msg_is_request(*head) && !msg_is_status(*head))
hh = head;
else
hh = &((*head)->sh_succ);
}
/* Append headers before separator or payload */
else if (*separator && (*separator)->sh_prev)
hh = (*separator)->sh_prev;
else if (*payload && (*payload)->sh_prev)
hh = (*payload)->sh_prev;
else
hh = msg_chain_tail(msg);
msg_insert_here_in_chain(msg, hh, h);
}
/** Insert one or more message header to the chain.
*
* The function msg_insert_here_in_chain() appends message header to the
* chain of headers after the given header.
*
* @param msg message
* @param prev pointer to h_succ of previous fragment in the list
* @param h header to be inserted.
*
* @return The pointer to the last header inserted.
*/
static
void msg_insert_here_in_chain(msg_t *msg,
msg_header_t **prev,
msg_header_t *h)
{
if (h) {
msg_header_t *last, *next;
assert(h->sh_prev == NULL);
assert(prev);
assert(!msg_chain_errors(h));
for (last = h; last->sh_succ; last = last->sh_succ)
;
last->sh_succ = next = *prev;
*prev = h;
h->sh_prev = prev;
if (next)
next->sh_prev = &last->sh_succ;
else
msg->m_tail = &last->sh_succ;
assert(msg->m_chain && msg_chain_errors(msg->m_chain) == 0);
}
}
/**
* Remove a message from header chain.
*
* The function @c msg_chain_remove() removes a message header from the header
* chain.
*
* @param msg pointer to the message
* @param h pointer to the header in the list to be removed
*
* @return The pointer to the header just removed.
*/
static inline
msg_header_t *msg_chain_remove(msg_t *msg, msg_header_t *h)
{
if (h) {
if (h->sh_prev) {
assert(*h->sh_prev == h);
assert(h->sh_succ == NULL || h->sh_succ->sh_prev == &h->sh_succ);
*h->sh_prev = h->sh_succ;
}
if (h->sh_succ)
h->sh_succ->sh_prev = h->sh_prev;
else if (msg && h->sh_prev)
msg->m_tail = h->sh_prev;
h->sh_succ = NULL; h->sh_prev = NULL;
if (msg)
assert(msg_chain_errors(msg->m_chain) == 0);
}
return h;
}
#ifndef NDEBUG
/**Check if header chain contains any loops.
*
* @return
* Return 0 if no loop, -1 otherwise.
*/
static
int msg_chain_loop(msg_header_t const *h)
{
msg_header_t const *h2;
if (!h) return 0;
for (h2 = h->sh_succ; h && h2 && h2->sh_succ; h = h->sh_succ) {
if (h == h2 || h == h2->sh_succ)
return 1;
h2 = h2->sh_succ->sh_succ;
if (h == h2)
return 1;
}
return 0;
}
/** Check header chain consistency.
*
* @return
* Return 0 if consistent, number of errors otherwise.
*/
static
int msg_chain_errors(msg_header_t const *h)
{
if (msg_chain_loop(h))
return -1;
for (; h; h = h->sh_succ) {
if (h->sh_succ && h->sh_succ->sh_prev != &h->sh_succ)
return -1;
if (h->sh_prev && h != (*h->sh_prev))
return -1;
}
return 0;
}
#endif
/* ====================================================================== */
/* Handling message structure - allocating, adding and removing headers */
/** Allocate a header structure
*
* The msg_header_alloc() function allocates a generic MO header structure
* and returns a pointer to it.
*
* @param home memory home
* @param hc header class
* @param extra amount of extra memory to be allocated after header structure
*
* @return
* A pointer to the newly created header object, or @c NULL upon an error.
*/
msg_header_t *msg_header_alloc(su_home_t *home,
msg_hclass_t *hc,
isize_t extra)
{
isize_t size = hc->hc_size;
msg_header_t *h = su_alloc(home, size + extra);
if (h) {
memset(h, 0, size);
h->sh_class = hc;
}
return h;
}
/**Add a (list of) header(s) to the header structure and fragment chain.
*
* The function @c msg_header_add() adds a header or list of headers into
* the given place within the message structure. It also inserts the headers
* into the the message fragment chain, if it exists.
*
* If the header is a prepend header, the new header is inserted before
* existing headers of the same class. If the header is an append header,
* the new header is inserted after existing headers of the same class. If
* the header is a singleton, existing headers of the same class are
* removed. If the header is a list header, the values in the new header are
* added to the existing list.
*
* @param msg message owning the fragment chain
* @param pub public message structure
* @param hh place in message structure to which header is added
* @param h list of header(s) to be added
*/
int msg_header_add(msg_t *msg,
msg_pub_t *pub,
msg_header_t **hh,
msg_header_t *h)
{
msg_header_t **head, *old = NULL, *end;
if (msg == NULL || h == NULL || h == MSG_HEADER_NONE || hh == NULL)
return -1;
if (pub == NULL)
pub = msg->m_object;
head = _msg_chain_head(msg);
if (*head) {
msg_header_t *sh, **prev;
for (sh = h, prev = NULL; sh; sh = sh->sh_next) {
sh->sh_succ = sh->sh_next;
sh->sh_prev = prev;
prev = &sh->sh_succ;
}
}
switch (h->sh_class->hc_kind) {
case msg_kind_single:
case msg_kind_list:
old = (*hh);
break;
case msg_kind_append:
case msg_kind_apndlist:
while (*hh)
hh = &(*hh)->sh_next;
break;
case msg_kind_prepend:
for (end = h; end->sh_next; end = end->sh_next)
;
end->sh_next = *hh;
}
if (*head) {
/* Insert into existing fragment chain */
msg_insert_chain(msg, pub, msg_is_prepend(h), head, h);
/* Remove replaced fragment */
if (old)
msg_chain_remove(msg, old);
}
/* Insert into header list */
*hh = h;
return 0;
}
/**Prepend a (list of) header(s) to the header structure and fragment chain.
*
* The function @c msg_header_prepend() adds a header or list of headers into
* the given place within the message structure. It also inserts the headers
* into the the message fragment chain, if it exists.
*
* Unlike msg_header_add(), msg_header_prepend() always inserts header @a h
* before other headers of the same class. If the header is a singleton,
* existing headers of the same class are removed. If the header is a list
* header, the values in the new header are prepended to the existing list.
*
* @param msg message owning the fragment chain
* @param pub public message structure
* @param hh place in message structure to which header is added
* @param h list of header(s) to be added
*/
int msg_header_prepend(msg_t *msg,
msg_pub_t *pub,
msg_header_t **hh,
msg_header_t *h)
{
msg_header_t **head, *old = NULL, *end;
assert(msg && pub);
if (msg == NULL || h == NULL || h == MSG_HEADER_NONE || hh == NULL)
return -1;
if (pub == NULL)
pub = msg->m_object;
head = _msg_chain_head(msg);
if (*head) {
msg_header_t *sh, **prev;
for (sh = h, prev = NULL; sh; sh = sh->sh_next) {
sh->sh_succ = sh->sh_next;
sh->sh_prev = prev;
prev = &sh->sh_succ;
}
}
switch (h->sh_class->hc_kind) {
case msg_kind_single:
case msg_kind_list:
old = (*hh);
break;
case msg_kind_append:
case msg_kind_apndlist:
case msg_kind_prepend:
for (end = h; end->sh_next; end = end->sh_next)
;
end->sh_next = *hh;
break;
}
if (*head) {
/* Insert into existing fragment chain */
msg_insert_chain(msg, pub, 1, head, h);
/* Remove replaced fragment */
if (old)
msg_chain_remove(msg, old);
}
/* Insert into header list */
*hh = h;
return 0;
}
/** Find place to insert header of the class @a hc. */
msg_header_t **
msg_hclass_offset(msg_mclass_t const *mc, msg_pub_t const *mo, msg_hclass_t *hc)
{
int i;
assert(mc && hc);
if (mc == NULL || hc == NULL)
return NULL;
if (hc->hc_hash > 0) {
unsigned j, N = mc->mc_hash_size;
for (j = hc->hc_hash % N; mc->mc_hash[j].hr_class; j = (j + 1) % N)
if (mc->mc_hash[j].hr_class == hc) {
return (msg_header_t **)((char *)mo + mc->mc_hash[j].hr_offset);
}
}
else
/* Header has no name. */
for (i = 0; i <= 6; i++)
if (hc->hc_hash == mc->mc_request[i].hr_class->hc_hash)
return (msg_header_t **)((char *)mo + mc->mc_request[i].hr_offset);
return NULL;
}
/** Append a parsed header object into the message structure */
static inline void
append_parsed(msg_t *msg, msg_pub_t *mo, msg_href_t const *hr, msg_header_t *h,
int always_into_chain)
{
msg_header_t **hh;
assert(msg); assert(hr->hr_offset);
hh = (msg_header_t **)((char *)mo + hr->hr_offset);
if (msg->m_chain || always_into_chain)
msg_insert_here_in_chain(msg, msg_chain_tail(msg), h);
if (*hh && msg_is_single(h)) {
/* If there is multiple instances of single headers,
put the extra headers into the list of erroneous headers */
msg_error_t **e;
for (e = &mo->msg_error; *e; e = &(*e)->er_next)
;
*e = (msg_error_t *)h;
msg->m_extract_err |= hr->hr_flags;
if (hr->hr_class->hc_critical)
mo->msg_flags |= MSG_FLG_ERROR;
return;
}
while (*hh)
hh = &(*hh)->sh_next;
*hh = h;
}
static int _msg_header_add_list_items(msg_t *msg,
msg_header_t **hh,
msg_header_t const *src);
/**Duplicate and add a (list of) header(s) to the message.
*
* The function @c msg_header_add_dup() duplicates and adds a (list of)
* header(s) into a message structure.
*
* When inserting headers into the fragment chain, a request (or status) is
* inserted first and replaces the existing request (or status). Other
* headers are inserted after the request or status.
*
* If the header is a singleton, existing headers with the same class are
* removed.
*
* @param msg message owning the fragment chain
* @param pub public message structure to which header is added
* @param src list of header(s) to be added
*/
int msg_header_add_dup(msg_t *msg,
msg_pub_t *pub,
msg_header_t const *src)
{
msg_header_t *h, **hh = NULL;
msg_hclass_t *hc = NULL;
if (msg == NULL)
return -1;
if (src == NULL || src == MSG_HEADER_NONE)
return 0;
if (pub == NULL)
pub = msg->m_object;
for ( ;src; src = src->sh_next) {
assert(src->sh_class);
if (!src->sh_class)
return -1;
if (hc != src->sh_class)
hh = msg_hclass_offset(msg->m_class, pub, hc = src->sh_class);
if (hh == NULL)
return -1;
if (!*hh || hc->hc_kind != msg_kind_list) {
int size = hc->hc_size;
isize_t xtra = hc->hc_dxtra(src, size) - size;
char *end;
if (!(h = msg_header_alloc(msg_home(msg), hc, xtra)))
return -1; /* error */
if (!(end = hc->hc_dup_one(h, src, (char *)h + size, xtra)))
return -1; /* error */
if (hc->hc_update)
msg_header_update_params(h->sh_common, 0);
assert(end == (char *)h + size + xtra);
if (msg_header_add(msg, pub, hh, h) < 0)
return -1;
hh = &h->sh_next;
}
else {
if (_msg_header_add_list_items(msg, hh, src) < 0)
break;
}
}
if (src)
return -1;
return 0;
}
/**Duplicate a header as a given type and add the duplicate into message.
*
* The function @c msg_header_add_dup_as() duplicates a header as a instance
* of the given header class. It adds the new copy into the message.
*
* When inserting headers into the fragment chain, a request (or status) is
* inserted first and replaces the existing request (or status). Other
* headers are inserted after the request or status.
*
* If the header is a singleton, existing headers with the same class are
* removed.
*
* @param msg message owning the fragment chain
* @param pub public message structure to which header is added
* @param hc header class for header target type
* @param src list of header(s) to be duplicated and added
*/
int msg_header_add_dup_as(msg_t *msg,
msg_pub_t *pub,
msg_hclass_t *hc,
msg_header_t const *src)
{
if (msg == NULL || hc == NULL)
return -1;
if (src == NULL || src == MSG_HEADER_NONE)
return 0;
if (pub == NULL)
pub = msg->m_object;
return _msg_header_add_dup_as(msg, pub, hc, src);
}
/** Duplicate and add a (list of) header to a message */
static
int _msg_header_add_dup_as(msg_t *msg,
msg_pub_t *pub,
msg_hclass_t *hc,
msg_header_t const *src)
{
msg_header_t *h, **hh;
hh = msg_hclass_offset(msg->m_class, pub, hc);
if (hh == NULL)
return -1;
if (*hh && hc->hc_kind == msg_kind_list)
return _msg_header_add_list_items(msg, hh, src);
if (!(h = msg_header_dup_as(msg_home(msg), hc, src)))
return -1;
return msg_header_add(msg, pub, hh, h);
}
/* Add list items */
static int _msg_header_add_list_items(msg_t *msg,
msg_header_t **hh,
msg_header_t const *src)
{
msg_header_t *h = *hh;
msg_param_t **s = msg_header_params(src->sh_common);
if (!s || !*s)
return 0;
msg_fragment_clear(h->sh_common);
/* Remove empty headers */
for (hh = &h->sh_next; *hh; *hh = (*hh)->sh_next)
msg_chain_remove(msg, *hh);
if (msg_header_join_items(msg_home(msg), h->sh_common, src->sh_common, 1)
< 0)
return -1;
return 0;
}
/** Parse a string as a given header field and add result to the message. */
int msg_header_add_make(msg_t *msg,
msg_pub_t *pub,
msg_hclass_t *hc,
char const *s)
{
msg_header_t *h, **hh;
if (msg == NULL)
return -1;
if (pub == NULL)
pub = msg->m_object;
hh = msg_hclass_offset(msg->m_class, pub, hc);
if (hh == NULL)
return -1;
if (!s)
return 0;
if (*hh && hc->hc_kind == msg_kind_list) {
/* Add list items */
msg_header_t *h = *hh;
msg_param_t **d;
char *s0;
skip_lws(&s);
d = msg_header_params(h->sh_common); assert(d);
msg_fragment_clear(h->sh_common);
/* Remove empty headers */
for (hh = &h->sh_next; *hh; *hh = (*hh)->sh_next)
msg_chain_remove(msg, *hh);
s0 = su_strdup(msg_home(msg), s);
if (!s0 || msg_commalist_d(msg_home(msg), &s0, d, msg_token_scan) < 0)
return -1;
return 0;
}
if (!(h = msg_header_make(msg_home(msg), hc, s)))
return -1;
return msg_header_add(msg, pub, hh, h);
}
/**Add string contents to message.
*
* Duplicate a string containing headers (or a message body, if the string
* starts with linefeed), parse it and add resulting header objects to the
* message object.
*
* @param msg message object
* @param pub message header structure where heades are added (may be NULL)
* @param str string to be copied and parsed (not modified, may be NULL)
*
* @retval 0 when succesful
* @retval -1 upon an error
*/
int msg_header_add_str(msg_t *msg,
msg_pub_t *pub,
char const *str)
{
char *s;
if (!msg)
return -1;
if (!str)
return 0;
s = su_strdup(msg_home(msg), str);
if (s == NULL)
return -1;
return msg_header_parse_str(msg, pub, s);
}
/**Add string to message.
*
* Parse a string containing headers (or a message body, if the string
* starts with linefeed) and add resulting header objects to the message
* object.
*
* @param msg message object
* @param pub message header structure where heades are added (may be NULL)
* @param s string to be parsed (and modified)
*
* @retval 0 when succesful
* @retval -1 upon an error
*
* @sa msg_header_add_str(), url_headers_as_string()
*
* @since New in @VERSION_1_12_4.
*/
int msg_header_parse_str(msg_t *msg,
msg_pub_t *pub,
char *s)
{
if (!msg)
return -1;
if (pub == NULL)
pub = msg->m_object;
if (s) {
size_t ssiz = strlen(s), used = 0;
ssize_t n = 1;
while (ssiz > used) {
if (IS_CRLF(s[used]))
break;
n = msg_extract_header(msg, pub, s + used, ssiz - used, 1);
if (n <= 0)
break;
used += n;
}
if (n > 0 && ssiz > used) {
used += CRLF_TEST(s + used);
if (ssiz > used)
msg_extract_payload(msg, pub, NULL, ssiz - used,
s + used, ssiz - used, 1);
}
if (n <= 0)
return -1;
}
return 0;
}
/** Insert a (list of) header(s) to the fragment chain.
*
* The function @c msg_header_insert() inserts header or list of headers
* into a message structure. It also inserts them into the the message
* fragment chain, if it exists.
*
* When inserting headers into the fragment chain, a request (or status) is
* inserted first and replaces the existing request (or status). Other
* headers are inserted after the request or status.
*
* If there can be only one header field of this type (hc_kind is
* msg_kind_single), existing header objects with the same class are
* removed.
*
* @param msg message object owning the fragment chain
* @param pub public message structure to which header is added
* @param h list of header(s) to be added
*/
int msg_header_insert(msg_t *msg, msg_pub_t *pub, msg_header_t *h)
{
msg_header_t **hh;
assert(msg);
if (msg == NULL || h == NULL || h == MSG_HEADER_NONE ||
h->sh_class == NULL)
return -1;
if (pub == NULL)
pub = msg->m_object;
hh = msg_hclass_offset(msg->m_class, pub, h->sh_class);
return msg_header_add(msg, pub, hh, h);
}
/**Remove a header from the header structure and fragment chain.
*
* The function @c msg_header_remove() removes a header from a message
* structure. It also removes the message from the message fragment chain
* and clears the encoding of other headers objects that share same
* encoding.
*
* @param msg message owning the fragment chain
* @param pub public message structure to which header is added
* @param h header to be removed
*/
int msg_header_remove(msg_t *msg, msg_pub_t *pub, msg_header_t *h)
{
msg_header_t **hh, **hh0;
if (msg == NULL || h == NULL || h == MSG_HEADER_NONE ||
h->sh_class == NULL)
return -1;
if (pub == NULL)
pub = msg->m_object;
/* First, remove from public structure (msg_pub_t) */
hh0 = msg_hclass_offset(msg->m_class, pub, h->sh_class);
if (!hh0)
return -1;
for (hh = hh0; *hh; hh = &(*hh)->sh_next) {
if (*hh == h) {
*hh = h->sh_next;
break;
}
}
if (h->sh_data) {
void const *data = (char *)h->sh_data + h->sh_len;
for (hh = hh0; *hh; hh = &(*hh)->sh_next) {
if (data == (char *)(*hh)->sh_data + (*hh)->sh_len) {
(*hh)->sh_data = NULL, (*hh)->sh_len = 0;
}
}
}
msg_chain_remove(msg, h);
return 0;
}
/**Remove a header list from the header structure and fragment chain.
*
* The function @c msg_header_remove_all() removes a list of headers from a
* message structure. It also removes the message from the message fragment
* chain and clears the encoding of other headers objects that share same
* encoding.
*
* @param msg message owning the fragment chain
* @param pub public message structure to which header is added
* @param h header list to be removed
*/
int msg_header_remove_all(msg_t *msg, msg_pub_t *pub, msg_header_t *h)
{
msg_header_t **hh, **hh0;
void const *data;
if (msg == NULL || h == NULL || h == MSG_HEADER_NONE ||
h->sh_class == NULL)
return -1;
if (pub == NULL)
pub = msg->m_object;
hh0 = msg_hclass_offset(msg->m_class, pub, h->sh_class);
if (!hh0)
return -1;
data = (char *)h->sh_data + h->sh_len;
/* First, remove from public structure (msg_pub_t) */
for (hh = hh0; *hh; hh = &(*hh)->sh_next) {
if (*hh == h) {
break;
}
if (data && data == (char *)(*hh)->sh_data + (*hh)->sh_len) {
h->sh_data = NULL, h->sh_len = 0;
(*hh)->sh_data = NULL, (*hh)->sh_len = 0;
}
}
/* Remove from header chain */
while (h) {
h->sh_data = NULL, h->sh_len = 0;
msg_chain_remove(msg, h);
h = h->sh_next;
}
*hh = NULL;
return 0;
}
/** Replace a header item with a (list of) header(s).
*
* The function @c msg_header_replace() removes a header structure from
* message and replaces it with a new one or a list of headers. It inserts
* the new headers into the the message fragment chain, if it exists.
*
* @param msg message object owning the fragment chain
* @param pub public message structure to which header is added
* @param replaced old header to be removed
* @param h list of header(s) to be added
*/
int msg_header_replace(msg_t *msg,
msg_pub_t *pub,
msg_header_t *replaced,
msg_header_t *h)
{
msg_header_t *h0, *last, **hh, **hh0;
if (msg == NULL || replaced == NULL)
return -1;
if (h == NULL || h == MSG_HEADER_NONE || h->sh_class == NULL)
return msg_header_remove(msg, pub, replaced);
if (pub == NULL)
pub = msg->m_object;
hh = hh0 = msg_hclass_offset(msg->m_class, pub, h->sh_class);
if (hh == NULL)
return -1;
if (replaced == NULL)
return msg_header_add(msg, pub, hh, h);
assert(h->sh_prev == NULL); /* Must not be in existing chain! */
for (last = h; last->sh_next; last = last->sh_next) {
if ((last->sh_succ = last->sh_next))
last->sh_next->sh_prev = &last->sh_succ;
}
for (h0 = *hh; h0; hh = &h0->sh_next, h0 = *hh) {
if (replaced == h0)
break;
}
if (h0 == NULL)
return -1;
*hh = h; /* Replace in list */
last->sh_next = replaced->sh_next;
if (replaced->sh_prev) {
*replaced->sh_prev = h;
h->sh_prev = replaced->sh_prev;
if ((last->sh_succ = replaced->sh_succ))
last->sh_succ->sh_prev = &last->sh_succ;
if (msg->m_tail == &replaced->sh_succ)
msg->m_tail = &last->sh_succ;
}
assert(msg->m_tail != &replaced->sh_succ);
replaced->sh_next = NULL;
replaced->sh_prev = NULL;
replaced->sh_succ = NULL;
if (replaced->sh_data) {
/* Remove cached encoding if it is shared with more than one header fragments */
int cleared = 0;
void const *data = (char *)replaced->sh_data + replaced->sh_len;
for (hh = hh0; *hh; hh = &(*hh)->sh_next) {
if (data == (char *)(*hh)->sh_data + (*hh)->sh_len) {
(*hh)->sh_data = NULL, (*hh)->sh_len = 0, cleared = 1;
}
}
if (cleared)
replaced->sh_data = NULL, replaced->sh_len = 0;
}
return 0;
}
/** Free a header structure */
void msg_header_free(su_home_t *home, msg_header_t *h)
{
su_free(home, h);
}
/** Free a (list of) header structures */
void msg_header_free_all(su_home_t *home, msg_header_t *h)
{
msg_header_t *h_next;
while (h) {
h_next = h->sh_next;
su_free(home, h);
h = h_next;
}
}