
/*
 * $Id: http.c,v 1.325 1998/09/24 20:41:21 rousskov Exp $
 *
 * DEBUG: section 11    Hypertext Transfer Protocol (HTTP)
 * AUTHOR: Harvest Derived
 *
 * SQUID Internet Object Cache  http://squid.nlanr.net/Squid/
 * ----------------------------------------------------------
 *
 *  Squid is the result of efforts by numerous individuals from the
 *  Internet community.  Development is led by Duane Wessels of the
 *  National Laboratory for Applied Network Research and funded by the
 *  National Science Foundation.  Squid is Copyrighted (C) 1998 by
 *  Duane Wessels and the University of California San Diego.  Please
 *  see the COPYRIGHT file for full details.  Squid incorporates
 *  software developed and/or copyrighted by other sources.  Please see
 *  the CREDITS file for full details.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *  
 *  This program 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 General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
 *
 */

/*
 * Anonymizing patch by lutz@as-node.jena.thur.de
 * have a look into http-anon.c to get more informations.
 */

#include "squid.h"

static const char *const crlf = "\r\n";

static CNCB httpConnectDone;
static CWCB httpSendComplete;
static CWCB httpSendRequestEntry;

static PF httpReadReply;
static PF httpSendRequest;
static PF httpStateFree;
static PF httpTimeout;
static void httpCacheNegatively(StoreEntry *);
static void httpMakePrivate(StoreEntry *);
static void httpMakePublic(StoreEntry *);
static int httpCachableReply(HttpStateData *);
static void httpMaybeRemovePublic(StoreEntry *, http_status);

static void
httpStateFree(int fdnotused, void *data)
{
    HttpStateData *httpState = data;
    if (httpState == NULL)
	return;
    storeUnlockObject(httpState->entry);
    if (httpState->reply_hdr) {
	memFree(MEM_8K_BUF, httpState->reply_hdr);
	httpState->reply_hdr = NULL;
    }
    requestUnlink(httpState->request);
    requestUnlink(httpState->orig_request);
    httpState->request = NULL;
    httpState->orig_request = NULL;
    cbdataFree(httpState);
}

int
httpCachable(method_t method)
{
    /* GET and HEAD are cachable. Others are not. */
    if (method != METHOD_GET && method != METHOD_HEAD)
	return 0;
    /* else cachable */
    return 1;
}

static void
httpTimeout(int fd, void *data)
{
    HttpStateData *httpState = data;
    StoreEntry *entry = httpState->entry;
    ErrorState *err;
    debug(11, 4) ("httpTimeout: FD %d: '%s'\n", fd, storeUrl(entry));
    assert(entry->store_status == STORE_PENDING);
    if (entry->mem_obj->inmem_hi == 0) {
	err = errorCon(ERR_READ_TIMEOUT, HTTP_GATEWAY_TIMEOUT);
	err->request = requestLink(httpState->orig_request);
	errorAppendEntry(entry, err);
    } else {
	storeAbort(entry, 0);
    }
    comm_close(fd);
}

/* This object can be cached for a long time */
static void
httpMakePublic(StoreEntry * entry)
{
    if (EBIT_TEST(entry->flags, ENTRY_CACHABLE))
	storeSetPublicKey(entry);
}

/* This object should never be cached at all */
static void
httpMakePrivate(StoreEntry * entry)
{
    storeExpireNow(entry);
    storeReleaseRequest(entry);	/* delete object when not used */
    /* storeReleaseRequest clears ENTRY_CACHABLE flag */
}

/* This object may be negatively cached */
static void
httpCacheNegatively(StoreEntry * entry)
{
    storeNegativeCache(entry);
    if (EBIT_TEST(entry->flags, ENTRY_CACHABLE))
	storeSetPublicKey(entry);
}

static void
httpMaybeRemovePublic(StoreEntry * e, http_status status)
{
    int remove = 0;
    StoreEntry *pe;
    if (!EBIT_TEST(e->flags, KEY_PRIVATE))
	return;
    switch (status) {
    case HTTP_OK:
    case HTTP_NON_AUTHORITATIVE_INFORMATION:
    case HTTP_MULTIPLE_CHOICES:
    case HTTP_MOVED_PERMANENTLY:
    case HTTP_MOVED_TEMPORARILY:
    case HTTP_FORBIDDEN:
    case HTTP_NOT_FOUND:
    case HTTP_METHOD_NOT_ALLOWED:
    case HTTP_GONE:
	remove = 1;
	break;
#if WORK_IN_PROGRESS
    case HTTP_UNAUTHORIZED:
	remove = 1;
	break;
#endif
    default:
	remove = 0;
	break;
    }
    if (!remove)
	return;
    assert(e->mem_obj);
    if ((pe = storeGetPublic(e->mem_obj->url, e->mem_obj->method)) != NULL) {
	assert(e != pe);
	storeRelease(pe);
    }
    if (e->mem_obj->method == METHOD_GET) {
	/* A fresh GET should eject old HEAD objects */
	if ((pe = storeGetPublic(e->mem_obj->url, METHOD_HEAD)) != NULL) {
	    assert(e != pe);
	    storeRelease(pe);
	}
    }
}

static int
httpCachableReply(HttpStateData * httpState)
{
    HttpReply *rep = httpState->entry->mem_obj->reply;
    HttpHeader *hdr = &rep->header;
    const int cc_mask = (rep->cache_control) ? rep->cache_control->mask : 0;
    if (EBIT_TEST(cc_mask, CC_PRIVATE))
	return 0;
    if (EBIT_TEST(cc_mask, CC_NO_CACHE))
	return 0;
    if (EBIT_TEST(cc_mask, CC_NO_STORE))
	return 0;
    if (httpState->request->flags.auth) {
	/*
	 * Responses to requests with authorization may be cached
	 * only if a Cache-Control: public reply header is present.
	 * RFC 2068, sec 14.9.4
	 */
	if (!EBIT_TEST(cc_mask, CC_PUBLIC))
	    return 0;
    }
    /*
     * We don't properly deal with Vary features yet, so we can't
     * cache these
     */
    if (httpHeaderHas(hdr, HDR_VARY))
	return 0;
    switch (httpState->entry->mem_obj->reply->sline.status) {
	/* Responses that are cacheable */
    case HTTP_OK:
    case HTTP_NON_AUTHORITATIVE_INFORMATION:
    case HTTP_MULTIPLE_CHOICES:
    case HTTP_MOVED_PERMANENTLY:
    case HTTP_GONE:
	/* don't cache objects from peers w/o LMT, Date, or Expires */
	/* check that is it enough to check headers @?@ */
	if (rep->date > -1)
	    return 1;
	else if (rep->last_modified > -1)
	    return 1;
	else if (!httpState->peer)
	    return 1;
	/* @?@ (here and 302): invalid expires header compiles to squid_curtime */
	else if (rep->expires > -1)
	    return 1;
	else
	    return 0;
	/* NOTREACHED */
	break;
	/* Responses that only are cacheable if the server says so */
    case HTTP_MOVED_TEMPORARILY:
	if (rep->expires > -1)
	    return 1;
	else
	    return 0;
	/* NOTREACHED */
	break;
	/* Errors can be negatively cached */
    case HTTP_NO_CONTENT:
    case HTTP_USE_PROXY:
    case HTTP_BAD_REQUEST:
    case HTTP_FORBIDDEN:
    case HTTP_NOT_FOUND:
    case HTTP_METHOD_NOT_ALLOWED:
    case HTTP_REQUEST_URI_TOO_LARGE:
    case HTTP_INTERNAL_SERVER_ERROR:
    case HTTP_NOT_IMPLEMENTED:
    case HTTP_BAD_GATEWAY:
    case HTTP_SERVICE_UNAVAILABLE:
    case HTTP_GATEWAY_TIMEOUT:
	return -1;
	/* NOTREACHED */
	break;
	/* Some responses can never be cached */
    case HTTP_PARTIAL_CONTENT:	/* Not yet supported */
    case HTTP_SEE_OTHER:
    case HTTP_NOT_MODIFIED:
    case HTTP_UNAUTHORIZED:
    case HTTP_PROXY_AUTHENTICATION_REQUIRED:
    case HTTP_INVALID_HEADER:	/* Squid header parsing error */
    default:			/* Unknown status code */
	return 0;
	/* NOTREACHED */
	break;
    }
    /* NOTREACHED */
}

/* rewrite this later using new interfaces @?@ */
void
httpProcessReplyHeader(HttpStateData * httpState, const char *buf, int size)
{
    char *t = NULL;
    StoreEntry *entry = httpState->entry;
    int room;
    int hdr_len;
    HttpReply *reply = entry->mem_obj->reply;
    debug(11, 3) ("httpProcessReplyHeader: key '%s'\n",
	storeKeyText(entry->key));
    if (httpState->reply_hdr == NULL)
	httpState->reply_hdr = memAllocate(MEM_8K_BUF);
    if (httpState->reply_hdr_state == 0) {
	hdr_len = strlen(httpState->reply_hdr);
	room = 8191 - hdr_len;
	strncat(httpState->reply_hdr, buf, room < size ? room : size);
	hdr_len += room < size ? room : size;
	if (hdr_len > 4 && strncmp(httpState->reply_hdr, "HTTP/", 5)) {
	    debug(11, 3) ("httpProcessReplyHeader: Non-HTTP-compliant header: '%s'\n", httpState->reply_hdr);
	    httpState->reply_hdr_state += 2;
	    reply->sline.status = HTTP_INVALID_HEADER;
	    return;
	}
	t = httpState->reply_hdr + hdr_len;
	/* headers can be incomplete only if object still arriving */
	if (!httpState->eof) {
	    size_t k = headersEnd(httpState->reply_hdr, 8192);
	    if (0 == k)
		return;		/* headers not complete */
	    t = httpState->reply_hdr + k;
	}
	*t = '\0';
	httpState->reply_hdr_state++;
    }
    if (httpState->reply_hdr_state == 1) {
	const Ctx ctx = ctx_enter(entry->mem_obj->url);
	httpState->reply_hdr_state++;
	debug(11, 9) ("GOT HTTP REPLY HDR:\n---------\n%s\n----------\n",
	    httpState->reply_hdr);
	/* Parse headers into reply structure */
	/* what happens if we fail to parse here? */
	httpReplyParse(reply, httpState->reply_hdr);	/* httpState->eof); */
	storeTimestampsSet(entry);
	/* Check if object is cacheable or not based on reply code */
	debug(11, 3) ("httpProcessReplyHeader: HTTP CODE: %d\n", reply->sline.status);
	if (neighbors_do_private_keys)
	    httpMaybeRemovePublic(entry, reply->sline.status);
	switch (httpCachableReply(httpState)) {
	case 1:
	    httpMakePublic(entry);
	    break;
	case 0:
	    httpMakePrivate(entry);
	    break;
	case -1:
	    httpCacheNegatively(entry);
	    break;
	default:
	    assert(0);
	    break;
	}
	if (reply->cache_control) {
	    if (EBIT_TEST(reply->cache_control->mask, CC_PROXY_REVALIDATE))
		EBIT_SET(entry->flags, ENTRY_REVALIDATE);
	    else if (EBIT_TEST(reply->cache_control->mask, CC_MUST_REVALIDATE))
		EBIT_SET(entry->flags, ENTRY_REVALIDATE);
	}
	if (httpState->flags.keepalive)
	    if (httpState->peer)
		httpState->peer->stats.n_keepalives_sent++;
	if (reply->keep_alive)
	    if (httpState->peer)
		httpState->peer->stats.n_keepalives_recv++;
	ctx_exit(ctx);
    }
}

static int
httpPconnTransferDone(HttpStateData * httpState)
{
    /* return 1 if we got the last of the data on a persistent connection */
    MemObject *mem = httpState->entry->mem_obj;
    HttpReply *reply = mem->reply;
    debug(11, 3) ("httpPconnTransferDone: FD %d\n", httpState->fd);
    /*
     * If we didn't send a keep-alive request header, then this
     * can not be a persistent connection.
     */
    if (!httpState->flags.keepalive)
	return 0;
    /*
     * What does the reply have to say about keep-alive?
     */
    if (!reply->keep_alive)
	return 0;
    debug(11, 5) ("httpPconnTransferDone: content_length=%d\n",
	reply->content_length);
    /*
     * Deal with gross HTTP stuff
     *    - If we haven't seen the end of the reply headers, we can't
     *      be persistent.
     *    - For HEAD requests we're done.
     *    - For "200 OK" check the content-length in the next block.
     *    - For "204 No Content" (even with content-length) we're done.
     *    - For "304 Not Modified" (even with content-length) we're done.
     *    - 1XX replies never have a body; we're done.
     *    - For all other replies, check content length in next block.
     */
    if (httpState->reply_hdr_state < 2)
	return 0;
    else if (httpState->request->method == METHOD_HEAD)
	return 1;
    else if (reply->sline.status == HTTP_OK)
	(void) 0;		/* common case, continue */
    else if (reply->sline.status == HTTP_NO_CONTENT)
	return 1;
    else if (reply->sline.status == HTTP_NOT_MODIFIED)
	return 1;
    else if (reply->sline.status < HTTP_OK)
	return 1;
    /*
     * If there is no content-length, then we can't be
     * persistent.  If there is a content length, then we must
     * wait until we've seen the end of the body.
     */
    if (reply->content_length < 0)
	return 0;
    else if (mem->inmem_hi < reply->content_length + reply->hdr_sz)
	return 0;
    else
	return 1;
}

/* This will be called when data is ready to be read from fd.  Read until
 * error or connection closed. */
/* XXX this function is too long! */
static void
httpReadReply(int fd, void *data)
{
    HttpStateData *httpState = data;
    LOCAL_ARRAY(char, buf, SQUID_TCP_SO_RCVBUF);
    StoreEntry *entry = httpState->entry;
    const request_t *request = httpState->request;
    int len;
    int bin;
    int clen;
    size_t read_sz;
#if DELAY_POOLS
    delay_id delay_id = delayMostBytesAllowed(entry->mem_obj);
#endif
    /* check if we want to defer reading */
    errno = 0;
    read_sz = SQUID_TCP_SO_RCVBUF;
#if DELAY_POOLS
    read_sz = delayBytesWanted(delay_id, 1, read_sz);
#endif
    Counter.syscalls.sock.reads++;
    len = read(fd, buf, read_sz);
    debug(11, 5) ("httpReadReply: FD %d: len %d.\n", fd, len);
    if (len > 0) {
	fd_bytes(fd, len, FD_READ);
#if DELAY_POOLS
	delayBytesIn(delay_id, len);
#endif
	kb_incr(&Counter.server.all.kbytes_in, len);
	kb_incr(&Counter.server.http.kbytes_in, len);
	commSetTimeout(fd, Config.Timeout.read, NULL, NULL);
	IOStats.Http.reads++;
	for (clen = len - 1, bin = 0; clen; bin++)
	    clen >>= 1;
	IOStats.Http.read_hist[bin]++;
    }
    if (!httpState->reply_hdr && len > 0) {
	/* Skip whitespace */
	while (len > 0 && isspace(*buf))
	    xmemmove(buf, buf + 1, len--);
	if (len == 0) {
	    /* Continue to read... */
	    commSetSelect(fd, COMM_SELECT_READ, httpReadReply, httpState, 0);
	    return;
	}
    }
    if (len < 0) {
	debug(50, 2) ("httpReadReply: FD %d: read failure: %s.\n",
	    fd, xstrerror());
	if (ignoreErrno(errno)) {
	    commSetSelect(fd, COMM_SELECT_READ, httpReadReply, httpState, 0);
	} else if (entry->mem_obj->inmem_hi == 0) {
	    fwdFail(httpState->fwdState, ERR_READ_ERROR, HTTP_INTERNAL_SERVER_ERROR, errno);
	    comm_close(fd);
	} else {
	    storeAbort(entry, 0);
	    comm_close(fd);
	}
    } else if (len == 0 && entry->mem_obj->inmem_hi == 0) {
	fwdFail(httpState->fwdState, ERR_ZERO_SIZE_OBJECT, HTTP_SERVICE_UNAVAILABLE, errno);
	httpState->eof = 1;
	comm_close(fd);
    } else if (len == 0) {
	/* Connection closed; retrieval done. */
	httpState->eof = 1;
	if (httpState->reply_hdr_state < 2)
	    /*
	     * Yes Henrik, there is a point to doing this.  When we
	     * called httpProcessReplyHeader() before, we didn't find
	     * the end of headers, but now we are definately at EOF, so
	     * we want to process the reply headers.
	     */
	    httpProcessReplyHeader(httpState, buf, len);
#ifdef PPNR_WIP
	storePPNR(entry);
#endif /* PPNR_WIP */
	storeComplete(entry);	/* deallocates mem_obj->request */
	comm_close(fd);
    } else {
#ifndef PPNR_WIP
	if (httpState->reply_hdr_state < 2)
#else
	if (httpState->reply_hdr_state < 2) {
#endif /* PPNR_WIP */
	    httpProcessReplyHeader(httpState, buf, len);
#ifdef PPNR_WIP
	    if (httpState->reply_hdr_state == 2)
		storePPNR(entry);
	}
#endif /* PPNR_WIP */
	storeAppend(entry, buf, len);
#ifdef OPTIMISTIC_IO
	if (entry->store_status == STORE_ABORTED) {
	    /*
	     * the above storeAppend() call could ABORT this entry,
	     * in that case, the server FD should already be closed.
	     * there's nothing for us to do.
	     */
	    (void) 0;
	} else
#endif
	if (httpPconnTransferDone(httpState)) {
	    /* yes we have to clear all these! */
	    commSetDefer(fd, NULL, NULL);
	    commSetTimeout(fd, -1, NULL, NULL);
	    commSetSelect(fd, COMM_SELECT_READ, NULL, NULL, 0);
	    comm_remove_close_handler(fd, httpStateFree, httpState);
	    storeComplete(entry);	/* deallocates mem_obj->request */
	    /* call storeComplete BEFORE fwdUnregister or else fwdUnregister
	     * will storeAbort */
	    fwdUnregister(fd, httpState->fwdState);
	    pconnPush(fd, request->host, request->port);
	    httpState->fd = -1;
	    httpStateFree(-1, httpState);
	} else {
	    /* Wait for EOF condition */
	    commSetSelect(fd, COMM_SELECT_READ, httpReadReply, httpState, 0);
	}
    }
}

/* This will be called when request write is complete. Schedule read of
 * reply. */
static void
httpSendComplete(int fd, char *bufnotused, size_t size, int errflag, void *data)
{
    HttpStateData *httpState = data;
    StoreEntry *entry = httpState->entry;
    ErrorState *err;
    debug(11, 5) ("httpSendComplete: FD %d: size %d: errflag %d.\n",
	fd, size, errflag);
    if (size > 0) {
	fd_bytes(fd, size, FD_WRITE);
	kb_incr(&Counter.server.all.kbytes_out, size);
	kb_incr(&Counter.server.http.kbytes_out, size);
    }
    if (errflag == COMM_ERR_CLOSING)
	return;
    if (errflag) {
	err = errorCon(ERR_WRITE_ERROR, HTTP_INTERNAL_SERVER_ERROR);
	err->xerrno = errno;
	err->request = requestLink(httpState->orig_request);
	errorAppendEntry(entry, err);
	comm_close(fd);
	return;
    } else {
	/* Schedule read reply. */
	commSetSelect(fd,
	    COMM_SELECT_READ,
	    httpReadReply,
	    httpState, 0);
	commSetDefer(fd, fwdCheckDeferRead, entry);
    }
}

/*
 * build request headers and append them to a given MemBuf 
 * used by httpBuildRequestPrefix()
 * note: calls httpHeaderInit(), the caller is responsible for Clean()-ing
 */
void
httpBuildRequestHeader(request_t * request,
    request_t * orig_request,
    StoreEntry * entry,
    HttpHeader * hdr_out,
    int cfd,
    http_state_flags flags)
{
    /* building buffer for complex strings */
#define BBUF_SZ (MAX_URL+32)
    LOCAL_ARRAY(char, bbuf, BBUF_SZ);
    String strConnection = StringNull;
    const HttpHeader *hdr_in = &orig_request->header;
    int we_do_ranges;
    const HttpHeaderEntry *e;
    HttpHeaderPos pos = HttpHeaderInitPos;
    httpHeaderInit(hdr_out, hoRequest);
    /* append our IMS header */
    if (entry && entry->lastmod > -1 && request->method == METHOD_GET)
	httpHeaderPutTime(hdr_out, HDR_IF_MODIFIED_SINCE, entry->lastmod);

    /* decide if we want to do Ranges ourselves 
     * (and fetch the whole object now)
     * We want to handle Ranges ourselves iff
     *    - we can actually parse client Range specs
     *    - the specs are expected to be simple enough (e.g. no out-of-order ranges)
     *    - reply will be cachable
     * (If the reply will be uncachable we have to though it away after 
     *  serving this request, so it is better to forward ranges to 
     *  the server and fetch only the requested content) 
     */
    we_do_ranges =
	orig_request->range && orig_request->flags.cachable && !httpHdrRangeWillBeComplex(orig_request->range);
    debug(11, 8) ("httpBuildRequestHeader: range specs: %p, cachable: %d; we_do_ranges: %d\n",
	orig_request->range, orig_request->flags.cachable, we_do_ranges);

    strConnection = httpHeaderGetList(hdr_in, HDR_CONNECTION);
    while ((e = httpHeaderGetEntry(hdr_in, &pos))) {
	debug(11, 5) ("httpBuildRequestHeader: %s: %s\n",
	    strBuf(e->name), strBuf(e->value));
	if (!httpRequestHdrAllowed(e, &strConnection))
	    continue;
	switch (e->id) {
	case HDR_PROXY_AUTHORIZATION:
	    /* If we're not going to do proxy auth, then it must be passed on */
	    if (!request->flags.used_proxy_auth)
		httpHeaderAddEntry(hdr_out, httpHeaderEntryClone(e));
	    break;
	case HDR_HOST:
	    /* Don't use client's Host: header for redirected requests */
	    if (!request->flags.redirected)
		httpHeaderAddEntry(hdr_out, httpHeaderEntryClone(e));
	    break;
	case HDR_IF_MODIFIED_SINCE:
	    /* append unless we added our own;
	     * note: at most one client's ims header can pass through */
	    if (!httpHeaderHas(hdr_out, HDR_IF_MODIFIED_SINCE))
		httpHeaderAddEntry(hdr_out, httpHeaderEntryClone(e));
	    break;
	case HDR_MAX_FORWARDS:
	    if (orig_request->method == METHOD_TRACE) {
		/* sacrificing efficiency over clarity, etc. */
		const int hops = httpHeaderGetInt(hdr_in, HDR_MAX_FORWARDS);
		if (hops > 0)
		    httpHeaderPutInt(hdr_out, HDR_MAX_FORWARDS, hops - 1);
	    }
	    break;
	case HDR_RANGE:
	case HDR_IF_RANGE:
	case HDR_REQUEST_RANGE:
	    if (!we_do_ranges)
		httpHeaderAddEntry(hdr_out, httpHeaderEntryClone(e));
	    break;
	case HDR_PROXY_CONNECTION:
	case HDR_CONNECTION:
	case HDR_VIA:
	case HDR_X_FORWARDED_FOR:
	case HDR_CACHE_CONTROL:
	    /* append these after the loop if needed */
	    break;
	default:
	    /* pass on all other header fields */
	    httpHeaderAddEntry(hdr_out, httpHeaderEntryClone(e));
	}
    }

    /* append fake user agent if configured and 
     * the real one is not supplied by the client */
    if (Config.fake_ua && !httpHeaderHas(hdr_out, HDR_USER_AGENT))
	httpHeaderPutStr(hdr_out, HDR_USER_AGENT, Config.fake_ua);

    /* append Via */
    {
	String strVia = httpHeaderGetList(hdr_in, HDR_VIA);
	snprintf(bbuf, BBUF_SZ, "%3.1f %s", orig_request->http_ver, ThisCache);
	strListAdd(&strVia, bbuf, ',');
	httpHeaderPutStr(hdr_out, HDR_VIA, strBuf(strVia));
	stringClean(&strVia);
    }
    /* append X-Forwarded-For */
    {
	String strFwd = httpHeaderGetList(hdr_in, HDR_X_FORWARDED_FOR);
	strListAdd(&strFwd, (cfd < 0 ? "unknown" : fd_table[cfd].ipaddr), ',');
	httpHeaderPutStr(hdr_out, HDR_X_FORWARDED_FOR, strBuf(strFwd));
	stringClean(&strFwd);
    }
    /* append Host if not there already */
    if (!httpHeaderHas(hdr_out, HDR_HOST)) {
	/* use port# only if not default */
	if (orig_request->port == urlDefaultPort(orig_request->protocol)) {
	    httpHeaderPutStr(hdr_out, HDR_HOST, orig_request->host);
	} else {
	    httpHeaderPutStrf(hdr_out, HDR_HOST, "%s:%d",
		orig_request->host, (int) orig_request->port);
	}
    }
    /* append Cache-Control, add max-age if not there already */
    {
	HttpHdrCc *cc = httpHeaderGetCc(hdr_in);
	if (!cc)
	    cc = httpHdrCcCreate();
	if (!EBIT_TEST(cc->mask, CC_MAX_AGE)) {
	    const char *url = entry ? storeUrl(entry) : urlCanonical(orig_request);
	    httpHdrCcSetMaxAge(cc, getMaxAge(url));
	    if (strLen(request->urlpath))
		assert(strstr(url, strBuf(request->urlpath)));
	}
	httpHeaderPutCc(hdr_out, cc);
	httpHdrCcDestroy(cc);
    }
    /* maybe append Connection: keep-alive */
    if (flags.keepalive) {
	if (flags.proxying) {
	    httpHeaderPutStr(hdr_out, HDR_PROXY_CONNECTION, "keep-alive");
	} else {
	    httpHeaderPutStr(hdr_out, HDR_CONNECTION, "keep-alive");
	}
    }
    stringClean(&strConnection);
}

/* build request prefix and append it to a given MemBuf; 
 * return the length of the prefix */
size_t
httpBuildRequestPrefix(request_t * request,
    request_t * orig_request,
    StoreEntry * entry,
    MemBuf * mb,
    int cfd,
    http_state_flags flags)
{
    const int offset = mb->size;
    memBufPrintf(mb, "%s %s HTTP/1.0\r\n",
	RequestMethodStr[request->method],
	strLen(request->urlpath) ? strBuf(request->urlpath) : "/");
    /* build and pack headers */
    {
	HttpHeader hdr;
	Packer p;
	httpBuildRequestHeader(request, orig_request, entry, &hdr, cfd, flags);
	packerToMemInit(&p, mb);
	httpHeaderPackInto(&hdr, &p);
	httpHeaderClean(&hdr);
	packerClean(&p);
    }
    /* append header terminator */
    memBufAppend(mb, "\r\n", 2);
    return mb->size - offset;
}

/* This will be called when connect completes. Write request. */
static void
httpSendRequest(int fd, void *data)
{
    HttpStateData *httpState = data;
    MemBuf mb;
    request_t *req = httpState->request;
    StoreEntry *entry = httpState->entry;
    int cfd;
    peer *p = httpState->peer;
    CWCB *sendHeaderDone;

    debug(11, 5) ("httpSendRequest: FD %d: httpState %p.\n", fd, httpState);

    if (pumpMethod(req->method))
	sendHeaderDone = httpSendRequestEntry;
    else
	sendHeaderDone = httpSendComplete;

    if (!opt_forwarded_for)
	cfd = -1;
    else if (entry->mem_obj == NULL)
	cfd = -1;
    else
	cfd = entry->mem_obj->fd;
    assert(-1 == cfd || FD_SOCKET == fd_table[cfd].type);
    if (p != NULL)
	httpState->flags.proxying = 1;
    /*
     * Is keep-alive okay for all request methods?
     */
    if (p == NULL)
	httpState->flags.keepalive = 1;
    else if (p->stats.n_keepalives_sent < 10)
	httpState->flags.keepalive = 1;
    else if ((double) p->stats.n_keepalives_recv / (double) p->stats.n_keepalives_sent > 0.50)
	httpState->flags.keepalive = 1;
    memBufDefInit(&mb);
    httpBuildRequestPrefix(req,
	httpState->orig_request,
	entry,
	&mb,
	cfd,
	httpState->flags);
    debug(11, 6) ("httpSendRequest: FD %d:\n%s\n", fd, mb.buf);
    comm_write_mbuf(fd, mb, sendHeaderDone, httpState);
}

void
httpStart(FwdState * fwdState, int fd)
{
    HttpStateData *httpState = memAllocate(MEM_HTTP_STATE_DATA);
    request_t *proxy_req;
    request_t *orig_req = fwdState->request;
    debug(11, 3) ("httpStart: \"%s %s\"\n",
	RequestMethodStr[orig_req->method],
	storeUrl(fwdState->entry));
    cbdataAdd(httpState, MEM_HTTP_STATE_DATA);
    storeLockObject(fwdState->entry);
    httpState->fwdState = fwdState;
    httpState->entry = fwdState->entry;
    httpState->fd = fd;
    if (fwdState->servers)
	httpState->peer = fwdState->servers->peer;	/* might be NULL */
    if (httpState->peer) {
	proxy_req = requestCreate(orig_req->method,
	    PROTO_NONE, storeUrl(httpState->entry));
	xstrncpy(proxy_req->host, httpState->peer->host, SQUIDHOSTNAMELEN);
	proxy_req->port = httpState->peer->http_port;
	proxy_req->flags = orig_req->flags;
	httpState->request = requestLink(proxy_req);
	httpState->orig_request = requestLink(orig_req);
	proxy_req->flags.proxying = 1;
	/*
	 * This NEIGHBOR_PROXY_ONLY check probably shouldn't be here.
	 * We might end up getting the object from somewhere else if,
	 * for example, the request to this neighbor fails.
	 */
	if (httpState->peer->options.proxy_only)
	    storeReleaseRequest(httpState->entry);
#if DELAY_POOLS
	if (httpState->peer->options.no_delay) {
	    proxy_req->delay_id = 0;
	} else {
	    proxy_req->delay_id = orig_req->delay_id;
	}
#endif
    } else {
	httpState->request = requestLink(orig_req);
	httpState->orig_request = requestLink(orig_req);
    }
    /*
     * register the handler to free HTTP state data when the FD closes
     */
    comm_add_close_handler(fd, httpStateFree, httpState);
    Counter.server.all.requests++;
    Counter.server.http.requests++;
    httpConnectDone(fd, COMM_OK, httpState);
}

static void
httpConnectDone(int fd, int status, void *data)
{
    HttpStateData *httpState = data;
    request_t *request = httpState->request;
    StoreEntry *entry = httpState->entry;
    ErrorState *err;
    if (status == COMM_ERR_DNS) {
	debug(11, 4) ("httpConnectDone: Unknown host: %s\n", request->host);
	err = errorCon(ERR_DNS_FAIL, HTTP_SERVICE_UNAVAILABLE);
	err->dnsserver_msg = xstrdup(dns_error_message);
	err->request = requestLink(httpState->orig_request);
	errorAppendEntry(entry, err);
	comm_close(fd);
    } else if (status != COMM_OK) {
	err = errorCon(ERR_CONNECT_FAIL, HTTP_SERVICE_UNAVAILABLE);
	err->xerrno = errno;
	err->host = xstrdup(request->host);
	err->port = request->port;
	err->request = requestLink(httpState->orig_request);
	errorAppendEntry(entry, err);
	if (httpState->peer)
	    peerCheckConnectStart(httpState->peer);
	comm_close(fd);
    } else {
	commSetSelect(fd, COMM_SELECT_WRITE, httpSendRequest, httpState, 0);
	commSetTimeout(fd, Config.Timeout.read, httpTimeout, httpState);
    }
}

static void
httpSendRequestEntry(int fd, char *bufnotused, size_t size, int errflag, void *data)
{
    HttpStateData *httpState = data;
    StoreEntry *entry = httpState->entry;
    ErrorState *err;
    debug(11, 5) ("httpSendRequestEntry: FD %d: size %d: errflag %d.\n",
	fd, size, errflag);
    if (size > 0) {
	fd_bytes(fd, size, FD_WRITE);
	kb_incr(&Counter.server.all.kbytes_out, size);
	kb_incr(&Counter.server.http.kbytes_out, size);
    }
    if (errflag == COMM_ERR_CLOSING)
	return;
    if (errflag) {
	err = errorCon(ERR_WRITE_ERROR, HTTP_INTERNAL_SERVER_ERROR);
	err->xerrno = errno;
	err->request = requestLink(httpState->orig_request);
	errorAppendEntry(entry, err);
	comm_close(fd);
	return;
    }
    pumpStart(fd, entry, httpState->orig_request, httpSendComplete, httpState);
}
