/*-
 * Copyright (c) 2006 Verdens Gang AS
 * Copyright (c) 2006-2008 Linpro AS
 * All rights reserved.
 *
 * Author: Poul-Henning Kamp <phk@phk.freebsd.dk>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * $Id: cache_vrt_esi.c 3744 2009-02-11 08:02:31Z tfheen $
 *
 * Runtime support for compiled VCL programs ESI processing.
 *
 * The basic ESI 1.0 is a very simple specification:
 *	http://www.w3.org/TR/esi-lang
 * But it seems that Oracle and Akamai has embrodiered it to be almost a new
 * layer of scripting language in HTTP transmission chains.
 *
 * It is not obvious how much help the "advanced" features of ESI really
 * are to users, so our aim is to pick the fruit starting with the lowest
 * hanging, esi:include
 */

#include "config.h"

#include <stdio.h>
#include <unistd.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>

#include "shmlog.h"
#include "vrt.h"
#include "vcl.h"
#include "cache.h"

#define NDEFELEM		10

/*--------------------------------------------------------------------*/

struct esi_bit {
	VTAILQ_ENTRY(esi_bit)	list;
	char			chunk_length[20];
	txt			verbatim;
	txt			host;
	txt			include;
	int			free_this;
};

VTAILQ_HEAD(esibithead, esi_bit);

struct esi_ptr {
	const char		*p;
	struct storage		*st;
};

struct esi_work {
	struct sess		*sp;
	size_t			off;

	struct esi_ptr		s;
	struct esi_ptr		p;

	txt			tag;

	txt			t;
	struct esi_bit		*eb;
	struct esi_bit		*ebl;	/* list of */
	int			neb;
	int			remflg;	/* inside <esi:remove> </esi:remove> */
	int			incmt;	/* inside <!--esi ... --> comment */
};

/*--------------------------------------------------------------------
 * Move the parse-pointer forward.
 */

static void
Nep(struct esi_ptr *ep)
{
	static const char * const finis = "";

	if (ep->p == finis)
		return;
	ep->p++;
	if (ep->p < (char*)ep->st->ptr + ep->st->len) 
		return;
	ep->st = VTAILQ_NEXT(ep->st, list);
	if (ep->st != NULL) {
		ep->p = (char *)ep->st->ptr;
		return;
	}
	ep->p = finis;
	return;
}

static void
N(struct esi_work *ew)
{

	if (*ew->p.p != '\0')
		ew->off++;
	Nep(&ew->p);
}

/*--------------------------------------------------------------------
 * Strcmp for objects pointers
 */

static int
CMP(const struct esi_ptr *ep, const char *str)
{
	struct esi_ptr p2;

	for (p2 = *ep; *str == *p2.p; str++)
		Nep(&p2);
	return (*str);
}


/*--------------------------------------------------------------------
 * Report a parsing error
 *
 * XXX: The "at xxx" count is usually the tail of the sequence.  Since we
 * XXX: wander over the storage in an oderly manner now, we could keep
 * XXX: track of line+pos and record the beginning of the stuff that
 * XXX: offends os in the central dispatch loop.
 * XXX: This is left a an excercise for the reader.
 */

static void
esi_error(const struct esi_work *ew, const char *p, int i, const char *err)
{
	int ellipsis = 0;
	char buf[256], *q;
	txt t;

	VSL_stats->esi_errors++;
	if (i == 0)
		i = p - ew->t.b;
	if (i > 20) {
		i = 20;
		ellipsis = 1;
	}
	q = buf;
	q += sprintf(buf, "at %zu: %s \"", ew->off, err);
	while (i > 0) {
		if (*p >= ' ' && *p <= '~') {
			*q++ = *p;
		} else if (*p == '\n') {
			*q++ = '\\';
			*q++ = 'n';
		} else if (*p == '\r') {
			*q++ = '\\';
			*q++ = 'r';
		} else if (*p == '\t') {
			*q++ = '\\';
			*q++ = 't';
		} else {
			/* XXX: use %%%02x instead ? */
			q += sprintf(q, "\\x%02x", *p & 0xff);
		}
		p++;
		i--;
	}
	if (ellipsis) {
		*q++ = '[';
		*q++ = '.';
		*q++ = '.';
		*q++ = '.';
		*q++ = ']';
	}
	*q++ = '"';
	*q++ = '\0';
	t.b = buf;
	t.e = q;
	WSPR(ew->sp, SLT_ESI_xmlerror, t);
}

/*--------------------------------------------------------------------
 * Add ESI bit to object
 */

static void
esi_addbit(struct esi_work *ew, const char *verbatim, unsigned len)
{

	if (ew->neb == 0) {
		ew->ebl = calloc(NDEFELEM, sizeof(struct esi_bit));
		XXXAN(ew->ebl);
		ew->neb = NDEFELEM;
		ew->ebl->free_this = 1;
	}
	ew->eb = ew->ebl;
	ew->ebl++;
	ew->neb--;


	VTAILQ_INSERT_TAIL(&ew->sp->obj->esibits, ew->eb, list);
	if (verbatim != NULL) {
		ew->eb->verbatim.b = TRUST_ME(verbatim);
		if (len > 0)
			ew->eb->verbatim.e = TRUST_ME(verbatim + len);
		sprintf(ew->eb->chunk_length, "%x\r\n", Tlen(ew->eb->verbatim));
		if (params->esi_syntax & 0x4)
			VSL(SLT_Debug, ew->sp->fd, "AddBit: %d <%.*s>",
			    Tlen(ew->eb->verbatim),
			    Tlen(ew->eb->verbatim),
			    ew->eb->verbatim.b);
	} else
		ew->eb->verbatim.b = ew->eb->verbatim.e = (void*)ew->eb;
}

/*--------------------------------------------------------------------*/

static void
esi_addpfx(struct esi_work *ew)
{
	const char *ep;

	if (ew->remflg) {
		/* In <esi:remove...> don't add anything */
		ew->s = ew->p;
		return;
	}
	while (ew->s.st != ew->p.st) {
		ep = (const char *)(ew->s.st->ptr + ew->s.st->len);
		esi_addbit(ew, ew->s.p, ep - ew->s.p);
		ew->s.p = ep;
		Nep(&ew->s);
	}
	if (ew->s.st != NULL && ew->p.p != ew->s.p)
		esi_addbit(ew, ew->s.p, ew->p.p - ew->s.p);
	ew->s.p = ew->p.p;
}

/*--------------------------------------------------------------------
 * Tease the next attribute and value out of an XML element.
 *
 * XXX: is the syntax correct ?
 */

static int
esi_attrib(const struct esi_work *ew, txt *in, txt *attrib, txt *val)
{

	AN(*in->b);
	/* Skip leading blanks */
	while(in->b < in->e && isspace(*in->b))
		in->b++;

	/* Nothing found */
	if (in->b >= in->e)
		return (0);

	if (!isalpha(*in->b)) {
		/* XXX error */
		esi_error(ew, in->b, 1, "XML 1.0 Illegal attribute character");
		return (-1);
	}

	/* Attribute name until '=' or space */
	*attrib = *in;
	while(in->b < in->e && *in->b != '=' && !isspace(*in->b)) {
		if (!isalnum(*in->b)) {
			esi_error(ew, attrib->b, 1 + (in->b - attrib->b),
			    "XML 1.0 Illegal attribute character");
			return (-1);
		}
		in->b++;
	}
	attrib->e = in->b;

	if (in->b >= in->e || isspace(*in->b)) {
		/* Attribute without value */
		val->b = val->e = in->b;
		return (1);
	}

	/* skip '=' */
	in->b++;

	if (isspace(*in->b)) {
		val->e = val->b = in->b;;
		*val->e = '\0';
		in->b++;
		return (1);
	}

	/* Value, if any ? */
	*val = *in;
	if (in->b >= in->e)
		return (1);

	if (*in->b == '"') {
		/* Skip quote */
		in->b++;
		val->b++;

		/* Anything goes, until next quote */
		while(in->b < in->e && *in->b != '"')
			in->b++;
		val->e = in->b;

		if (in->b >= in->e) {
			esi_error(ew, val->b, in->e - val->b,
			    "XML 1.0 missing ending quote");
			return (-1);
		}

		/* Skip quote */
		in->b++;
	} else {
		/* Anything until whitespace */
		while(in->b < in->e && !isspace(*in->b))
			in->b++;
		val->e = in->b;
		in->b++;
	}
	*val->e = '\0';
	return (1);
}

/*--------------------------------------------------------------------
 * Add one piece to the output, either verbatim or include
 */

static void
esi_handle_include(struct esi_work *ew)
{
	struct esi_bit *eb;
	char *p, *q;
	txt t = ew->tag;
	txt tag;
	txt val;
	unsigned u, v;
	struct ws *ws;

	if (ew->eb == NULL || ew->eb->include.b != NULL)
		esi_addbit(ew, NULL, 0);
	eb = ew->eb;
	VSL(SLT_Debug, ew->sp->fd, "Incl \"%.*s\"", t.e - t.b, t.b);
	while (esi_attrib(ew, &t, &tag, &val) == 1) {
		if (params->esi_syntax & 0x4)
			VSL(SLT_Debug, ew->sp->fd, "<%.*s> -> <%.*s>",
			    tag.e - tag.b, tag.b, val.e - val.b, val.b);
		if (Tlen(tag) != 3 || memcmp(tag.b, "src", 3))
			continue;
		if (Tlen(val) == 0) {
			esi_error(ew, tag.b, Tlen(tag),
			    "ESI esi:include src attribute withou value");
			continue;
		}

		if (Tlen(val) > 7 && !memcmp(val.b, "http://", 7)) {
			/*  Rewrite to Host: header inplace */
			eb->host.b = val.b;
			memcpy(eb->host.b, "Host: ", 6);
			q = eb->host.b + 6;
			for (p = eb->host.b + 7; p < val.e && *p != '/'; p++)
				*q++ = *p;
			*q++ = '\0';
			eb->host.e = q;
			assert(*p == '/');	/* XXX */
			/* The rest is the URL */
			eb->include.b = p;
			eb->include.e = val.e;
		} else if (Tlen(val) > 0 && *val.b == '/') {
			/* Absolute on this host */
			eb->include = val;
		} else {

			/*
			 * Decision:  We interpret the relative URL against
			 * the actual URL we asked the backend for.
			 * The client's request URL may be entirely
			 * different and have been rewritten underway.
			 */
			CHECK_OBJ_NOTNULL(ew->sp, SESS_MAGIC);
			CHECK_OBJ_NOTNULL(ew->sp->bereq, BEREQ_MAGIC);
			CHECK_OBJ_NOTNULL(ew->sp->bereq->http, HTTP_MAGIC);
			tag = ew->sp->bereq->http->hd[HTTP_HDR_URL];

			/* Use the objects WS to store the result */
			CHECK_OBJ_NOTNULL(ew->sp->obj, OBJECT_MAGIC);
			ws = ew->sp->obj->ws_o;
			WS_Assert(ws);

			/* Look for the last '/' before a '?' */
			q = NULL;
			for (p = tag.b; p < tag.e && *p != '?'; p++)
				if (*p == '/')
					q = p;
			if (q != NULL)
				tag.e = q + 1;

			u = WS_Reserve(ws, 0);
			v = snprintf(ws->f, u - 1, "%.*s%.*s",
			    pdiff(tag.b, tag.e), tag.b,
			    pdiff(val.b, val.e), val.b);
			v++;
			xxxassert(v < u);
			eb->include.b = ws->f;
			eb->include.e = ws->f + v;
			WS_Release(ws, v);
		}
	}
}

/*--------------------------------------------------------------------
 * See if this looks like XML: first non-white char must be '<'
 */

static int
looks_like_xml(const struct object *obj) {
	struct storage *st;
	unsigned u;

	VTAILQ_FOREACH(st, &obj->store, list) {
		AN(st);
		for (u = 0; u < st->len; u++) {
			if (isspace(st->ptr[u]))
				continue;
			if (st->ptr[u] == '<')
				return (1);
			else
				return (0);
		}
	}
	return (0);
}

/*--------------------------------------------------------------------
 * A quick stroll through the object, to find out if it contains any
 * esi sequences at all.
 */

static int
contain_esi(const struct object *obj) {
	struct storage *st;
	unsigned u;
	const char *r, *r2;
	static const char * const wanted = "<esi:";
	static const char * const wanted2 = "<!--esi";

	/*
	 * Do a fast check to see if there is any '<esi:' sequences at all
	 */
	r = wanted;
	r2 = wanted2;
	VTAILQ_FOREACH(st, &obj->store, list) {
		AN(st);
		for (u = 0; u < st->len; u++) {
			if (st->ptr[u] != *r) {
				r = wanted;
			} else if (*++r == '\0')
				return (1);
			if (st->ptr[u] != *r2) {
				r2 = wanted2;
			} else if (*++r2 == '\0')
				return (1);
		}
	}
	return (0);
}

/*--------------------------------------------------------------------*/

static void
parse_esi_comment(struct esi_work *ew)
{

	esi_addpfx(ew);

	N(ew); N(ew); N(ew); N(ew); N(ew); N(ew); N(ew);
	assert(!ew->incmt);
	ew->incmt = 1;
	ew->s.p = ew->p.p;
}

/*--------------------------------------------------------------------*/

static void
parse_comment(struct esi_work *ew)
{

	do {
		N(ew);
		if (*ew->p.p == '-' && !CMP(&ew->p, "-->")) {
			N(ew);
			N(ew);
			N(ew);
			break;
		}
	} while (*ew->p.p != '\0');
}

/*--------------------------------------------------------------------*/

static void
parse_cdata(struct esi_work *ew)
{

	esi_addpfx(ew);

	do {
		N(ew);
		if (*ew->p.p == ']' && !CMP(&ew->p, "]]>")) {
			N(ew);
			N(ew);
			N(ew);
			break;
		}
	} while (*ew->p.p != '\0');
}

/*--------------------------------------------------------------------*/

static void
parse_esi_tag(struct esi_work *ew, int closing)
{
	int l, ll, empty;
	struct esi_ptr px;
	char *q;

	esi_addpfx(ew);

	do
		N(ew);
	while (*ew->p.p != '>' && *ew->p.p != '\0');
	if (*ew->p.p == '\0') {
		esi_addpfx(ew);
		esi_error(ew, ew->s.p, 0,
		    "XML 1.0 incomplete language element");
		return;
	}
	N(ew);

	if (ew->p.st == ew->s.st) {
		ew->tag.b = TRUST_ME(ew->s.p);
		ew->tag.e = TRUST_ME(ew->p.p);
	} else {
		/*
		 * The element is spread over more than one storage
		 * segment, pull it together in the object workspace
		 * XXX: Ideally, we should only pull together the bits
		 * XXX: we need, like the filename.
		 */
		ew->tag.b = ew->sp->obj->ws_o->f;
		ew->tag.e = ew->tag.b + WS_Reserve(ew->sp->obj->ws_o, 0);
		px = ew->s;
		q = ew->tag.b;
		while (px.p != ew->p.p) {
			xxxassert(q < ew->tag.e);
			*q++ = *px.p;
			Nep(&px);
		}
		ew->tag.e = q;
		WS_Release(ew->sp->obj->ws_o, Tlen(ew->tag));
	}
	ll = Tlen(ew->tag);
	ew->tag.b++;
	ew->tag.e--;
	empty = (ew->tag.e[-1] == '/') ? 1 : 0; 
	if (empty)
		ew->tag.e--;

	if (empty && closing)
		esi_error(ew, ew->s.p, ll,
		    "XML 1.0 empty and closing element");

	ew->tag.b += 4 + (closing ? 1 : 0);
	l = Tlen(ew->tag);
	VSL(SLT_Debug, ew->sp->fd,
	    "tag {%.*s} %d %d %d", l, ew->tag.b, ew->remflg, empty, closing);
	if (l >= 6 && !memcmp(ew->tag.b, "remove", 6)) {
		if (empty) {
			/* XXX ?? */
		} else if (closing) {
			if (!ew->remflg)
				esi_error(ew, ew->s.p, ll,
				    "ESI 1.0 esi:remove not opened");
			ew->remflg = 0;
		} else {
			if (ew->remflg)
				esi_error(ew, ew->s.p, ll,
				    "ESI 1.0 forbids nested esi:remove");
			ew->remflg = 1;
		}
	} else if (ew->remflg) {
		esi_error(ew, ew->s.p, ll,
		    "ESI 1.0 forbids esi: elements inside esi:remove");
	} else if (l >= 7 && !memcmp(ew->tag.b, "comment", 7)) {
		if (closing)
			esi_error(ew, ew->s.p, ll,
			    "ESI 1.0 closing esi:comment illegal");
		else if (!empty)
			esi_error(ew, ew->s.p, ll,
			    "ESI 1.0 wants empty esi:comment");
	} else if (l >= 7 && !memcmp(ew->tag.b, "include", 7)) {
		if (closing) {
			esi_error(ew, ew->s.p, ll,
			    "ESI 1.0 closing esi:include illegal");
		} else if (!empty) {
			esi_error(ew, ew->s.p, ll,
			    "ESI 1.0 wants empty esi:include");
		} 
		ew->tag.b += 7;
		esi_handle_include(ew);
	} else {
		esi_error(ew, ew->s.p, ll,
		    "ESI 1.0 unimplemented element");
	}
	ew->s = ew->p;
}

/*--------------------------------------------------------------------*/

void
VRT_ESI(struct sess *sp)
{
	struct esi_work *ew, eww[1];

	CHECK_OBJ_NOTNULL(sp, SESS_MAGIC);
	CHECK_OBJ_NOTNULL(sp->obj, OBJECT_MAGIC);

	assert(sp->obj->busy_sp);
	if (sp->cur_method != VCL_MET_FETCH) {
		/* XXX: we should catch this at compile time */
		WSP(sp, SLT_VCL_error,
		    "esi can only be called from vcl_fetch");
		return;
	}

	if (VTAILQ_EMPTY(&sp->obj->store))
		return;

	if (!(params->esi_syntax & 0x00000001)) {
		/*
		 * By default, we will not ESI process an object where
		 *  the first non-space character is different from '<'
		 */
		if (!looks_like_xml(sp->obj)) {
			WSP(sp, SLT_ESI_xmlerror,
			    "No ESI processing, first char not '<'");
			return;
		}
	}

	/*
	 * Do a fast check to see if there is any '<esi:' sequences at all
	 */
	if (!contain_esi(sp->obj))
		return;

	VSL_stats->esi_parse++;
	/* XXX: only if GET ? */
	ew = eww;
	memset(eww, 0, sizeof eww);
	ew->sp = sp;
	ew->off = 1;

	ew->p.st = VTAILQ_FIRST(&sp->obj->store);
	AN(ew->p.st);
	ew->p.p = (char *)ew->p.st->ptr;

	/* ->s points to the first un-dealt-with byte */
	ew->s = ew->p;

	while (*ew->p.p != '\0') {

		if (ew->incmt && *ew->p.p == '-' && !CMP(&ew->p, "-->")) {
			/* End of ESI comment */
			esi_addpfx(ew);
			N(ew);
			N(ew);
			N(ew);
			ew->s = ew->p;
			ew->incmt = 0;
			continue;
		}
		/* Skip forward to the first '<' */
		if (*ew->p.p != '<') {
			N(ew);
			continue;
		}

		if (!CMP(&ew->p, "<!--esi")) {
			parse_esi_comment(ew);
		} else if (!CMP(&ew->p, "<!--")) {
			parse_comment(ew);
		} else if (!CMP(&ew->p, "</esi")) {
			parse_esi_tag(ew, 1);
		} else if (!CMP(&ew->p, "<esi:")) {
			parse_esi_tag(ew, 0);
		} else if (!CMP(&ew->p, "<![CDATA[")) {
			parse_cdata(ew);
		} else {
			/*
			 * Something we don't care about, just skip it.
			 */
			N(ew);
			if (!(params->esi_syntax & 0x2)) {
				/* XXX: drop this ? */
				do {
					N(ew);
				} while (*ew->p.p != '>' && *ew->p.p != '\0');
			}
		}
	}
	esi_addpfx(ew);

	/*
	 * XXX: we could record the starting point of these elements
	 * XXX: so that the char-index were more useful, but we are
	 * XXX: not trivially able to print their contents, so leave
	 * XXX: it like this for now, pending more thought about the
	 * XXX: proper way to report these errors.
	 */
	if (ew->remflg)
		esi_error(ew, ew->t.e, -1,
		    "ESI 1.0 unterminated <esi:remove> element");
	if (ew->incmt)
		esi_error(ew, ew->t.e, -1,
		    "ESI 1.0 unterminated <!--esi comment");

	/*
	 * Our ESI implementation needs chunked encoding
	 */
	http_Unset(sp->obj->http, H_Content_Length);
	http_PrintfHeader(sp->wrk, sp->fd, sp->obj->http,
	    "Transfer-Encoding: chunked");
}

/*--------------------------------------------------------------------*/

void
ESI_Deliver(struct sess *sp)
{
	struct esi_bit *eb;
	struct object *obj;

	WRW_Reserve(sp->wrk, &sp->fd);
	VTAILQ_FOREACH(eb, &sp->obj->esibits, list) {
		if (Tlen(eb->verbatim)) {
			if (sp->http->protover >= 1.1)
				(void)WRW_Write(sp->wrk, eb->chunk_length, -1);
			sp->acct_req.bodybytes += WRW_Write(sp->wrk,
			    eb->verbatim.b, Tlen(eb->verbatim));
			if (sp->http->protover >= 1.1)
				(void)WRW_Write(sp->wrk, "\r\n", -1);
		}
		if (eb->include.b == NULL ||
		    sp->esis >= params->max_esi_includes)
			continue;

		if (WRW_FlushRelease(sp->wrk)) {
			vca_close_session(sp, "remote closed");
			return;
		}

		sp->esis++;
		obj = sp->obj;
		sp->obj = NULL;
		*sp->http = *sp->http0;
		/* XXX: reset sp->ws */
		http_SetH(sp->http, HTTP_HDR_URL, eb->include.b);
		if (eb->host.b != NULL)  {
			http_Unset(sp->http, H_Host);
			http_Unset(sp->http, H_If_Modified_Since);
			http_SetHeader(sp->wrk, sp->fd, sp->http, eb->host.b);
		}
		/*
		 * XXX: We should decide if we should cache the director
		 * XXX: or not (for session/backend coupling).  Until then
		 * XXX: make sure we don't trip up the check in vcl_recv.
		 */
		sp->director = NULL;
		sp->director_sess = NULL;	
		sp->step = STP_RECV;
		http_ForceGet(sp->http);

		/* Don't do conditionals */
		sp->http->conds = 0;
		http_Unset(sp->http, H_If_Modified_Since);

		/* Client content already taken care of */
		http_Unset(sp->http, H_Content_Length);

		while (1) {
			CNT_Session(sp);
			if (sp->step == STP_DONE)
				break;
			AN(sp->wrk);
			WSL_Flush(sp->wrk, 0);
			DSL(0x20, SLT_Debug, sp->id, "loop waiting for ESI");
			(void)usleep(10000);
		}
		assert(sp->step == STP_DONE);
		sp->esis--;
		sp->obj = obj;
		WRW_Reserve(sp->wrk, &sp->fd);
		if (sp->fd < 0)
			break;
	}
	if (sp->esis == 0 && sp->http->protover >= 1.1)
		(void)WRW_Write(sp->wrk, "0\r\n\r\n", -1);
	if (WRW_FlushRelease(sp->wrk))
		vca_close_session(sp, "remote closed");
}

/*--------------------------------------------------------------------*/

void
ESI_Destroy(struct object *o)
{
	struct esi_bit *eb;

	/*
	 * Delete esi_bits from behind and free(3) the ones that want to be.
	 */
	while (!VTAILQ_EMPTY(&o->esibits)) {
		eb = VTAILQ_LAST(&o->esibits, esibithead);
		VTAILQ_REMOVE(&o->esibits, eb, list);
		if (eb->free_this)
			free(eb);
	}
}

