view usr/src/uts/common/fs/sockfs/nl7chttp.c @ 13779:bf40125f4b37

3063 many instances of unlike enum comparison Reviewed by: Robert Mustacchi <rm@joyent.com> Approved by: Garrett D'Amore <garrett@damore.org>
author Richard Lowe <richlowe@richlowe.net>
date Sun, 08 Jul 2012 03:19:56 +0100
parents cebb50cbe4f9
children
line wrap: on
line source

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <sys/sysmacros.h>
#include <sys/strsubr.h>
#include <fs/sockfs/nl7c.h>
#include <fs/sockfs/nl7curi.h>
#include <fs/sockfs/socktpi.h>

#include <inet/nca/ncadoorhdr.h>
#include <inet/nca/ncalogd.h>


volatile uint64_t	nl7c_http_response_chunked = 0;
volatile uint64_t	nl7c_http_response_chunkparse = 0;

volatile uint64_t	nl7c_http_response_pass1 = 0;
volatile uint64_t	nl7c_http_response_pass2 = 0;
volatile uint64_t	nl7c_http_response_304 = 0;
volatile uint64_t	nl7c_http_response_307 = 0;
volatile uint64_t	nl7c_http_response_400 = 0;

volatile uint64_t	nl7c_http_cond_304 = 0;
volatile uint64_t	nl7c_http_cond_412 = 0;

/*
 * Some externs:
 */

extern uint64_t		nl7c_uri_bytes;
extern kmem_cache_t	*nl7c_uri_kmc;
extern kmem_cache_t	*nl7c_uri_rd_kmc;
extern void		nl7c_uri_inactive(uri_desc_t *);
extern uint32_t		nca_major_version;
extern uint32_t		nca_minor_version;

/*
 * HTTP connection persistent headers, mblk_t's, and state values stored in
 * (struct sonode *).so_nl7c_flags & NL7C_SCHEMEPRIV.
 */

char	Shttp_conn_cl[] = "Connection: close\r\n";
char	Shttp_conn_ka[] = "Connection: Keep-Alive\r\n";

mblk_t	*http_conn_cl;
mblk_t	*http_conn_ka;

#define	HTTP_CONN_CL	0x00010000
#define	HTTP_CONN_KA	0x00020000

/*
 * Hex ascii Digit to Integer accumulate, if (char)c is a valid ascii
 * hex digit then the contents of (int32_t)n will be left shifted and
 * the new digit added in, else n will be set to -1.
 */

#define	hd2i(c, n) {							\
	(n) *= 16;							\
	if (isdigit(c))							\
		(n) += (c) - '0';					\
	else if ((c) >= 'a' && (c) <= 'f')				\
		(n) += (c) - 'W';					\
	else if ((c) >= 'A' && (c) <= 'F')				\
		(n) += (c) - '7';					\
	else								\
		(n) = -1;						\
}

/*
 * HTTP parser action values:
 */

typedef enum act_e {
	REQUEST		= 0x0001,
	NUMERIC		= 0x0002,
	QUALIFIER	= 0x0004,
	PASS		= 0x0008,
	FILTER		= 0x0010,
	NOCACHE		= 0x0020,
	HASH		= 0x0040,
	DATE		= 0x0080,
	ETAG		= 0x0100,
	RESPONSE	= 0x0200,
	URIABS		= 0x0400,
	URIREL		= 0x0800,
	HEX		= 0x1000
} act_t;

#define	UNDEF		PASS

/*
 * HTTP parser token:
 */

typedef struct token_s {
	int	tokid;			/* Token ident */
	char	*text;			/* Token text */
	act_t	act;			/* Action to take */
} token_t;

/*
 * The ttree_t (or token tree) is an ascending ordered binary tree
 * built by ttree_build() from an array of tokens and subsequently
 * used by ttree_line_parse() to parse multiline text data.
 */
typedef struct ttree_s {
	token_t *tok;			/* Token */
	struct ttree_s *lt, *gt;	/* < and > next node */
} ttree_t;

/*
 * Note: req_tree[] and res_tree[] must be in ascending case insensitive
 * order of the char[] strings used to initialize each element.
 *
 * See "nl7ctokreq.txt" and "nl7ctokres.txt" which are processed by
 * "nl7ctokgen" to produce "nl7ctokgen.h" and included here.
 */

#define	INIT(s, t) {s, S##s, t}

#include "nl7ctokgen.h"
static ttree_t *req_tree;
static ttree_t *res_tree;

/*
 * HTTP scheme private state:
 */

typedef struct http_s {
	boolean_t	parsed;		/* Response parsed */
	uint32_t	major, minor;	/* HTTP/major.minor */
	uint32_t	headlen;	/* HTTP header length */
	clock_t		date;		/* Response Date: */
	clock_t		expire;		/* Response Expire: */
	clock_t		moddate;	/* Request *Modified-Since date */
	enum tokid_e	modtokid;	/* Request *Modified-Since tokid */
	time_t		lastmod;	/* Response Last-Modified: */
	str_t		accept;		/* Request Accept: */
	str_t		acceptchar;	/* Request Accept-Charset: */
	str_t		acceptenco;	/* Request Accept-Encoding: */
	str_t		acceptlang;	/* Request Accept-Language: */
	str_t		etag;		/* Request/Response ETag: */
	str_t		uagent;		/* Request User-Agent: */
} http_t;

static kmem_cache_t *http_kmc;

/*
 * HTTP date routines, dow[] for day of the week, Dow[] for day of the
 * week for the Unix epoch (i.e. day 0 is a Thu), months[] for the months
 * of the year, and dom[] for day number of the year for the first day
 * of each month (non leap year).
 */

static char *dow[] = {"sunday", "monday", "tuesday", "wednesday", "thursday",
	"friday", "saturday", 0};

static char *Dow[] = {"Thu", "Fri", "Sat", "Sun", "Mon", "Tue", "Wed", 0};

static char *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul",
	"Aug", "Sep", "Oct", "Nov", "Dec", 0};

static int dom[] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334};

/*
 * http_date2time_t(const char *) - returns the time(2) value (i.e.
 * the value 0 is Thu, 01 Jan 1970 00:00:00 GMT) for the following
 * time formats used by HTTP request and response headers:
 *
 *	1) Sun, 07 Dec 1998 14:49:37 GMT	; RFC 822, updated by RFC 1123
 *	2) Sunday, 07-Dec-98 14:49:37 GMT	; RFC 850, obsoleted by RFC 1036
 *	3) Sun Nov  7 14:49:37 1998		; ANSI C's asctime() format
 *	4) 60					; Time delta of N seconds
 *
 * On error a time_t value of -1 is returned.
 *
 * All dates are GMT (must be part of the date string for types
 * 1 and 2 and not for type 1).
 *
 * Note, the given mstr_t pointed to by *sp will be modified.
 */

static time_t
http_date2time_t(char *cp, char *ep)
{
	char	*scp = cp;
	time_t	secs;
	char	**tpp;
	char	*tp;
	char	c, sc;
	ssize_t	n;

	ssize_t	zeroleap = 1970 / 4 - 1970 / 100 + 1970 / 400;
	ssize_t	leap;
	ssize_t	year;
	ssize_t	month;
	ssize_t	day;
	ssize_t	hour;
	ssize_t	min;
	ssize_t	sec;

	/* Parse and skip day-of-week (we don't use it) */
	tpp = dow;
	tp = *tpp;
	n = 0;
	while (cp < ep) {
		c = *cp++;
		if (c == ',' || c == ' ')
			break;
		c = tolower(c);
		if (*tp == 0 || *tp != c) {
			cp = scp;
			if ((tp = *++tpp) == NULL)
				break;
			continue;
		}
		tp++;
	}
	if (cp == NULL) {
		/* Not case 1-3, try 4 */
		while (cp < ep) {
			c = *cp;
			if (isdigit(c)) {
				cp++;
				n *= 10;
				n += c - '0';
				continue;
			}
			/* An invalid date sytax */
			return (-1);
		}
		/* Case 4, delta from current time */
		return (gethrestime_sec() + n);
	}
	if (c == ',') {
		/* Case 1 or 2, skip <SP> */
		if (cp == ep)
			return (-1);
		c = *cp++;
		if (c != ' ')
			return (-1);
		/* Get day of the month */
		if (cp == ep)
			return (-1);
		c = *cp++;
		if (! isdigit(c))
			return (-1);
		n = c - '0';
		if (cp == ep)
			return (-1);
		c = *cp++;
		if (! isdigit(c))
			return (-1);
		n *= 10;
		n += c - '0';
		day = n;
		/* Get day/month/year seperator */
		if (cp == ep)
			return (-1);
		sc = *cp++;
		if (sc != ' ' && sc != '-')
			return (-1);
		/* Parse month */
		tpp = months;
		tp = *tpp++;
		scp = cp;
		n = 0;
		while (cp < ep) {
			c = *cp;
			if (c == sc) {
				cp++;
				break;
			}
			c = tolower(c);
			if (*tp == 0 || tolower(*tp) != c) {
				if ((tp = *tpp++) == NULL)
					break;
				cp = scp;
				n++;
				continue;
			}
			cp++;
			tp++;
		}
		if (cp == NULL)
			return (-1);
		month = n;
		/* Get year */
		if (cp == ep)
			return (-1);
		c = *cp++;
		if (! isdigit(c))
			return (-1);
		n = c - '0';
		if (cp == ep)
			return (-1);
		c = *cp++;
		if (! isdigit(c))
			return (-1);
		n *= 10;
		n += c - '0';
		if (cp == ep)
			return (-1);
		c = *cp++;
		if (sc == ' ') {
			/* Case 1, get 2 more year digits */
			if (! isdigit(c))
				return (-1);
			n *= 10;
			n += c - '0';
			if (cp == ep)
				return (-1);
			c = *cp++;
			if (! isdigit(c))
				return (-1);
			n *= 10;
			n += c - '0';
			/* Get seperator char */
			if (cp == ep)
				return (-1);
			c = *cp;
			if (c != ' ')
				return (-1);
			cp++;
		} else {
			/*
			 * Case 2, 2 digit year and as this is a so-called
			 * Unix date format and the begining of time was
			 * 1970 so we can extend this obsoleted date syntax
			 * past the year 1999 into the year 2038 for 32 bit
			 * machines and through 2069 for 64 bit machines.
			 */
			if (n > 69)
				n += 1900;
			else
				n += 2000;
		}
		year = n;
		/* Get GMT time */
		if (c != ' ')
			return (-1);
		if (cp == ep)
			return (-1);
		c = *cp++;
		if (! isdigit(c))
			return (-1);
		n = c - '0';
		if (cp == ep)
			return (-1);
		c = *cp++;
		if (! isdigit(c))
			return (-1);
		n *= 10;
		n += c - '0';
		hour = n;
		if (cp == ep)
			return (-1);
		c = *cp++;
		if (c != ':')
			return (-1);
		if (cp == ep)
			return (-1);
		c = *cp++;
		if (! isdigit(c))
			return (-1);
		n = c - '0';
		if (cp == ep)
			return (-1);
		c = *cp++;
		if (! isdigit(c))
			return (-1);
		n *= 10;
		n += c - '0';
		min = n;
		if (cp == ep)
			return (-1);
		c = *cp++;
		if (c != ':')
			return (-1);
		if (cp == ep)
			return (-1);
		c = *cp++;
		if (! isdigit(c))
			return (-1);
		n = c - '0';
		if (cp == ep)
			return (-1);
		c = *cp++;
		if (! isdigit(c))
			return (-1);
		n *= 10;
		n += c - '0';
		sec = n;
		if (cp == ep)
			return (-1);
		c = *cp++;
		if (c != ' ')
			return (-1);
		if (cp == ep)
			return (-1);
		c = *cp++;
		if (c != 'G')
			return (-1);
		if (cp == ep)
			return (-1);
		c = *cp++;
		if (c != 'M')
			return (-1);
		if (cp == ep)
			return (-1);
		c = *cp++;
		if (c != 'T')
			return (-1);
	} else {
		/* case 3, parse month */
		sc = c;
		tpp = months;
		tp = *tpp++;
		scp = cp;
		n = 0;
		while (cp < ep) {
			c = *cp;
			if (c == sc) {
				cp++;
				break;
			}
			c = tolower(c);
			if (*tp == 0 || tolower(*tp) != c) {
				if ((tp = *tpp++) == NULL)
					break;
				cp = scp;
				n++;
				continue;
			}
			cp++;
			tp++;
		}
		if (cp == NULL)
			return (-1);
		month = n;
		/* Get day of the month */
		if (cp == ep)
			return (-1);
		c = *cp++;
		if (! isdigit(c))
			return (-1);
		n = c - '0';
		if (cp == ep)
			return (-1);
		c = *cp++;
		if (! isdigit(c))
			return (-1);
		n *= 10;
		n += c - '0';
		day = n;
		/* Skip <SP> */
		if (cp == ep)
			return (-1);
		c = *cp++;
		if (c != ' ')
			return (-1);
		/* Get time */
		if (cp == ep)
			return (-1);
		c = *cp++;
		if (! isdigit(c))
			return (-1);
		n = c - '0';
		if (cp == ep)
			return (-1);
		c = *cp++;
		if (! isdigit(c))
			return (-1);
		n *= 10;
		n += c - '0';
		hour = n;
		if (cp == ep)
			return (-1);
		c = *cp++;
		if (c != ':')
			return (-1);
		if (cp == ep)
			return (-1);
		c = *cp++;
		if (! isdigit(c))
			return (-1);
		n = c - '0';
		if (cp == ep)
			return (-1);
		c = *cp++;
		if (! isdigit(c))
			return (-1);
		n *= 10;
		n += c - '0';
		min = n;
		if (cp == ep)
			return (-1);
		c = *cp++;
		if (c != ':')
			return (-1);
		if (cp == ep)
			return (-1);
		c = *cp++;
		if (! isdigit(c))
			return (-1);
		n = c - '0';
		if (cp == ep)
			return (-1);
		c = *cp++;
		if (! isdigit(c))
			return (-1);
		n *= 10;
		n += c - '0';
		sec = n;
		/* Skip <SP> */
		if (cp == ep)
			return (-1);
		c = *cp++;
		if (c != ' ')
			return (-1);
		/* Get year */
		if (cp == ep)
			return (-1);
		c = *cp++;
		if (! isdigit(c))
			return (-1);
		n = c - '0';
		if (cp == ep)
			return (-1);
		c = *cp++;
		if (! isdigit(c))
			return (-1);
		n *= 10;
		n += c - '0';
		if (cp == ep)
			return (-1);
		c = *cp++;
		if (! isdigit(c))
			return (-1);
		n *= 10;
		n += c - '0';
		if (cp == ep)
			return (-1);
		c = *cp++;
		if (! isdigit(c))
			return (-1);
		n *= 10;
		n += c - '0';
		year = n;
	}

	/* Last, caclulate seconds since Unix day zero */
	leap = year;
	if (month < 2)
		leap--;
	leap = leap / 4 - leap / 100 + leap / 400 - zeroleap;
	secs = ((((year - 1970) * 365 + dom[month] + day  - 1 + leap) * 24
	    + hour) * 60 + min) * 60 + sec;

	return (secs);
}

/*
 * http_today(char *) - returns in the given char* pointer the current
 * date in ascii with a format of (char [29]):
 *
 *	Sun, 07 Dec 1998 14:49:37 GMT	; RFC 822, updated by RFC 1123
 */

static void
http_today(char *cp)
{
	ssize_t	i;
	char	*fp;

	ssize_t	leap;
	ssize_t	year;
	ssize_t	month;
	ssize_t	dow;
	ssize_t	day;
	ssize_t	hour;
	ssize_t	min;
	ssize_t	sec;

	/* Secs since Thu, 01 Jan 1970 00:00:00 GMT */
	time_t	now = gethrestime_sec();

	sec = now % 60;
	now /= 60;
	min = now % 60;
	now /= 60;
	hour = now % 24;
	now /= 24;
	dow = now % 7;

	year = 1970;
	for (;;) {
		if (year % 4 == 0 && year % 100 != 0 || year % 400 == 0)
			day = 366;
		else
			day = 365;
		if (now < day)
			break;
		now -= day;
		year++;
	}

	now++;
	if (year % 4 == 0 && year % 100 != 0 || year % 400 == 0)
		leap = 1;
	else
		leap = 0;
	month = 11;
	for (i = 11; i; i--) {
		if (i < 2)
			leap = 0;
		if (now > dom[i] + leap)
			break;
		month--;
	}
	day = now - dom[i] - leap;

	fp = Dow[dow];
	*cp++ = *fp++;
	*cp++ = *fp++;
	*cp++ = *fp++;
	*cp++ = ',';
	*cp++ = ' ';

	i = day / 10;
	*cp++ = '0' + i;
	*cp++ = '0' + (day - i * 10);
	*cp++ = ' ';

	fp = months[month];
	*cp++ = *fp++;
	*cp++ = *fp++;
	*cp++ = *fp++;
	*cp++ = ' ';

	i = year / 1000;
	*cp++ = '0' + i;
	year -= i * 1000;
	i = year / 100;
	*cp++ = '0' + i;
	year -= i * 100;
	i = year / 10;
	*cp++ = '0' + i;
	year -= i * 10;
	*cp++ = '0' + year;
	*cp++ = ' ';

	i = hour / 10;
	*cp++ = '0' + i;
	*cp++ = '0' + (hour - i * 10);
	*cp++ = ':';

	i = min / 10;
	*cp++ = '0' + i;
	*cp++ = '0' + (min - i * 10);
	*cp++ = ':';

	i = sec / 10;
	*cp++ = '0' + i;
	*cp++ = '0' + (sec - i * 10);
	*cp++ = ' ';

	*cp++ = 'G';
	*cp++ = 'M';
	*cp = 'T';
}

/*
 * Given the ttree_t pointer "*t", parse the char buffer pointed to
 * by "**cpp" of multiline text data up to the pointer "**epp", the
 * pointer "*hash" points to the current text hash.
 *
 * If a match is found a pointer to the ttree_t token will be returned,
 * "**cpp" will point to the next line, "**epp" will point to the first
 * EOL char, "**hpp" will point to remainder of the parse data (if none,
 * **hpp == **epp), and "*hash" will be updated.
 *
 * If no match, as above except "**hpp" points to the begining of the
 * line and "*hash" wont be updated.
 *
 * If no EOL is found NULL is returned, "**epp" is set to NULL, no further
 * calls can be made until additional data is ready and all arguments are
 * reset.
 *
 * If EOH (i.e. an empty line) NULL is returned, "**hpp" is set to NULL,
 * *cpp points to past EOH, no further calls can be made.
 */

static token_t *
ttree_line_parse(ttree_t *t, char **cpp, char **epp, char **hpp, uint32_t *hash)
{
	char	ca, cb;			/* current line <=> parse node */

	char	*cp = *cpp;
	char	*ep = *epp;

	char	*tp = t->tok->text;	/* current parse text */
	char	*sp = cp;		/* saved *cp */

	int	parse;			/* parse state */

	uint32_t hv;			/* hash value */

	if (hash != NULL)
		hv = *hash;

	/* Special case, check for EOH (i.e. empty line) */
	if (cp < ep) {
		ca = *cp;
		if (ca == '\n') {
			/* End of header */
			*cpp = ++cp;
			*hpp = NULL;
			return (NULL);
		} else if (ca == '\r') {
			cp++;
			if (cp < ep) {
				ca = *cp;
				if (ca == '\n') {
					/* End of header */
					*cpp = ++cp;
					*hpp = NULL;
					return (NULL);
				}
			}
			cp = *cpp;
		}
	}
	while (cp < ep) {
		/* Get next parse text char */
		cb = *tp;
		if (cb != 0) {
			/* Get next current line char */
			ca = *cp++;
			/* Case insensitive */
			cb = tolower(cb);
			ca = tolower(ca);
			if (ca == cb) {
				/*
				 * Char match, next char.
				 *
				 * Note, parse text can contain EOL chars.
				 */
				tp++;
				continue;
			}
			if (ca == '\r' || ca == '\n') {
				/* EOL, always go less than */
				t = t->lt;
			} else if (ca < cb) {
				/* Go less than */
				t = t->lt;
			} else {
				/* Go greater than */
				t = t->gt;
			}
			while (t != NULL && t->tok == NULL) {
				/* Null node, so descend to < node */
				t = t->lt;
			}
			if (t != NULL) {
				/* Initialize for next node compare */
				tp = t->tok->text;
				cp = sp;
				continue;
			}
			/*
			 * End of tree walk, no match, return pointer
			 * to the start of line then below find EOL.
			 */
			*hpp = *cpp;
		} else {
			/*
			 * End of token text, match, return pointer to
			 * the rest of header text then below find EOL.
			 */
			*hpp = cp;
		}
		/*
		 * Find end of line. Note, the HTTP line syntax supports
		 * implicit multi-line if the next line starts with a <SP>
		 * or <HT>.
		 */
		parse = 0;
		while (cp < ep) {
			ca = *cp;
			if (parse == 0 && ca == '\r') {
				*epp = cp;
				parse = 1;
			} else if (parse == 0 && ca == '\n') {
				*epp = cp;
				parse = 2;
			} else if (parse == 1 && ca == '\n') {
				parse = 2;
			} else if (parse >= 2 && (ca == ' ' || ca == '\t')) {
				parse++;
			} else if (parse > 2) {
				parse = 0;
			} else if (parse == 2) {
				break;
			} else if (t != NULL && (t->tok->act & HASH) &&
			    hash != NULL) {
				CHASH(hv, ca);
			}
			cp++;
		}
		if (parse < 2) {
			/* No EOL, not enough data */
			*epp = NULL;
			return (t != NULL ? t->tok : NULL);
		}
		/*
		 * Return updated hash value (if any), update parse current
		 * pointer for next call (i.e. begin of next line), and last
		 * return pointer to the matching token_t.
		 */
		if (t != NULL && (t->tok->act & HASH) && hash != NULL)
			*hash = hv;
		*cpp = cp;
		return (t != NULL ? t->tok : NULL);
	}
	/*
	 * End of parse text, ...
	 */
	*epp = NULL;
	return (NULL);
}

/*
 * Given a NULL terminated array of token_t(s) ordered in ascending
 * case insensitive order a binary tree is allocated and populated with
 * pointers into the array and a pointer to the root node is returned.
 *
 * Todo, for maximum ttree parse efficiency needs to be path compressed,
 * the function ttree_line_parse() handles the empty nodes correctly.
 */
static ttree_t *
ttree_build(token_t *list, int sz)
{
	ttree_t *treev;
	int	max, lvl, inc, ix;

	/* calc the size of the tree */
	for (max = 1; max < sz; max <<= 1)
		;
	/* allocate the tree */
	treev = kmem_alloc(sizeof (*treev) * (max - 1), KM_SLEEP);

	/* walk the tree and populate from list vector */
	lvl = max;
	while (lvl >>= 1) {
		inc = lvl >> 1;
		for (ix = lvl; ix < max; ix += lvl << 1) {
			if (ix <= sz) {
				treev[ix - 1].tok = &list[ix - 1];
			} else {
				treev[ix - 1].tok = 0;
			}
			if (inc) {
				treev[ix - 1].lt = &treev[ix - inc - 1];
				treev[ix - 1].gt = &treev[ix + inc - 1];
			} else {
				treev[ix - 1].lt = 0;
				treev[ix - 1].gt = 0;
			}
		}
	}

	return (&treev[(max >> 1) - 1]);
}

void
nl7c_http_init(void)
{
	int	n;

	http_kmc = kmem_cache_create("NL7C_http_kmc",
	    sizeof (http_t), 0, NULL, NULL, NULL, NULL, NULL, 0);

	req_tree = ttree_build(tokreq, tokreq_cnt - 1);
	res_tree = ttree_build(tokres, tokres_cnt - 1);

	n = sizeof (Shttp_conn_cl) - 1;
	http_conn_cl = allocb_wait(n, BPRI_HI, STR_NOSIG, NULL);
	bcopy(Shttp_conn_cl, http_conn_cl->b_rptr, n);
	http_conn_cl->b_wptr += n;

	n = sizeof (Shttp_conn_ka) - 1;
	http_conn_ka = allocb_wait(n, BPRI_HI, STR_NOSIG, NULL);
	bcopy(Shttp_conn_ka, http_conn_ka->b_rptr, n);
	http_conn_ka->b_wptr += n;
}

void
nl7c_http_free(void *arg)
{
	http_t	*http = arg;

	kmem_cache_free(http_kmc, http);
}

#define	STR_T_NOTCMP_OPT(a, b, m) (					\
    a->m.cp && b->m.cp &&						\
	((a->m.ep - a->m.cp) != (b->m.ep - b->m.cp) ||			\
	strncmp(a->m.cp, b->m.cp, (b->m.ep - b->m.cp))))

#define	STR_T_NOTCMP(a, b, m) (						\
    a->m.cp && ! b->m.cp ||						\
    b->m.cp && ! a->m.cp ||						\
    STR_T_NOTCMP_OPT(a, b, m))

boolean_t
nl7c_http_cmp(void *arg1, void *arg2)
{
	http_t	*httpa = arg1;		/* Response */
	http_t	*httpb = arg2;		/* Request */

	if (httpa->major != httpb->major ||
	    httpa->minor != httpb->minor ||
	    STR_T_NOTCMP(httpa, httpb, accept) ||
	    STR_T_NOTCMP(httpa, httpb, acceptchar) ||
	    STR_T_NOTCMP(httpa, httpb, acceptenco) ||
	    STR_T_NOTCMP(httpa, httpb, acceptlang) ||
	    STR_T_NOTCMP_OPT(httpa, httpb, etag))
		return (B_FALSE);
	return (B_TRUE);
}

/*
 * In-line HTTP responses:
 */

static char http_resp_304[] =
	"HTTP/#.# 304 Not Modified\r\n"
	"Date: #############################\r\n"
	"Server: NCA/#.# (Solaris)\r\n";

static char http_resp_412[] =
	"HTTP/#.# 412 Precondition Failed\r\n"
	"Date: #############################\r\n"
	"Server: NCA/#.# (Solaris)\r\n";

static uri_desc_t *
http_mkresponse(uri_desc_t *req, uri_desc_t *res, char *proto, int sz)
{
	http_t		*qhttp = req->scheme;
	http_t		*shttp = res->scheme;
	uri_desc_t	*uri = kmem_cache_alloc(nl7c_uri_kmc, KM_SLEEP);
	char		*alloc;
	char		*cp;
	char		*ep = &proto[sz];
	uri_rd_t	*rdp;
	int		cnt;

	char		hdr_etag[] = "ETag: ";

	/* Any optional header(s) */
	if (shttp->etag.cp != NULL) {
		/* Response has an ETag:, count it */
		sz += sizeof (hdr_etag) - 1 +
		    (shttp->etag.ep - shttp->etag.cp) + 2;
	}
	sz += 2;
	alloc = kmem_alloc(sz, KM_SLEEP);

	/* Minimum temp uri initialization as needed by uri_response() */
	REF_INIT(uri, 1, nl7c_uri_inactive, nl7c_uri_kmc);
	uri->hash = URI_TEMP;
	uri->tail = NULL;
	uri->scheme = NULL;
	uri->reqmp = NULL;
	uri->count = 0;
	cv_init(&uri->waiting, NULL, CV_DEFAULT, NULL);
	mutex_init(&uri->proclock, NULL, MUTEX_DEFAULT, NULL);

	URI_RD_ADD(uri, rdp, sz, -1);
	rdp->data.kmem = alloc;
	atomic_add_64(&nl7c_uri_bytes, sz);

	cp = alloc;
	if (qhttp->major == 1) {
		/*
		 * Full response format.
		 *
		 * Copy to first sub char '#'.
		 */
		while (proto < ep) {
			if (*proto == '#')
				break;
			*cp++ = *proto++;
		}

		/* Process the HTTP version substitutions */
		if (*proto != '#') goto bad;
		*cp++ = '0' + qhttp->major;
		proto++;
		while (proto < ep) {
			if (*proto == '#')
				break;
			*cp++ = *proto++;
		}
		if (*proto != '#') goto bad;
		*cp++ = '0' + qhttp->minor;
		proto++;

		/* Copy to the next sub char '#' */
		while (proto < ep) {
			if (*proto == '#')
				break;
			*cp++ = *proto++;
		}

		/* Process the "Date: " substitution */
		if (*proto != '#') goto bad;
		http_today(cp);

		/* Skip to the next nonsub char '#' */
		while (proto < ep) {
			if (*proto != '#')
				break;
			cp++;
			proto++;
		}

		/* Copy to the next sub char '#' */
		while (proto < ep) {
			if (*proto == '#')
				break;
			*cp++ = *proto++;
		}

		/* Process the NCA version substitutions */
		if (*proto != '#') goto bad;
		*cp++ = '0' + nca_major_version;
		proto++;
		while (proto < ep) {
			if (*proto == '#')
				break;
			*cp++ = *proto++;
		}
		if (*proto != '#') goto bad;
		*cp++ = '0' + nca_minor_version;
		proto++;

		/* Copy remainder of HTTP header */
		while (proto < ep) {
			*cp++ = *proto++;
		}
	} else {
		goto bad;
	}
	/* Any optional header(s) */
	if (shttp->etag.cp != NULL) {
		/* Response has an ETag:, add it */
		cnt = sizeof (hdr_etag) - 1;
		bcopy(hdr_etag, cp, cnt);
		cp += cnt;
		cnt = (shttp->etag.ep - shttp->etag.cp);
		bcopy(shttp->etag.cp, cp, cnt);
		cp += cnt;
		*cp++ = '\r';
		*cp++ = '\n';
	}
	/* Last, add empty line */
	uri->eoh = cp;
	*cp++ = '\r';
	*cp = '\n';

	return (uri);

bad:
	/*
	 * Free any resources allocated here, note that while we could
	 * use the uri_inactive() to free the uri by doing a REF_RELE()
	 * we instead free it here as the URI may be in less then a fully
	 * initialized state.
	 */
	kmem_free(alloc, sz);
	kmem_cache_free(nl7c_uri_kmc, uri);
	return (NULL);
}

uri_desc_t *
nl7c_http_cond(uri_desc_t *req, uri_desc_t *res)
{
	http_t	*qhttp = req->scheme;
	time_t	qdate = qhttp->moddate;
	http_t	*shttp = res->scheme;
	time_t	sdate = shttp->lastmod == -1 ? shttp->date : shttp->lastmod;
	uri_desc_t *uri;

	if (qhttp->modtokid == Qhdr_If_Modified_Since &&
	    sdate != -1 && qdate != -1 && sdate <= qdate) {
		/*
		 * Request is If-Modified-Since: and both response
		 * and request dates are valid and response is the
		 * same age as request so return a 304 response uri
		 * instead of the cached response.
		 */
		nl7c_http_cond_304++;
		uri = http_mkresponse(req, res, http_resp_304,
		    sizeof (http_resp_304) - 1);
		if (uri != NULL) {
			/* New response uri */
			REF_RELE(res);
			return (uri);
		}
		return (res);
	} else if (qhttp->modtokid == Qhdr_If_Unmodified_Since &&
	    sdate != -1 && qdate != -1 && sdate >= qdate) {
		/*
		 * Request is If-Unmodified-Since: and both response
		 * and request dates are valid and response is not the
		 * same age as the request so return a 412 response
		 * uri instead of the cached response.
		 */
		nl7c_http_cond_412++;
		uri = http_mkresponse(req, res, http_resp_412,
		    sizeof (http_resp_412) - 1);
		if (uri != NULL) {
			/* New response uri */
			REF_RELE(res);
			return (uri);
		}
		return (res);
	}
	/*
	 * No conditional response meet or unknown type or no
	 * valid dates so just return the original uri response.
	 */
	return (res);
}

/*
 * Return the appropriate HTTP connection persist header
 * based on the request HTTP persistent header state.
 */

mblk_t *
nl7c_http_persist(struct sonode *so)
{
	uint64_t	flags = SOTOTPI(so)->sti_nl7c_flags & NL7C_SCHEMEPRIV;
	mblk_t		*mp;

	if (flags & HTTP_CONN_CL)
		mp = dupb(http_conn_cl);
	else if (flags & HTTP_CONN_KA)
		mp = dupb(http_conn_ka);
	else
		mp = NULL;
	return (mp);
}

/*
 * Parse the buffer *p of size len and update the uri_desc_t *uri and our
 * http_t *http with the results.
 */

boolean_t
nl7c_http_request(char **cpp, char *ep, uri_desc_t *uri, struct sonode *so)
{
	sotpi_info_t *sti = SOTOTPI(so);
	http_t	*http = kmem_cache_alloc(http_kmc, KM_SLEEP);
	char	*cp = *cpp;
	char	*hp;
	char	*scp, *sep;
	char	*HTTP = "HTTP/";
	token_t	*match;
	boolean_t persist = B_FALSE;

	ASSERT(cp <= ep);

	if (cp == ep) {
		goto bad;
	}
	/*
	 * Initialize any uri_desc_t and/or http_t members.
	 */
	uri->scheme = (void *)http;
	uri->auth.cp = NULL;
	uri->auth.ep = NULL;
	uri->resplen = URI_LEN_NOVALUE;
	uri->respclen = URI_LEN_NOVALUE;
	uri->eoh = NULL;
	uri->nocache = B_FALSE;
	uri->conditional = B_FALSE;
	http->parsed = B_FALSE;
	http->accept.cp = NULL;
	http->acceptchar.cp = NULL;
	http->acceptenco.cp = NULL;
	http->acceptlang.cp = NULL;
	http->etag.cp = NULL;
	http->uagent.cp = NULL;
	http->date = -1;
	http->expire = -1;
	http->lastmod = -1;
	if (*cp == '\r') {
		/*
		 * Special case for a Request-Line without an HTTP version,
		 * assume it's an old style, i.e. HTTP version 0.9 request.
		 */
		http->major = 0;
		http->minor = 9;
		goto got_version;
	}
	/*
	 * Skip URI path delimiter, must be a <SP>.
	 */
	if (*cp++ != ' ')
		/* Unkown or bad Request-Line format, just punt */
		goto bad;
	/*
	 * The URI parser has parsed through the URI and the <SP>
	 * delimiter, parse the HTTP/N.N version
	 */
	while (cp < ep && *HTTP == *cp) {
		HTTP++;
		cp++;
	}
	if (*HTTP != 0) {
		if (cp == ep)
			goto more;
		goto bad;
	}
	if (cp == ep)
		goto more;
	if (*cp < '0' || *cp > '9')
		goto bad;
	http->major = *cp++ - '0';
	if (cp == ep)
		goto more;
	if (*cp++ != '.')
		goto bad;
	if (cp == ep)
		goto more;
	if (*cp < '0' || *cp > '9')
		goto bad;
	http->minor = *cp++ - '0';
	if (cp == ep)
		goto more;

got_version:

	if (*cp++ != '\r')
		goto bad;
	if (cp == ep)
		goto more;
	if (*cp++ != '\n')
		goto bad;
	/*
	 * Initialize persistent state based on HTTP version.
	 */
	if (http->major == 1) {
		if (http->minor >= 1) {
			/* 1.1 persistent by default */
			persist = B_TRUE;
		} else {
			/* 1.0 isn't persistent by default */
			persist = B_FALSE;
		}
	} else if (http->major == 0) {
		/* Before 1.0 no persistent connections */
		persist = B_FALSE;
	} else {
		/* >= 2.0 not supported (yet) */
		goto bad;
	}
	/*
	 * Parse HTTP headers through the EOH
	 * (End Of Header, i.e. an empty line).
	 */
	for (sep = ep; cp < ep; ep = sep) {
		/* Get the next line */
		scp = cp;
		match = ttree_line_parse(req_tree, &cp, &ep, &hp, &uri->hvalue);
		if (match != NULL) {
			if (match->act & QUALIFIER) {
				/*
				 * Header field text is used to qualify this
				 * request/response, based on qualifier type
				 * optionally convert and store *http.
				 */
				char	c;
				int	n = 0;
				time_t	secs;

				ASSERT(hp != NULL && ep != NULL);

				if (match->act & NUMERIC) {
					while (hp < ep) {
						c = *hp++;
						if (! isdigit(c))
							goto bad;
						n *= 10;
						n += c - '0';
					}
				} else if (match->act & DATE) {
					secs = http_date2time_t(hp, ep);
				}
				switch (match->tokid) {

				case Qhdr_Accept_Charset:
					http->acceptchar.cp = hp;
					http->acceptchar.ep = ep;
					break;

				case Qhdr_Accept_Encoding:
					http->acceptenco.cp = hp;
					http->acceptenco.ep = ep;
					break;

				case Qhdr_Accept_Language:
					http->acceptlang.cp = hp;
					http->acceptlang.ep = ep;
					break;

				case Qhdr_Accept:
					http->accept.cp = hp;
					http->accept.ep = ep;
					break;

				case Qhdr_Authorization:
					goto pass;

				case Qhdr_Connection_close:
					persist = B_FALSE;
					break;

				case Qhdr_Connection_Keep_Alive:
					persist = B_TRUE;
					break;

				case Qhdr_Date:
					http->date = secs;
					break;

				case Qhdr_ETag:
					http->etag.cp = hp;
					http->etag.ep = ep;
					break;

				case Qhdr_Host:
					uri->auth.cp = hp;
					uri->auth.ep = ep;
					break;

				case Qhdr_If_Modified_Since:
				case Qhdr_If_Unmodified_Since:
					http->moddate = secs;
					http->modtokid = match->tokid;
					uri->conditional = B_TRUE;
					break;

				case Qhdr_Keep_Alive:
					persist = B_TRUE;
					break;

				case Qhdr_User_Agent:
					http->uagent.cp = hp;
					http->uagent.ep = ep;
					break;

				default:
					break;

				};
			}
			if (match->act & FILTER) {
				/*
				 * Filter header, do a copyover the header
				 * text, guarenteed to be at least 1 byte.
				 */
				char	*cop = scp;
				int	n = (ep - cop) - 1;
				char	filter[] = "NL7C-Filtered";

				n = MIN(n, sizeof (filter) - 1);
				if (n > 0)
					bcopy(filter, cop, n);
				cop += n;
				ASSERT(cop < ep);
				*cop++ = ':';
				while (cop < ep)
					*cop++ = ' ';
			}
			if (match->act & NOCACHE) {
				uri->nocache = B_TRUE;
			}
		} else if (hp == NULL) {
			goto done;
		} else if (ep == NULL) {
			goto more;
		}
	}
	/* No EOH found */
	goto more;

done:
	/*
	 * Initialize socket persist state and response persist type
	 * flag based on the persist state of the request headers.
	 *
	 */
	if (persist)
		sti->sti_nl7c_flags |= NL7C_SOPERSIST;
	else
		sti->sti_nl7c_flags &= ~NL7C_SOPERSIST;

	if (http->major == 1) {
		sti->sti_nl7c_flags &= ~NL7C_SCHEMEPRIV;
		if (http->minor >= 1) {
			if (! persist)
				sti->sti_nl7c_flags |= HTTP_CONN_CL;
		} else {
			if (persist)
				sti->sti_nl7c_flags |= HTTP_CONN_KA;
			else
				sti->sti_nl7c_flags |= HTTP_CONN_CL;
		}
	}
	/*
	 * Last, update parse consumed text pointer.
	 */
	*cpp = cp;
	return (B_TRUE);

pass:
	*cpp = NULL;
	return (B_TRUE);

bad:
	*cpp = NULL;
more:
	return (B_FALSE);
}

boolean_t
nl7c_http_response(char **cpp, char *ep, uri_desc_t *uri, struct sonode *so)
{
	sotpi_info_t *sti = SOTOTPI(so);
	http_t	*http = uri->scheme;
	char	*cp = *cpp;
	char	*hp;
	char	*scp, *sep;
	char	*HTTP = "HTTP/";
	int	status = 0;
	token_t	*match;
#ifdef	NOT_YET
	uint32_t major, minor;
#endif
	boolean_t nocache = B_FALSE;
	boolean_t persist = B_FALSE;

	ASSERT(http != NULL);

	if (http->parsed) {
		if (uri->respclen != URI_LEN_NOVALUE) {
			/* Chunked response */
			sep = ep;
			goto chunked;
		}
		/* Already parsed, nothing todo */
		return (B_TRUE);
	}

	/*
	 * Parse the HTTP/N.N version. Note, there's currently no use
	 * for the actual response major nor minor values as only the
	 * request values are used.
	 */
	while (cp < ep && *HTTP == *cp) {
		HTTP++;
		cp++;
	}
	if (*HTTP != 0) {
		if (cp == ep)
			goto more;
		goto bad;
	}
	if (cp == ep)
		goto more;

	if (*cp < '0' || *cp > '9')
		goto bad;
#ifdef	NOT_YET
	major = *cp++ - '0';
#else
	cp++;
#endif

	if (cp == ep)
		goto more;
	if (*cp++ != '.')
		goto bad;
	if (cp == ep)
		goto more;
	if (*cp < '0' || *cp > '9')
		goto bad;
#ifdef	NOT_YET
	minor = *cp++ - '0';
#else
	cp++;
#endif

	if (cp == ep)
		goto more;

got_version:

	/*
	 * Get the response code.
	 */
	if (*cp++ != ' ')
		goto bad;
	if (cp == ep)
		goto more;

	do {
		if (*cp == ' ')
			break;
		if (*cp < '0' || *cp > '9')
			goto bad;
		if (status)
			status *= 10;
		status += *cp++ - '0';
	} while (cp < ep);

	switch (status) {
	case 200:
		/*
		 * The only response status we continue to process.
		 */
		break;
	case 304:
		nl7c_http_response_304++;
		nocache = B_TRUE;
		uri->resplen = 0;
		goto pass;
	case 307:
		nl7c_http_response_307++;
		nocache = B_TRUE;
		uri->resplen = 0;
		goto pass;
	case 400:
		nl7c_http_response_400++;
		/*
		 * Special case some response status codes, just mark
		 * as nocache and no response length and pass on the
		 * request/connection.
		 */
		nocache = B_TRUE;
		uri->resplen = 0;
		goto pass;
	default:
		/*
		 * All other response codes result in a parse failure.
		 */
		goto bad;
	}

	/*
	 * Initialize persistent state based on request HTTP version.
	 */
	if (http->major == 1) {
		if (http->minor >= 1) {
			/* 1.1 persistent by default */
			persist = B_TRUE;
		} else {
			/* 1.0 isn't persistent by default */
			persist = B_FALSE;
		}
	} else if (http->major == 0) {
		/* Before 1.0 no persistent connections */
		persist = B_FALSE;
	} else {
		/* >= 2.0 not supported (yet) */
		goto bad;
	}

	/*
	 * Parse HTTP headers through the EOH
	 * (End Of Header, i.e. an empty line).
	 */
	for (sep = ep; cp < ep; ep = sep) {
		/* Get the next line */
		scp = cp;
		match = ttree_line_parse(res_tree, &cp, &ep, &hp, NULL);
		if (match != NULL) {
			if (match->act & QUALIFIER) {
				/*
				 * Header field text is used to qualify this
				 * request/response, based on qualifier type
				 * optionally convert and store *http.
				 */
				char	c;
				int	n = 0;
				time_t	secs;

				ASSERT(hp != NULL && ep != NULL);

				if (match->act & NUMERIC) {
					while (hp < ep) {
						c = *hp++;
						if (match->act & HEX) {
							hd2i(c, n);
							if (n == -1)
								goto bad;
						} else {
							if (! isdigit(c))
								goto bad;
							n *= 10;
							n += c - '0';
						}
					}
				} else if (match->act & DATE) {
					secs = http_date2time_t(hp, ep);
				}
				switch (match->tokid) {

				case Shdr_Cache_Control_Max_Age:
					break;

				case Shdr_Cache_Control_No_Cache:
					nocache = B_TRUE;
					break;

				case Shdr_Cache_Control_No_Store:
					nocache = B_TRUE;
					break;

				case Shdr_Connection_close:
					persist = B_FALSE;
					break;

				case Shdr_Connection_Keep_Alive:
					persist = B_TRUE;
					break;

				case Shdr_Chunked:
					uri->respclen = 0;
					uri->resplen = 0;
					nl7c_http_response_chunked++;
					break;

				case Shdr_Content_Length:
					if (uri->respclen == URI_LEN_NOVALUE)
						uri->resplen = n;
					break;

				case Shdr_Date:
					http->date = secs;
					break;

				case Shdr_ETag:
					http->etag.cp = hp;
					http->etag.ep = ep;
					break;

				case Shdr_Expires:
					http->expire = secs;
					break;

				case Shdr_Keep_Alive:
					persist = B_TRUE;
					break;

				case Shdr_Last_Modified:
					http->lastmod = secs;
					break;

				case Shdr_Set_Cookie:
					nocache = B_TRUE;
					break;

				case Shdr_Server:
					break;

				default:
					nocache = B_TRUE;
					break;
				};
			}
			if (match->act & FILTER) {
				/*
				 * Filter header, do a copyover the header
				 * text, guarenteed to be at least 1 byte.
				 */
				char	*cop = scp;
				int	n = (ep - cop) - 1;
				char	filter[] = "NL7C-Filtered";

				n = MIN(n, sizeof (filter) - 1);
				if (n > 0)
					bcopy(filter, cop, n);
				cop += n;
				ASSERT(cop < ep);
				*cop++ = ':';
				while (cop < ep)
					*cop++ = ' ';
			}
			if (match->act & NOCACHE) {
				nocache = B_TRUE;
			}
		} else if (hp == NULL) {
			uri->eoh = scp;
			goto done;
		} else if (ep == NULL) {
			goto more;
		}
	}
	/* No EOH found */
	goto more;

done:
	/* Parse completed */
	http->parsed = B_TRUE;
	/* Save the HTTP header length */
	http->headlen = (cp - *cpp);
	if (uri->respclen == URI_LEN_NOVALUE) {
		if (uri->resplen == URI_LEN_NOVALUE) {
			nl7c_http_response_pass1++;
			goto pass;
		}
	}
	/* Add header length to URI response length */
	uri->resplen += http->headlen;

	/* Set socket persist state */
	if (persist)
		sti->sti_nl7c_flags |= NL7C_SOPERSIST;
	else
		sti->sti_nl7c_flags &= ~NL7C_SOPERSIST;

	if (http->major == 1) {
		sti->sti_nl7c_flags &= ~NL7C_SCHEMEPRIV;
		if (http->minor >= 1) {
			if (! persist)
				sti->sti_nl7c_flags |= HTTP_CONN_CL;
		} else {
			if (persist)
				sti->sti_nl7c_flags |= HTTP_CONN_KA;
			else
				sti->sti_nl7c_flags |= HTTP_CONN_CL;
		}
	}

	if (nocache) {
		/*
		 * Response not to be cached, only post response
		 * processing code common to both non and cached
		 * cases above here and code for the cached case
		 * below.
		 *
		 * Note, chunked transfer processing is the last
		 * to be done.
		 */
		uri->nocache = B_TRUE;
		if (uri->respclen != URI_LEN_NOVALUE) {
			/* Chunked response */
			goto chunked;
		}
		/* Nothing more todo */
		goto parsed;
	}

	if (http->expire != -1 && http->date != -1) {
		if (http->expire <= http->date) {
			/* ??? just pass */
			nl7c_http_response_pass2++;
			goto pass;
		}
		/* Have a valid expire and date so calc an lbolt expire */
		uri->expire = ddi_get_lbolt() + SEC_TO_TICK(http->expire -
		    http->date);
	} else if (nl7c_uri_ttl != -1) {
		/* No valid expire speced and we have a TTL */
		uri->expire = ddi_get_lbolt() + SEC_TO_TICK(nl7c_uri_ttl);
	}

chunked:
	/*
	 * Chunk transfer parser and processing, a very simple parser
	 * is implemented here for the common case were one, or more,
	 * complete chunk(s) are passed in (i.e. length header + body).
	 *
	 * All other cases are passed.
	 */
	scp = cp;
	while (uri->respclen != URI_LEN_NOVALUE && cp < sep) {
		if (uri->respclen == URI_LEN_CONSUMED) {
			/* Skip trailing "\r\n" */
			if (cp == sep)
				goto more;
			if (*cp++ != '\r')
				goto bad;
			if (cp == sep)
				goto more;
			if (*cp++ != '\n')
				goto bad;
			uri->respclen = 0;
		}
		if (uri->respclen == 0) {
			/* Parse a chunklen "[0-9A-Fa-f]+" */
			char	c;
			int	n = 0;

			if (cp == sep)
				goto more;
			nl7c_http_response_chunkparse++;
			while (cp < sep && (c = *cp++) != '\r') {
				hd2i(c, n);
				if (n == -1)
					goto bad;
			}
			if (cp == sep)
				goto more;
			if (*cp++ != '\n')
				goto bad;
			uri->respclen = n;
			if (n == 0) {
				/* Last chunk, skip trailing "\r\n" */
				if (cp == sep)
					goto more;
				if (*cp++ != '\r')
					goto bad;
				if (cp == sep)
					goto more;
				if (*cp++ != '\n')
					goto bad;
				uri->respclen = URI_LEN_NOVALUE;
				break;
			}
		}
		if (uri->respclen > 0) {
			/* Consume some bytes for the current chunk */
			uint32_t sz = (sep - cp);

			if (sz > uri->respclen)
				sz = uri->respclen;
			uri->respclen -= sz;
			cp += sz;
			if (uri->respclen == 0) {
				/* End of chunk, skip trailing "\r\n" */
				if (cp == sep) {
					uri->respclen = URI_LEN_CONSUMED;
					goto more;
				}
				if (*cp++ != '\r')
					goto bad;
				if (cp == sep)
					goto more;
				if (*cp++ != '\n')
					goto bad;
				if (cp == sep)
					goto more;
			}
		}
	}
	uri->resplen += (cp - scp);

parsed:
	*cpp = cp;
	return (B_TRUE);

pass:
	*cpp = NULL;
	return (B_TRUE);

bad:
	*cpp = NULL;
	return (B_FALSE);

more:
	uri->resplen += (cp - scp);
	*cpp = cp;
	return (B_FALSE);
}

boolean_t
nl7c_http_log(uri_desc_t *quri, uri_desc_t *suri, nca_request_log_t *req,
    char **wp, char **pep, uint32_t *off)
{
	http_t	*qhttp = quri->scheme;
	http_t	*shttp = suri->scheme;
	int	sz;

	if (qhttp->uagent.cp != NULL) {
		sz = (qhttp->uagent.ep - qhttp->uagent.cp);
		if ((*wp + sz + 1) >= *pep) goto full;
		bcopy(qhttp->uagent.cp, *wp, sz);
		*wp += sz;
		*(*wp)++ = 0;
		sz++;
		req->useragent_len = sz;
		req->useragent = *off;
		*off += sz;
	}

	req->response_len -= (uint_t)shttp->headlen;

	req->method = NCA_GET;

	if (qhttp->major == 1) {
		if (qhttp->minor == 0) {
			req->version = HTTP_1_0;
		} else if (qhttp->minor == 1) {
			req->version = HTTP_1_1;
		} else {
			req->version = HTTP_0_0;
		}
	} else if (qhttp->major == 0) {
		req->version = HTTP_0_9;
	} else {
		req->version = HTTP_0_0;
	}

	return (B_FALSE);

full:
	return (B_TRUE);
}