<commit_before>/*-
 * Copyright (c) 2006 Verdens Gang AS
 * Copyright (c) 2006-2011 Varnish Software AS
 * All rights reserved.
 *
 * Author: <NAME> <<EMAIL>>
 *
 * 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.
 *
 * This file contains the central state machine for pushing requests.
 *
 * We cannot just use direct calls because it is possible to kick a
 * request back to the lookup stage (usually after a rewrite).  The
 * state engine also allows us to break the processing up into some
 * logical chunks which improves readability a little bit.
 *
 * Since the states are rather nasty in detail, I have decided to embedd
 * a dot(1) graph in the source code comments.  So to see the big picture,
 * extract the DOT lines and run though dot(1), for instance with the
 * command:
 *	sed -n '/^DOT/s///p' cache_center.c | dot -Tps > /tmp/_.ps
 */

/*
DOT digraph vcl_center {
xDOT	page="8.2,11.5"
DOT	size="7.2,10.5"
DOT	margin="0.5"
DOT	center="1"
DOT acceptor [
DOT	shape=hexagon
DOT	label="Request received"
DOT ]
DOT ERROR [shape=plaintext]
DOT RESTART [shape=plaintext]
DOT acceptor -> start [style=bold,color=green]
 */

#include "config.h"

#include <stdio.h>
#include <errno.h>
#include <math.h>
#include <poll.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#ifndef HAVE_SRANDOMDEV
#include "compat/srandomdev.h"
#endif

#include "vcl.h"
#include "cli_priv.h"
#include "cache.h"
#include "hash_slinger.h"
#include "stevedore.h"
#include "vsha256.h"

static unsigned xids;

/*--------------------------------------------------------------------
 * WAIT
 * Wait (briefly) until we have a full request in our htc.
 */

static int
cnt_wait(struct sess *sp)
{
	int i;
	struct pollfd pfd[1];

	CHECK_OBJ_NOTNULL(sp, SESS_MAGIC);
	AZ(sp->vcl);
	AZ(sp->obj);
	assert(sp->xid == 0);

	i = HTC_Complete(sp->htc);
	if (i == 0 && params->session_linger > 0) {
		pfd[0].fd = sp->fd;
		pfd[0].events = POLLIN;
		pfd[0].revents = 0;
		i = poll(pfd, 1, params->session_linger);
		if (i)
			i = HTC_Rx(sp->htc);
	}
	if (i == 0) {
		WSL(sp->wrk, SLT_Debug, sp->fd, "herding");
		sp->wrk->stats.sess_herd++;
		SES_Charge(sp);
		sp->wrk = NULL;
		vca_return_session(sp);
		return (1);
	}
	if (i == 1) {
		sp->step = STP_START;
		return (0);
	}
	if (i == -2)
		vca_close_session(sp, "overflow");
	else if (i == -1 && Tlen(sp->htc->rxbuf) == 0 &&
	    (errno == 0 || errno == ECONNRESET))
		vca_close_session(sp, "EOF");
	else
		vca_close_session(sp, "error");
	sp->step = STP_DONE;
	return (0);
}

/*--------------------------------------------------------------------
 * We have a refcounted object on the session, now deliver it.
 *
DOT subgraph xcluster_prepresp {
DOT	prepresp [
DOT		shape=ellipse
DOT		label="Filter obj.->resp."
DOT	]
DOT	vcl_deliver [
DOT		shape=record
DOT		label="vcl_deliver()|resp."
DOT	]
DOT	prepresp -> vcl_deliver [style=bold,color=green]
DOT	prepresp -> vcl_deliver [style=bold,color=cyan]
DOT	prepresp -> vcl_deliver [style=bold,color=red]
DOT	prepresp -> vcl_deliver [style=bold,color=blue,]
DOT	vcl_deliver -> deliver [style=bold,color=green,label=deliver]
DOT	vcl_deliver -> deliver [style=bold,color=red]
DOT	vcl_deliver -> deliver [style=bold,color=blue]
DOT     vcl_deliver -> errdeliver [label="error"]
DOT     errdeliver [label="ERROR",shape=plaintext]
DOT     vcl_deliver -> rstdeliver [label="restart",color=purple]
DOT     rstdeliver [label="RESTART",shape=plaintext]
DOT     vcl_deliver -> streambody [style=bold,color=cyan,label="deliver"]
DOT }
 *
 */

static int
cnt_prepresp(struct sess *sp)
{

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

	if (sp->wrk->do_stream)
		AssertObjCorePassOrBusy(sp->obj->objcore);

	sp->wrk->res_mode = 0;

	if ((sp->wrk->h_content_length != NULL || !sp->wrk->do_stream) &&
	    !sp->wrk->do_gzip && !sp->wrk->do_gunzip)
		sp->wrk->res_mode |= RES_LEN;

	if (!sp->disable_esi && sp->obj->esidata != NULL) {
		/* In ESI mode, we don't know the aggregate length */
		sp->wrk->res_mode &= ~RES_LEN;
		sp->wrk->res_mode |= RES_ESI;
	}

	if (sp->esi_level > 0) {
		sp->wrk->res_mode &= ~RES_LEN;
		sp->wrk->res_mode |= RES_ESI_CHILD;
	}

	if (params->http_gzip_support && sp->obj->gziped &&
	    !RFC2616_Req_Gzip(sp)) {
		/*
		 * We don't know what it uncompresses to
		 * XXX: we could cache that
		 */
		sp->wrk->res_mode &= ~RES_LEN;
		sp->wrk->res_mode |= RES_GUNZIP;
	}

	if (!(sp->wrk->res_mode & (RES_LEN|RES_CHUNKED|RES_EOF))) {
		if (sp->obj->len == 0 && !sp->wrk->do_stream)
			/*
			 * If the object is empty, neither ESI nor GUNZIP
			 * can make it any different size
			 */
			sp->wrk->res_mode |= RES_LEN;
		else if (!sp->wantbody) {
			/* Nothing */
		} else if (sp->http->protover >= 11) {
			sp->wrk->res_mode |= RES_CHUNKED;
		} else {
			sp->wrk->res_mode |= RES_EOF;
			sp->doclose = "EOF mode";
		}
	}

	sp->t_resp = TIM_real();
	if (sp->obj->objcore != NULL) {
		if ((sp->t_resp - sp->obj->last_lru) > params->lru_timeout &&
		    EXP_Touch(sp->obj->objcore))
			sp->obj->last_lru = sp->t_resp;
		sp->obj->last_use = sp->t_resp;	/* XXX: locking ? */
	}
	http_Setup(sp->wrk->resp, sp->wrk->ws);
	RES_BuildHttp(sp);
	VCL_deliver_method(sp);
	switch (sp->handling) {
	case VCL_RET_DELIVER:
		break;
	case VCL_RET_RESTART:
		if (sp->restarts >= params->max_restarts)
			break;
		if (sp->wrk->do_stream) {
			VDI_CloseFd(sp);
			HSH_Drop(sp);
		} else {
			(void)HSH_Deref(sp->wrk, NULL, &sp->obj);
		}
		AZ(sp->obj);
		sp->restarts++;
		sp->director = NULL;
		sp->wrk->h_content_length = NULL;
		http_Setup(sp->wrk->bereq, NULL);
		http_Setup(sp->wrk->beresp, NULL);
		http_Setup(sp->wrk->resp, NULL);
		sp->step = STP_RECV;
		return (0);
	default:
		WRONG("Illegal action in vcl_deliver{}");
	}
	if (sp->wrk->do_stream) {
		AssertObjCorePassOrBusy(sp->obj->objcore);
		sp->step = STP_STREAMBODY;
	} else {
		sp->step = STP_DELIVER;
	}
	return (0);
}

/*--------------------------------------------------------------------
 * Deliver an already stored object
 *
DOT subgraph xcluster_deliver {
DOT	deliver [
DOT		shape=ellipse
DOT		label="Send body"
DOT	]
DOT }
DOT deliver -> DONE [style=bold,color=green]
DOT deliver -> DONE [style=bold,color=red]
DOT deliver -> DONE [style=bold,color=blue]
 *
 */

static int
cnt_deliver(struct sess *sp)
{

	sp->director = NULL;
	sp->restarts = 0;

	RES_WriteObj(sp);

	/* No point in saving the body if it is hit-for-pass */
	if (sp->obj->objcore != NULL) {
		CHECK_OBJ_NOTNULL(sp->obj->objcore, OBJCORE_MAGIC);
		if (sp->obj->objcore->flags & OC_F_PASS)
			STV_Freestore(sp->obj);
	}

	assert(WRW_IsReleased(sp->wrk));
	assert(sp->wrk->wrw.ciov == sp->wrk->wrw.siov);
	(void)HSH_Deref(sp->wrk, NULL, &sp->obj);
	http_Setup(sp->wrk->resp, NULL);
	sp->step = STP_DONE;
	return (0);
}

/*--------------------------------------------------------------------
 * This is the final state, figure out if we should close or recycle
 * the client connection
 *
DOT	DONE [
DOT		shape=hexagon
DOT		label="Request completed"
DOT	]
 */

static int
cnt_done(struct sess *sp)
{
	double dh, dp, da;
	int i;

	CHECK_OBJ_NOTNULL(sp, SESS_MAGIC);
	CHECK_OBJ_ORNULL(sp->vcl, VCL_CONF_MAGIC);

	AZ(sp->obj);
	AZ(sp->vbc);
	sp->director = NULL;
	sp->restarts = 0;

	sp->wrk->do_esi = 0;
	sp->wrk->do_gunzip = 0;
	sp->wrk->do_gzip = 0;
	sp->wrk->do_stream = 0;
	sp->wrk->is_gunzip = 0;
	sp->wrk->is_gzip = 0;

	if (sp->vcl != NULL && sp->esi_level == 0) {
		if (sp->wrk->vcl != NULL)
			VCL_Rel(&sp->wrk->vcl);
		sp->wrk->vcl = sp->vcl;
		sp->vcl = NULL;
	}

	SES_Charge(sp);

	sp->t_end = TIM_real();
	sp->wrk->lastused = sp->t_end;
	if (sp->xid == 0) {
		sp->t_req = sp->t_end;
		sp->t_resp = sp->t_end;
	} else if (sp->esi_level == 0) {
		dp = sp->t_resp - sp->t_req;
		da = sp->t_end - sp->t_resp;
		dh = sp->t_req - sp->t_open;
		/* XXX: Add StatReq == StatSess */
		/* XXX: Workaround for pipe */
		if (sp->fd >= 0) {
			WSP(sp, SLT_Length, "%ju", (uintmax_t)sp->acct_req.bodybytes);
		}
		WSL(sp->wrk, SLT_ReqEnd, sp->id, "%u %.9f %.9f %.9f %.9f %.9f",
		    sp->xid, sp->t_req, sp->t_end, dh, dp, da);
	}
	sp->xid = 0;
	sp->t_open = sp->t_end;
	sp->t_resp = NAN;
	WSL_Flush(sp->wrk, 0);

	/* If we did an ESI include, don't mess up our state */
	if (sp->esi_level > 0)
		return (1);

	memset(&sp->acct_req, 0, sizeof sp->acct_req);

	sp->t_req = NAN;
	sp->hash_always_miss = 0;
	sp->hash_ignore_busy = 0;

	if (sp->fd >= 0 && sp->doclose != NULL) {
		/*
		 * This is an orderly close of the connection; ditch nolinger
		 * before we close, to get queued data transmitted.
		 */
		// XXX: not yet (void)VTCP_linger(sp->fd, 0);
		vca_close_session(sp, sp->doclose);
	}

	if (sp->fd < 0) {
		sp->wrk->stats.sess_closed++;
		sp->wrk = NULL;
		SES_Delete(sp);
		return (1);
	}

	if (sp->wrk->stats.client_req >= params->wthread_stats_rate)
		WRK_SumStat(sp->wrk);
	/* Reset the workspace to the session-watermark */
	WS_Reset(sp->ws, sp->ws_ses);
	WS_Reset(sp->wrk->ws, NULL);

	i = HTC_Reinit(sp->htc);
	if (i == 1) {
		sp->wrk->stats.sess_pipeline++;
		sp->step = STP_START;
		return (0);
	}
	if (Tlen(sp->htc->rxbuf)) {
		sp->wrk->stats.sess_readahead++;
		sp->step = STP_WAIT;
		return (0);
	}
	if (params->session_linger > 0) {
		sp->wrk->stats.sess_linger++;
		sp->step = STP_WAIT;
		return (0);
	}
	sp->wrk->stats.sess_herd++;
	sp->wrk = NULL;
	vca_return_session(sp);
	return (1);
}

/*--------------------------------------------------------------------
 * Emit an error
 *
DOT subgraph xcluster_error {
DOT	vcl_error [
DOT		shape=record
DOT		label="vcl_error()|resp."
DOT	]
DOT	ERROR -> vcl_error
DOT	vcl_error-> prepresp [label=deliver]
DOT }
DOT vcl_error-> rsterr [label="restart",color=purple]
DOT rsterr [label="RESTART",shape=plaintext]
 */

static int
cnt_error(struct sess *sp)
{
	struct worker *w;
	struct http *h;
	char date[40];

	CHECK_OBJ_NOTNULL(sp, SESS_MAGIC);

	sp->wrk->do_esi = 0;
	sp->wrk->is_gzip = 0;
	sp->wrk->is_gunzip = 0;
	sp->wrk->do_gzip = 0;
	sp->wrk->do_gunzip = 0;
	sp->wrk->do_stream = 0;

	w = sp->wrk;
	if (sp->obj == NULL) {
		HSH_Prealloc(sp);
		EXP_Clr(&w->exp);
		sp->obj = STV_NewObject(sp, NULL, params->http_resp_size,
		     &w->exp, (uint16_t)params->http_max_hdr);
		if (sp->obj == NULL)
			sp->obj = STV_NewObject(sp, TRANSIENT_STORAGE,
			    params->http_resp_size, &w->exp,
			    (uint16_t)params->http_max_hdr);
		if (sp->obj == NULL) {
			sp->doclose = "Out of objects";
			sp->director = NULL;
			sp->wrk->h_content_length = NULL;
			http_Setup(sp->wrk->beresp, NULL);
			http_Setup(sp->wrk->bereq, NULL);
			sp->step = STP_DONE;
			return(0);
		}
		AN(sp->obj);
		sp->obj->xid = sp->xid;
		sp->obj->exp.entered = sp->t_req;
	} else {
		/* XXX: Null the headers ? */
	}
	CHECK_OBJ_NOTNULL(sp->obj, OBJECT_MAGIC);
	h = sp->obj->http;

	if (sp->err_code < 100 || sp->err_code > 999)
		sp->err_code = 501;

	http_PutProtocol(w, sp->fd, h, "HTTP/1.1");
	http_PutStatus(h, sp->err_code);
	TIM_format(TIM_real(), date);
	http_PrintfHeader(w, sp->fd, h, "Date: %s", date);
	http_SetHeader(w, sp->fd, h, "Server: Varnish");

	if (sp->err_reason != NULL)
		http_PutResponse(w, sp->fd, h, sp->err_reason);
	else
		http_PutResponse(w, sp->fd, h,
		    http_StatusMessage(sp->err_code));
	VCL_error_method(sp);

	if (sp->handling == VCL_RET_RESTART &&
	    sp->restarts <  params->max_restarts) {
		HSH_Drop(sp);
		sp->director = NULL;
		sp->restarts++;
		sp->step = STP_RECV;
		return (0);
	} else if (sp->handling == VCL_RET_RESTART)
		sp->handling = VCL_RET_DELIVER;


	/* We always close when we take this path */
	sp->doclose = "error";
	sp->wantbody = 1;

	assert(sp->handling == VCL_RET_DELIVER);
	sp->err_code = 0;
	sp->err_reason = NULL;
	http_Setup(sp->wrk->bereq, NULL);
	sp->step = STP_PREPRESP;
	return (0);
}

/*--------------------------------------------------------------------
 * Fetch response headers from the backend
 *
DOT subgraph xcluster_fetch {
DOT	fetch [
DOT		shape=ellipse
DOT		label="fetch hdr\nfrom backend\n(find obj.ttl)"
DOT	]
DOT	vcl_fetch [
DOT		shape=record
DOT		label="vcl_fetch()|req.\nbereq.\nberesp."
DOT	]
DOT	fetch -> vcl_fetch [style=bold,color=blue]
DOT	fetch -> vcl_fetch [style=bold,color=red]
DOT	fetch_pass [
DOT		shape=ellipse
DOT		label="obj.f.pass=true"
DOT	]
DOT	vcl_fetch -> fetch_pass [label="hit_for_pass",style=bold,color=red]
DOT }
DOT fetch_pass -> fetchbody [style=bold,color=red]
DOT vcl_fetch -> fetchbody [label="deliver",style=bold,color=blue]
DOT vcl_fetch -> rstfetch [label="restart",color=purple]
DOT rstfetch [label="RESTART",shape=plaintext]
DOT fetch -> errfetch
DOT vcl_fetch -> errfetch [label="error"]
DOT errfetch [label="ERROR",shape=plaintext]
 */

static int
cnt_fetch(struct sess *sp)
{
	int i;

	CHECK_OBJ_NOTNULL(sp, SESS_MAGIC);
	CHECK_OBJ_NOTNULL(sp->vcl, VCL_CONF_MAGIC);

	AN(sp->director);
	AZ(sp->vbc);
	AZ(sp->wrk->h_content_length);
	AZ(sp->wrk->do_close);
	AZ(sp->wrk->storage_hint);

	http_Setup(sp->wrk->beresp, sp->wrk->ws);

	i = FetchHdr(sp);
	/*
	 * If we recycle a backend connection, there is a finite chance
	 * that the backend closed it before we get a request to it.
	 * Do a single retry in that case.
	 */
	if (i == 1) {
		VSC_C_main->backend_retry++;
		i = FetchHdr(sp);
	}

	if (i) {
		sp->handling = VCL_RET_ERROR;
		sp->err_code = 503;
	} else {
		/*
		 * These two headers can be spread over multiple actual headers
		 * and we rely on their content outside of VCL, so collect them
		 * into one line here.
		 */
		http_CollectHdr(sp->wrk->beresp, H_Cache_Control);
		http_CollectHdr(sp->wrk->beresp, H_Vary);

		/*
		 * Figure out how the fetch is supposed to happen, before the
		 * headers are adultered by VCL
		 * NB: Also sets other sp->wrk variables
		 */
		sp->wrk->body_status = RFC2616_Body(sp);

		sp->err_code = http_GetStatus(sp->wrk->beresp);

		/*
		 * What does RFC2616 think about TTL ?
		 */
		EXP_Clr(&sp->wrk->exp);
		sp->wrk->exp.entered = TIM_real();
		RFC2616_Ttl(sp);

		/* pass from vclrecv{} has negative TTL */
		if (sp->objcore == NULL)
			sp->wrk->exp.ttl = -1.;

		AZ(sp->wrk->do_esi);

		VCL_fetch_method(sp);

		switch (sp->handling) {
		case VCL_RET_HIT_FOR_PASS:
			if (sp->objcore != NULL)
				sp->objcore->flags |= OC_F_PASS;
			sp->step = STP_FETCHBODY;
			return (0);
		case VCL_RET_DELIVER:
			AssertObjCorePassOrBusy(sp->objcore);
			sp->step = STP_FETCHBODY;
			return (0);
		default:
			break;
		}

		/* We are not going to fetch the body, Close the connection */
		VDI_CloseFd(sp);
	}

	/* Clean up partial fetch */
	AZ(sp->vbc);

	if (sp->objcore != NULL) {
		CHECK_OBJ_NOTNULL(sp->objcore, OBJCORE_MAGIC);
		AZ(HSH_Deref(sp->wrk, sp->objcore, NULL));
		sp->objcore = NULL;
	}
	http_Setup(sp->wrk->bereq, NULL);
	http_Setup(sp->wrk->beresp, NULL);
	sp->wrk->h_content_length = NULL;
	sp->director = NULL;
	sp->wrk->storage_hint = NULL;

	switch (sp->handling) {
	case VCL_RET_RESTART:
		sp->restarts++;
		sp->step = STP_RECV;
		return (0);
	case VCL_RET_ERROR:
		sp->step = STP_ERROR;
		return (0);
	default:
		WRONG("Illegal action in vcl_fetch{}");
	}
}

/*--------------------------------------------------------------------
 * Fetch response body from the backend
 *
DOT subgraph xcluster_body {
DOT	fetchbody [
DOT		shape=diamond
DOT		label="stream ?"
DOT	]
DOT	fetchbody2 [
DOT		shape=ellipse
DOT		label="fetch body\nfrom backend\n"
DOT	]
DOT }
DOT fetchbody -> fetchbody2 [label=no,style=bold,color=red]
DOT fetchbody -> fetchbody2 [style=bold,color=blue]
DOT fetchbody -> prepresp [label=yes,style=bold,color=cyan]
DOT fetchbody2 -> prepresp [style=bold,color=red]
DOT fetchbody2 -> prepresp [style=bold,color=blue]
 */


static int
cnt_fetchbody(struct sess *sp)
{
	int i;
	struct http *hp, *hp2;
	char *b;
	uint16_t nhttp;
	unsigned l;
	struct vsb *vary = NULL;
	int varyl = 0, pass;

	assert(sp->handling == VCL_RET_HIT_FOR_PASS ||
	    sp->handling == VCL_RET_DELIVER);

	if (sp->objcore == NULL) {
		/* This is a pass from vcl_recv */
		pass = 1;
		/* VCL may have fiddled this, but that doesn't help */
		sp->wrk->exp.ttl = -1.;
	} else if (sp->handling == VCL_RET_HIT_FOR_PASS) {
		/* pass from vcl_fetch{} -> hit-for-pass */
		/* XXX: the bereq was not filtered pass... */
		pass = 1;
	} else {
		/* regular object */
		pass = 0;
	}

	/*
	 * The VCL variables beresp.do_g[un]zip tells us how we want the
	 * object processed before it is stored.
	 *
	 * The backend Content-Encoding header tells us what we are going
	 * to receive, which we classify in the following three classes:
	 *
	 *	"Content-Encoding: gzip"	--> object is gzip'ed.
	 *	no Content-Encoding		--> object is not gzip'ed.
	 *	anything else			--> do nothing wrt gzip
	 *
	 */

	AZ(sp->wrk->vfp);

	/* We do nothing unless the param is set */
	if (!params->http_gzip_support)
		sp->wrk->do_gzip = sp->wrk->do_gunzip = 0;

	sp->wrk->is_gzip =
	    http_HdrIs(sp->wrk->beresp, H_Content_Encoding, "gzip");

	sp->wrk->is_gunzip =
	    !http_GetHdr(sp->wrk->beresp, H_Content_Encoding, NULL);

	/* It can't be both */
	assert(sp->wrk->is_gzip == 0 || sp->wrk->is_gunzip == 0);

	/* We won't gunzip unless it is gzip'ed */
	if (sp->wrk->do_gunzip && !sp->wrk->is_gzip)
		sp->wrk->do_gunzip = 0;

	/* If we do gunzip, remove the C-E header */
	if (sp->wrk->do_gunzip)
		http_Unset(sp->wrk->beresp, H_Content_Encoding);

	/* We wont gzip unless it is ungziped */
	if (sp->wrk->do_gzip && !sp->wrk->is_gunzip)
		sp->wrk->do_gzip = 0;

	/* If we do gzip, add the C-E header */
	if (sp->wrk->do_gzip)
		http_SetHeader(sp->wrk, sp->fd, sp->wrk->beresp,
		    "Content-Encoding: gzip");

	/* But we can't do both at the same time */
	assert(sp->wrk->do_gzip == 0 || sp->wrk->do_gunzip == 0);

	/* ESI takes precedence and handles gzip/gunzip itself */
	if (sp->wrk->do_esi)
		sp->wrk->vfp = &vfp_esi;
	else if (sp->wrk->do_gunzip)
		sp->wrk->vfp = &vfp_gunzip;
	else if (sp->wrk->do_gzip)
		sp->wrk->vfp = &vfp_gzip;
	else if (sp->wrk->is_gzip)
		sp->wrk->vfp = &vfp_testgzip;

	if (sp->wrk->do_esi || sp->esi_level > 0)
		sp->wrk->do_stream = 0;
	if (!sp->wantbody)
		sp->wrk->do_stream = 0;

	/* No reason to try streaming a non-existing body */
	if (sp->wrk->body_status == BS_NONE)
		sp->wrk->do_stream = 0;

	l = http_EstimateWS(sp->wrk->beresp,
	    pass ? HTTPH_R_PASS : HTTPH_A_INS, &nhttp);

	/* Create Vary instructions */
	if (sp->objcore != NULL) {
		CHECK_OBJ_NOTNULL(sp->objcore, OBJCORE_MAGIC);
		varyl = VRY_Create(sp, sp->wrk->beresp, &vary);
		if (varyl > 0) {
			AN(vary);
			assert(varyl == VSB_len(vary));
			l += varyl;
		} else if (varyl < 0) {
			/* Vary parse error */
			AZ(vary);
			sp->err_code = 503;
			sp->step = STP_ERROR;
			VDI_CloseFd(sp);
			return (0);
		} else
			/* No vary */
			AZ(vary);
	}

	/*
	 * Space for producing a Content-Length: header including padding
	 * A billion gigabytes is enough for anybody.
	 */
	l += strlen("Content-Length: XxxXxxXxxXxxXxxXxx") + sizeof(void *);

	if (sp->wrk->exp.ttl < params->shortlived || sp->objcore == NULL)
		sp->wrk->storage_hint = TRANSIENT_STORAGE;

	sp->obj = STV_NewObject(sp, sp->wrk->storage_hint, l,
	    &sp->wrk->exp, nhttp);
	if (sp->obj == NULL) {
		/*
		 * Try to salvage the transaction by allocating a
		 * shortlived object on Transient storage.
		 */
		if (sp->wrk->exp.ttl > params->shortlived)
			sp->wrk->exp.ttl = params->shortlived;
		sp->wrk->exp.grace = 0.0;
		sp->wrk->exp.keep = 0.0;
		sp->obj = STV_NewObject(sp, TRANSIENT_STORAGE, l,
		    &sp->wrk->exp, nhttp);
	}
	if (sp->obj == NULL) {
		sp->err_code = 503;
		sp->step = STP_ERROR;
		VDI_CloseFd(sp);
		return (0);
	}
	CHECK_OBJ_NOTNULL(sp->obj, OBJECT_MAGIC);<commit_msg>Hint storage to Transient for hit-for-pass too<commit_after>/*-
 * Copyright (c) 2006 Verdens Gang AS
 * Copyright (c) 2006-2011 Varnish Software AS
 * All rights reserved.
 *
 * Author: <NAME> <<EMAIL>>
 *
 * 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.
 *
 * This file contains the central state machine for pushing requests.
 *
 * We cannot just use direct calls because it is possible to kick a
 * request back to the lookup stage (usually after a rewrite).  The
 * state engine also allows us to break the processing up into some
 * logical chunks which improves readability a little bit.
 *
 * Since the states are rather nasty in detail, I have decided to embedd
 * a dot(1) graph in the source code comments.  So to see the big picture,
 * extract the DOT lines and run though dot(1), for instance with the
 * command:
 *	sed -n '/^DOT/s///p' cache_center.c | dot -Tps > /tmp/_.ps
 */

/*
DOT digraph vcl_center {
xDOT	page="8.2,11.5"
DOT	size="7.2,10.5"
DOT	margin="0.5"
DOT	center="1"
DOT acceptor [
DOT	shape=hexagon
DOT	label="Request received"
DOT ]
DOT ERROR [shape=plaintext]
DOT RESTART [shape=plaintext]
DOT acceptor -> start [style=bold,color=green]
 */

#include "config.h"

#include <stdio.h>
#include <errno.h>
#include <math.h>
#include <poll.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#ifndef HAVE_SRANDOMDEV
#include "compat/srandomdev.h"
#endif

#include "vcl.h"
#include "cli_priv.h"
#include "cache.h"
#include "hash_slinger.h"
#include "stevedore.h"
#include "vsha256.h"

static unsigned xids;

/*--------------------------------------------------------------------
 * WAIT
 * Wait (briefly) until we have a full request in our htc.
 */

static int
cnt_wait(struct sess *sp)
{
	int i;
	struct pollfd pfd[1];

	CHECK_OBJ_NOTNULL(sp, SESS_MAGIC);
	AZ(sp->vcl);
	AZ(sp->obj);
	assert(sp->xid == 0);

	i = HTC_Complete(sp->htc);
	if (i == 0 && params->session_linger > 0) {
		pfd[0].fd = sp->fd;
		pfd[0].events = POLLIN;
		pfd[0].revents = 0;
		i = poll(pfd, 1, params->session_linger);
		if (i)
			i = HTC_Rx(sp->htc);
	}
	if (i == 0) {
		WSL(sp->wrk, SLT_Debug, sp->fd, "herding");
		sp->wrk->stats.sess_herd++;
		SES_Charge(sp);
		sp->wrk = NULL;
		vca_return_session(sp);
		return (1);
	}
	if (i == 1) {
		sp->step = STP_START;
		return (0);
	}
	if (i == -2)
		vca_close_session(sp, "overflow");
	else if (i == -1 && Tlen(sp->htc->rxbuf) == 0 &&
	    (errno == 0 || errno == ECONNRESET))
		vca_close_session(sp, "EOF");
	else
		vca_close_session(sp, "error");
	sp->step = STP_DONE;
	return (0);
}

/*--------------------------------------------------------------------
 * We have a refcounted object on the session, now deliver it.
 *
DOT subgraph xcluster_prepresp {
DOT	prepresp [
DOT		shape=ellipse
DOT		label="Filter obj.->resp."
DOT	]
DOT	vcl_deliver [
DOT		shape=record
DOT		label="vcl_deliver()|resp."
DOT	]
DOT	prepresp -> vcl_deliver [style=bold,color=green]
DOT	prepresp -> vcl_deliver [style=bold,color=cyan]
DOT	prepresp -> vcl_deliver [style=bold,color=red]
DOT	prepresp -> vcl_deliver [style=bold,color=blue,]
DOT	vcl_deliver -> deliver [style=bold,color=green,label=deliver]
DOT	vcl_deliver -> deliver [style=bold,color=red]
DOT	vcl_deliver -> deliver [style=bold,color=blue]
DOT     vcl_deliver -> errdeliver [label="error"]
DOT     errdeliver [label="ERROR",shape=plaintext]
DOT     vcl_deliver -> rstdeliver [label="restart",color=purple]
DOT     rstdeliver [label="RESTART",shape=plaintext]
DOT     vcl_deliver -> streambody [style=bold,color=cyan,label="deliver"]
DOT }
 *
 */

static int
cnt_prepresp(struct sess *sp)
{

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

	if (sp->wrk->do_stream)
		AssertObjCorePassOrBusy(sp->obj->objcore);

	sp->wrk->res_mode = 0;

	if ((sp->wrk->h_content_length != NULL || !sp->wrk->do_stream) &&
	    !sp->wrk->do_gzip && !sp->wrk->do_gunzip)
		sp->wrk->res_mode |= RES_LEN;

	if (!sp->disable_esi && sp->obj->esidata != NULL) {
		/* In ESI mode, we don't know the aggregate length */
		sp->wrk->res_mode &= ~RES_LEN;
		sp->wrk->res_mode |= RES_ESI;
	}

	if (sp->esi_level > 0) {
		sp->wrk->res_mode &= ~RES_LEN;
		sp->wrk->res_mode |= RES_ESI_CHILD;
	}

	if (params->http_gzip_support && sp->obj->gziped &&
	    !RFC2616_Req_Gzip(sp)) {
		/*
		 * We don't know what it uncompresses to
		 * XXX: we could cache that
		 */
		sp->wrk->res_mode &= ~RES_LEN;
		sp->wrk->res_mode |= RES_GUNZIP;
	}

	if (!(sp->wrk->res_mode & (RES_LEN|RES_CHUNKED|RES_EOF))) {
		if (sp->obj->len == 0 && !sp->wrk->do_stream)
			/*
			 * If the object is empty, neither ESI nor GUNZIP
			 * can make it any different size
			 */
			sp->wrk->res_mode |= RES_LEN;
		else if (!sp->wantbody) {
			/* Nothing */
		} else if (sp->http->protover >= 11) {
			sp->wrk->res_mode |= RES_CHUNKED;
		} else {
			sp->wrk->res_mode |= RES_EOF;
			sp->doclose = "EOF mode";
		}
	}

	sp->t_resp = TIM_real();
	if (sp->obj->objcore != NULL) {
		if ((sp->t_resp - sp->obj->last_lru) > params->lru_timeout &&
		    EXP_Touch(sp->obj->objcore))
			sp->obj->last_lru = sp->t_resp;
		sp->obj->last_use = sp->t_resp;	/* XXX: locking ? */
	}
	http_Setup(sp->wrk->resp, sp->wrk->ws);
	RES_BuildHttp(sp);
	VCL_deliver_method(sp);
	switch (sp->handling) {
	case VCL_RET_DELIVER:
		break;
	case VCL_RET_RESTART:
		if (sp->restarts >= params->max_restarts)
			break;
		if (sp->wrk->do_stream) {
			VDI_CloseFd(sp);
			HSH_Drop(sp);
		} else {
			(void)HSH_Deref(sp->wrk, NULL, &sp->obj);
		}
		AZ(sp->obj);
		sp->restarts++;
		sp->director = NULL;
		sp->wrk->h_content_length = NULL;
		http_Setup(sp->wrk->bereq, NULL);
		http_Setup(sp->wrk->beresp, NULL);
		http_Setup(sp->wrk->resp, NULL);
		sp->step = STP_RECV;
		return (0);
	default:
		WRONG("Illegal action in vcl_deliver{}");
	}
	if (sp->wrk->do_stream) {
		AssertObjCorePassOrBusy(sp->obj->objcore);
		sp->step = STP_STREAMBODY;
	} else {
		sp->step = STP_DELIVER;
	}
	return (0);
}

/*--------------------------------------------------------------------
 * Deliver an already stored object
 *
DOT subgraph xcluster_deliver {
DOT	deliver [
DOT		shape=ellipse
DOT		label="Send body"
DOT	]
DOT }
DOT deliver -> DONE [style=bold,color=green]
DOT deliver -> DONE [style=bold,color=red]
DOT deliver -> DONE [style=bold,color=blue]
 *
 */

static int
cnt_deliver(struct sess *sp)
{

	sp->director = NULL;
	sp->restarts = 0;

	RES_WriteObj(sp);

	/* No point in saving the body if it is hit-for-pass */
	if (sp->obj->objcore != NULL) {
		CHECK_OBJ_NOTNULL(sp->obj->objcore, OBJCORE_MAGIC);
		if (sp->obj->objcore->flags & OC_F_PASS)
			STV_Freestore(sp->obj);
	}

	assert(WRW_IsReleased(sp->wrk));
	assert(sp->wrk->wrw.ciov == sp->wrk->wrw.siov);
	(void)HSH_Deref(sp->wrk, NULL, &sp->obj);
	http_Setup(sp->wrk->resp, NULL);
	sp->step = STP_DONE;
	return (0);
}

/*--------------------------------------------------------------------
 * This is the final state, figure out if we should close or recycle
 * the client connection
 *
DOT	DONE [
DOT		shape=hexagon
DOT		label="Request completed"
DOT	]
 */

static int
cnt_done(struct sess *sp)
{
	double dh, dp, da;
	int i;

	CHECK_OBJ_NOTNULL(sp, SESS_MAGIC);
	CHECK_OBJ_ORNULL(sp->vcl, VCL_CONF_MAGIC);

	AZ(sp->obj);
	AZ(sp->vbc);
	sp->director = NULL;
	sp->restarts = 0;

	sp->wrk->do_esi = 0;
	sp->wrk->do_gunzip = 0;
	sp->wrk->do_gzip = 0;
	sp->wrk->do_stream = 0;
	sp->wrk->is_gunzip = 0;
	sp->wrk->is_gzip = 0;

	if (sp->vcl != NULL && sp->esi_level == 0) {
		if (sp->wrk->vcl != NULL)
			VCL_Rel(&sp->wrk->vcl);
		sp->wrk->vcl = sp->vcl;
		sp->vcl = NULL;
	}

	SES_Charge(sp);

	sp->t_end = TIM_real();
	sp->wrk->lastused = sp->t_end;
	if (sp->xid == 0) {
		sp->t_req = sp->t_end;
		sp->t_resp = sp->t_end;
	} else if (sp->esi_level == 0) {
		dp = sp->t_resp - sp->t_req;
		da = sp->t_end - sp->t_resp;
		dh = sp->t_req - sp->t_open;
		/* XXX: Add StatReq == StatSess */
		/* XXX: Workaround for pipe */
		if (sp->fd >= 0) {
			WSP(sp, SLT_Length, "%ju", (uintmax_t)sp->acct_req.bodybytes);
		}
		WSL(sp->wrk, SLT_ReqEnd, sp->id, "%u %.9f %.9f %.9f %.9f %.9f",
		    sp->xid, sp->t_req, sp->t_end, dh, dp, da);
	}
	sp->xid = 0;
	sp->t_open = sp->t_end;
	sp->t_resp = NAN;
	WSL_Flush(sp->wrk, 0);

	/* If we did an ESI include, don't mess up our state */
	if (sp->esi_level > 0)
		return (1);

	memset(&sp->acct_req, 0, sizeof sp->acct_req);

	sp->t_req = NAN;
	sp->hash_always_miss = 0;
	sp->hash_ignore_busy = 0;

	if (sp->fd >= 0 && sp->doclose != NULL) {
		/*
		 * This is an orderly close of the connection; ditch nolinger
		 * before we close, to get queued data transmitted.
		 */
		// XXX: not yet (void)VTCP_linger(sp->fd, 0);
		vca_close_session(sp, sp->doclose);
	}

	if (sp->fd < 0) {
		sp->wrk->stats.sess_closed++;
		sp->wrk = NULL;
		SES_Delete(sp);
		return (1);
	}

	if (sp->wrk->stats.client_req >= params->wthread_stats_rate)
		WRK_SumStat(sp->wrk);
	/* Reset the workspace to the session-watermark */
	WS_Reset(sp->ws, sp->ws_ses);
	WS_Reset(sp->wrk->ws, NULL);

	i = HTC_Reinit(sp->htc);
	if (i == 1) {
		sp->wrk->stats.sess_pipeline++;
		sp->step = STP_START;
		return (0);
	}
	if (Tlen(sp->htc->rxbuf)) {
		sp->wrk->stats.sess_readahead++;
		sp->step = STP_WAIT;
		return (0);
	}
	if (params->session_linger > 0) {
		sp->wrk->stats.sess_linger++;
		sp->step = STP_WAIT;
		return (0);
	}
	sp->wrk->stats.sess_herd++;
	sp->wrk = NULL;
	vca_return_session(sp);
	return (1);
}

/*--------------------------------------------------------------------
 * Emit an error
 *
DOT subgraph xcluster_error {
DOT	vcl_error [
DOT		shape=record
DOT		label="vcl_error()|resp."
DOT	]
DOT	ERROR -> vcl_error
DOT	vcl_error-> prepresp [label=deliver]
DOT }
DOT vcl_error-> rsterr [label="restart",color=purple]
DOT rsterr [label="RESTART",shape=plaintext]
 */

static int
cnt_error(struct sess *sp)
{
	struct worker *w;
	struct http *h;
	char date[40];

	CHECK_OBJ_NOTNULL(sp, SESS_MAGIC);

	sp->wrk->do_esi = 0;
	sp->wrk->is_gzip = 0;
	sp->wrk->is_gunzip = 0;
	sp->wrk->do_gzip = 0;
	sp->wrk->do_gunzip = 0;
	sp->wrk->do_stream = 0;

	w = sp->wrk;
	if (sp->obj == NULL) {
		HSH_Prealloc(sp);
		EXP_Clr(&w->exp);
		sp->obj = STV_NewObject(sp, NULL, params->http_resp_size,
		     &w->exp, (uint16_t)params->http_max_hdr);
		if (sp->obj == NULL)
			sp->obj = STV_NewObject(sp, TRANSIENT_STORAGE,
			    params->http_resp_size, &w->exp,
			    (uint16_t)params->http_max_hdr);
		if (sp->obj == NULL) {
			sp->doclose = "Out of objects";
			sp->director = NULL;
			sp->wrk->h_content_length = NULL;
			http_Setup(sp->wrk->beresp, NULL);
			http_Setup(sp->wrk->bereq, NULL);
			sp->step = STP_DONE;
			return(0);
		}
		AN(sp->obj);
		sp->obj->xid = sp->xid;
		sp->obj->exp.entered = sp->t_req;
	} else {
		/* XXX: Null the headers ? */
	}
	CHECK_OBJ_NOTNULL(sp->obj, OBJECT_MAGIC);
	h = sp->obj->http;

	if (sp->err_code < 100 || sp->err_code > 999)
		sp->err_code = 501;

	http_PutProtocol(w, sp->fd, h, "HTTP/1.1");
	http_PutStatus(h, sp->err_code);
	TIM_format(TIM_real(), date);
	http_PrintfHeader(w, sp->fd, h, "Date: %s", date);
	http_SetHeader(w, sp->fd, h, "Server: Varnish");

	if (sp->err_reason != NULL)
		http_PutResponse(w, sp->fd, h, sp->err_reason);
	else
		http_PutResponse(w, sp->fd, h,
		    http_StatusMessage(sp->err_code));
	VCL_error_method(sp);

	if (sp->handling == VCL_RET_RESTART &&
	    sp->restarts <  params->max_restarts) {
		HSH_Drop(sp);
		sp->director = NULL;
		sp->restarts++;
		sp->step = STP_RECV;
		return (0);
	} else if (sp->handling == VCL_RET_RESTART)
		sp->handling = VCL_RET_DELIVER;


	/* We always close when we take this path */
	sp->doclose = "error";
	sp->wantbody = 1;

	assert(sp->handling == VCL_RET_DELIVER);
	sp->err_code = 0;
	sp->err_reason = NULL;
	http_Setup(sp->wrk->bereq, NULL);
	sp->step = STP_PREPRESP;
	return (0);
}

/*--------------------------------------------------------------------
 * Fetch response headers from the backend
 *
DOT subgraph xcluster_fetch {
DOT	fetch [
DOT		shape=ellipse
DOT		label="fetch hdr\nfrom backend\n(find obj.ttl)"
DOT	]
DOT	vcl_fetch [
DOT		shape=record
DOT		label="vcl_fetch()|req.\nbereq.\nberesp."
DOT	]
DOT	fetch -> vcl_fetch [style=bold,color=blue]
DOT	fetch -> vcl_fetch [style=bold,color=red]
DOT	fetch_pass [
DOT		shape=ellipse
DOT		label="obj.f.pass=true"
DOT	]
DOT	vcl_fetch -> fetch_pass [label="hit_for_pass",style=bold,color=red]
DOT }
DOT fetch_pass -> fetchbody [style=bold,color=red]
DOT vcl_fetch -> fetchbody [label="deliver",style=bold,color=blue]
DOT vcl_fetch -> rstfetch [label="restart",color=purple]
DOT rstfetch [label="RESTART",shape=plaintext]
DOT fetch -> errfetch
DOT vcl_fetch -> errfetch [label="error"]
DOT errfetch [label="ERROR",shape=plaintext]
 */

static int
cnt_fetch(struct sess *sp)
{
	int i;

	CHECK_OBJ_NOTNULL(sp, SESS_MAGIC);
	CHECK_OBJ_NOTNULL(sp->vcl, VCL_CONF_MAGIC);

	AN(sp->director);
	AZ(sp->vbc);
	AZ(sp->wrk->h_content_length);
	AZ(sp->wrk->do_close);
	AZ(sp->wrk->storage_hint);

	http_Setup(sp->wrk->beresp, sp->wrk->ws);

	i = FetchHdr(sp);
	/*
	 * If we recycle a backend connection, there is a finite chance
	 * that the backend closed it before we get a request to it.
	 * Do a single retry in that case.
	 */
	if (i == 1) {
		VSC_C_main->backend_retry++;
		i = FetchHdr(sp);
	}

	if (i) {
		sp->handling = VCL_RET_ERROR;
		sp->err_code = 503;
	} else {
		/*
		 * These two headers can be spread over multiple actual headers
		 * and we rely on their content outside of VCL, so collect them
		 * into one line here.
		 */
		http_CollectHdr(sp->wrk->beresp, H_Cache_Control);
		http_CollectHdr(sp->wrk->beresp, H_Vary);

		/*
		 * Figure out how the fetch is supposed to happen, before the
		 * headers are adultered by VCL
		 * NB: Also sets other sp->wrk variables
		 */
		sp->wrk->body_status = RFC2616_Body(sp);

		sp->err_code = http_GetStatus(sp->wrk->beresp);

		/*
		 * What does RFC2616 think about TTL ?
		 */
		EXP_Clr(&sp->wrk->exp);
		sp->wrk->exp.entered = TIM_real();
		RFC2616_Ttl(sp);

		/* pass from vclrecv{} has negative TTL */
		if (sp->objcore == NULL)
			sp->wrk->exp.ttl = -1.;

		AZ(sp->wrk->do_esi);

		VCL_fetch_method(sp);

		switch (sp->handling) {
		case VCL_RET_HIT_FOR_PASS:
			if (sp->objcore != NULL)
				sp->objcore->flags |= OC_F_PASS;
			sp->step = STP_FETCHBODY;
			return (0);
		case VCL_RET_DELIVER:
			AssertObjCorePassOrBusy(sp->objcore);
			sp->step = STP_FETCHBODY;
			return (0);
		default:
			break;
		}

		/* We are not going to fetch the body, Close the connection */
		VDI_CloseFd(sp);
	}

	/* Clean up partial fetch */
	AZ(sp->vbc);

	if (sp->objcore != NULL) {
		CHECK_OBJ_NOTNULL(sp->objcore, OBJCORE_MAGIC);
		AZ(HSH_Deref(sp->wrk, sp->objcore, NULL));
		sp->objcore = NULL;
	}
	http_Setup(sp->wrk->bereq, NULL);
	http_Setup(sp->wrk->beresp, NULL);
	sp->wrk->h_content_length = NULL;
	sp->director = NULL;
	sp->wrk->storage_hint = NULL;

	switch (sp->handling) {
	case VCL_RET_RESTART:
		sp->restarts++;
		sp->step = STP_RECV;
		return (0);
	case VCL_RET_ERROR:
		sp->step = STP_ERROR;
		return (0);
	default:
		WRONG("Illegal action in vcl_fetch{}");
	}
}

/*--------------------------------------------------------------------
 * Fetch response body from the backend
 *
DOT subgraph xcluster_body {
DOT	fetchbody [
DOT		shape=diamond
DOT		label="stream ?"
DOT	]
DOT	fetchbody2 [
DOT		shape=ellipse
DOT		label="fetch body\nfrom backend\n"
DOT	]
DOT }
DOT fetchbody -> fetchbody2 [label=no,style=bold,color=red]
DOT fetchbody -> fetchbody2 [style=bold,color=blue]
DOT fetchbody -> prepresp [label=yes,style=bold,color=cyan]
DOT fetchbody2 -> prepresp [style=bold,color=red]
DOT fetchbody2 -> prepresp [style=bold,color=blue]
 */


static int
cnt_fetchbody(struct sess *sp)
{
	int i;
	struct http *hp, *hp2;
	char *b;
	uint16_t nhttp;
	unsigned l;
	struct vsb *vary = NULL;
	int varyl = 0, pass;

	assert(sp->handling == VCL_RET_HIT_FOR_PASS ||
	    sp->handling == VCL_RET_DELIVER);

	if (sp->objcore == NULL) {
		/* This is a pass from vcl_recv */
		pass = 1;
		/* VCL may have fiddled this, but that doesn't help */
		sp->wrk->exp.ttl = -1.;
	} else if (sp->handling == VCL_RET_HIT_FOR_PASS) {
		/* pass from vcl_fetch{} -> hit-for-pass */
		/* XXX: the bereq was not filtered pass... */
		pass = 1;
	} else {
		/* regular object */
		pass = 0;
	}

	/*
	 * The VCL variables beresp.do_g[un]zip tells us how we want the
	 * object processed before it is stored.
	 *
	 * The backend Content-Encoding header tells us what we are going
	 * to receive, which we classify in the following three classes:
	 *
	 *	"Content-Encoding: gzip"	--> object is gzip'ed.
	 *	no Content-Encoding		--> object is not gzip'ed.
	 *	anything else			--> do nothing wrt gzip
	 *
	 */

	AZ(sp->wrk->vfp);

	/* We do nothing unless the param is set */
	if (!params->http_gzip_support)
		sp->wrk->do_gzip = sp->wrk->do_gunzip = 0;

	sp->wrk->is_gzip =
	    http_HdrIs(sp->wrk->beresp, H_Content_Encoding, "gzip");

	sp->wrk->is_gunzip =
	    !http_GetHdr(sp->wrk->beresp, H_Content_Encoding, NULL);

	/* It can't be both */
	assert(sp->wrk->is_gzip == 0 || sp->wrk->is_gunzip == 0);

	/* We won't gunzip unless it is gzip'ed */
	if (sp->wrk->do_gunzip && !sp->wrk->is_gzip)
		sp->wrk->do_gunzip = 0;

	/* If we do gunzip, remove the C-E header */
	if (sp->wrk->do_gunzip)
		http_Unset(sp->wrk->beresp, H_Content_Encoding);

	/* We wont gzip unless it is ungziped */
	if (sp->wrk->do_gzip && !sp->wrk->is_gunzip)
		sp->wrk->do_gzip = 0;

	/* If we do gzip, add the C-E header */
	if (sp->wrk->do_gzip)
		http_SetHeader(sp->wrk, sp->fd, sp->wrk->beresp,
		    "Content-Encoding: gzip");

	/* But we can't do both at the same time */
	assert(sp->wrk->do_gzip == 0 || sp->wrk->do_gunzip == 0);

	/* ESI takes precedence and handles gzip/gunzip itself */
	if (sp->wrk->do_esi)
		sp->wrk->vfp = &vfp_esi;
	else if (sp->wrk->do_gunzip)
		sp->wrk->vfp = &vfp_gunzip;
	else if (sp->wrk->do_gzip)
		sp->wrk->vfp = &vfp_gzip;
	else if (sp->wrk->is_gzip)
		sp->wrk->vfp = &vfp_testgzip;

	if (sp->wrk->do_esi || sp->esi_level > 0)
		sp->wrk->do_stream = 0;
	if (!sp->wantbody)
		sp->wrk->do_stream = 0;

	/* No reason to try streaming a non-existing body */
	if (sp->wrk->body_status == BS_NONE)
		sp->wrk->do_stream = 0;

	l = http_EstimateWS(sp->wrk->beresp,
	    pass ? HTTPH_R_PASS : HTTPH_A_INS, &nhttp);

	/* Create Vary instructions */
	if (sp->objcore != NULL) {
		CHECK_OBJ_NOTNULL(sp->objcore, OBJCORE_MAGIC);
		varyl = VRY_Create(sp, sp->wrk->beresp, &vary);
		if (varyl > 0) {
			AN(vary);
			assert(varyl == VSB_len(vary));
			l += varyl;
		} else if (varyl < 0) {
			/* Vary parse error */
			AZ(vary);
			sp->err_code = 503;
			sp->step = STP_ERROR;
			VDI_CloseFd(sp);
			return (0);
		} else
			/* No vary */
			AZ(vary);
	}

	/*
	 * Space for producing a Content-Length: header including padding
	 * A billion gigabytes is enough for anybody.
	 */
	l += strlen("Content-Length: XxxXxxXxxXxxXxxXxx") + sizeof(void *);

	if (sp->wrk->exp.ttl < params->shortlived || pass == 1)
		sp->wrk->storage_hint = TRANSIENT_STORAGE;

	sp->obj = STV_NewObject(sp, sp->wrk->storage_hint, l,
	    &sp->wrk->exp, nhttp);
	if (sp->obj == NULL) {
		/*
		 * Try to salvage the transaction by allocating a
		 * shortlived object on Transient storage.
		 */
		if (sp->wrk->exp.ttl > params->shortlived)
			sp->wrk->exp.ttl = params->shortlived;
		sp->wrk->exp.grace = 0.0;
		sp->wrk->exp.keep = 0.0;
		sp->obj = STV_NewObject(sp, TRANSIENT_STORAGE, l,
		    &sp->wrk->exp, nhttp);
	}
	if (sp->obj == NULL) {
		sp->err_code = 503;
		sp->step = STP_ERROR;
		VDI_CloseFd(sp);
		return (0);
	}
	CHECK_OBJ_NOTNULL(sp->obj, OBJECT_MAGIC);
{'fraction_non_alphanumeric': 0.18333533149502457, 'fraction_numerical': 0.00879990408823882, 'mean_word_length': 3.2652894252403355, 'pattern_counts': {'":': 0, '<': 33, '<?xml version=': 0, '>': 1074, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3682541', 'n_tokens_mistral': 17920, 'n_tokens_neox': 16628, 'n_words': 3899}