nl7chttp.c revision d3d50737e566cade9a08d73d2af95105ac7cd960
/*
* 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
* 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>
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";
#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;
/*
* HTTP parser token:
*/
typedef struct token_s {
int tokid; /* Token ident */
char *text; /* Token text */
} 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 {
} 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"
/*
* HTTP scheme private state:
*/
typedef struct http_s {
} 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).
*/
"friday", "saturday", 0};
"Aug", "Sep", "Oct", "Nov", "Dec", 0};
/*
* 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
{
char **tpp;
char *tp;
char c, sc;
ssize_t n;
/* Parse and skip day-of-week (we don't use it) */
n = 0;
c = *cp++;
if (c == ',' || c == ' ')
break;
c = tolower(c);
break;
continue;
}
tp++;
}
/* Not case 1-3, try 4 */
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> */
return (-1);
c = *cp++;
if (c != ' ')
return (-1);
/* Get day of the month */
return (-1);
c = *cp++;
if (! isdigit(c))
return (-1);
n = c - '0';
return (-1);
c = *cp++;
if (! isdigit(c))
return (-1);
n *= 10;
n += c - '0';
day = n;
return (-1);
return (-1);
/* Parse month */
n = 0;
c = *cp;
if (c == sc) {
cp++;
break;
}
c = tolower(c);
break;
n++;
continue;
}
cp++;
tp++;
}
return (-1);
month = n;
/* Get year */
return (-1);
c = *cp++;
if (! isdigit(c))
return (-1);
n = c - '0';
return (-1);
c = *cp++;
if (! isdigit(c))
return (-1);
n *= 10;
n += c - '0';
return (-1);
c = *cp++;
if (sc == ' ') {
/* Case 1, get 2 more year digits */
if (! isdigit(c))
return (-1);
n *= 10;
n += c - '0';
return (-1);
c = *cp++;
if (! isdigit(c))
return (-1);
n *= 10;
n += c - '0';
/* Get seperator char */
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);
return (-1);
c = *cp++;
if (! isdigit(c))
return (-1);
n = c - '0';
return (-1);
c = *cp++;
if (! isdigit(c))
return (-1);
n *= 10;
n += c - '0';
hour = n;
return (-1);
c = *cp++;
if (c != ':')
return (-1);
return (-1);
c = *cp++;
if (! isdigit(c))
return (-1);
n = c - '0';
return (-1);
c = *cp++;
if (! isdigit(c))
return (-1);
n *= 10;
n += c - '0';
min = n;
return (-1);
c = *cp++;
if (c != ':')
return (-1);
return (-1);
c = *cp++;
if (! isdigit(c))
return (-1);
n = c - '0';
return (-1);
c = *cp++;
if (! isdigit(c))
return (-1);
n *= 10;
n += c - '0';
sec = n;
return (-1);
c = *cp++;
if (c != ' ')
return (-1);
return (-1);
c = *cp++;
if (c != 'G')
return (-1);
return (-1);
c = *cp++;
if (c != 'M')
return (-1);
return (-1);
c = *cp++;
if (c != 'T')
return (-1);
} else {
/* case 3, parse month */
sc = c;
n = 0;
c = *cp;
if (c == sc) {
cp++;
break;
}
c = tolower(c);
break;
n++;
continue;
}
cp++;
tp++;
}
return (-1);
month = n;
/* Get day of the month */
return (-1);
c = *cp++;
if (! isdigit(c))
return (-1);
n = c - '0';
return (-1);
c = *cp++;
if (! isdigit(c))
return (-1);
n *= 10;
n += c - '0';
day = n;
/* Skip <SP> */
return (-1);
c = *cp++;
if (c != ' ')
return (-1);
/* Get time */
return (-1);
c = *cp++;
if (! isdigit(c))
return (-1);
n = c - '0';
return (-1);
c = *cp++;
if (! isdigit(c))
return (-1);
n *= 10;
n += c - '0';
hour = n;
return (-1);
c = *cp++;
if (c != ':')
return (-1);
return (-1);
c = *cp++;
if (! isdigit(c))
return (-1);
n = c - '0';
return (-1);
c = *cp++;
if (! isdigit(c))
return (-1);
n *= 10;
n += c - '0';
min = n;
return (-1);
c = *cp++;
if (c != ':')
return (-1);
return (-1);
c = *cp++;
if (! isdigit(c))
return (-1);
n = c - '0';
return (-1);
c = *cp++;
if (! isdigit(c))
return (-1);
n *= 10;
n += c - '0';
sec = n;
/* Skip <SP> */
return (-1);
c = *cp++;
if (c != ' ')
return (-1);
/* Get year */
return (-1);
c = *cp++;
if (! isdigit(c))
return (-1);
n = c - '0';
return (-1);
c = *cp++;
if (! isdigit(c))
return (-1);
n *= 10;
n += c - '0';
return (-1);
c = *cp++;
if (! isdigit(c))
return (-1);
n *= 10;
n += c - '0';
return (-1);
c = *cp++;
if (! isdigit(c))
return (-1);
n *= 10;
n += c - '0';
year = n;
}
/* Last, caclulate seconds since Unix day zero */
if (month < 2)
leap--;
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;
/* Secs since Thu, 01 Jan 1970 00:00:00 GMT */
now /= 60;
now /= 60;
now /= 24;
year = 1970;
for (;;) {
day = 366;
else
day = 365;
break;
year++;
}
now++;
leap = 1;
else
leap = 0;
month = 11;
for (i = 11; i; i--) {
if (i < 2)
leap = 0;
break;
month--;
}
*cp++ = ',';
*cp++ = ' ';
i = day / 10;
*cp++ = '0' + i;
*cp++ = ' ';
*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++ = ' ';
i = hour / 10;
*cp++ = '0' + i;
*cp++ = ':';
i = min / 10;
*cp++ = '0' + i;
*cp++ = ':';
i = sec / 10;
*cp++ = '0' + i;
*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 *
{
int parse; /* parse state */
/* Special case, check for EOH (i.e. empty line) */
if (ca == '\n') {
/* End of header */
return (NULL);
} else if (ca == '\r') {
cp++;
if (ca == '\n') {
/* End of header */
return (NULL);
}
}
}
}
/* Get next parse text char */
if (cb != 0) {
/* Get next current line char */
/* Case insensitive */
/*
* Char match, next char.
*
* Note, parse text can contain EOL chars.
*/
tp++;
continue;
}
/* EOL, always go less than */
t = t->lt;
/* Go less than */
t = t->lt;
} else {
/* Go greater than */
t = t->gt;
}
/* Null node, so descend to < node */
t = t->lt;
}
if (t != NULL) {
/* Initialize for next node compare */
continue;
}
/*
* End of tree walk, no match, return pointer
* to the start of line then below find EOL.
*/
} else {
/*
* End of token text, match, return pointer to
* the rest of header text then below find EOL.
*/
}
/*
* 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;
parse = 1;
parse = 2;
parse = 2;
parse++;
} else if (parse > 2) {
parse = 0;
} else if (parse == 2) {
break;
}
cp++;
}
if (parse < 2) {
/* No EOL, not enough data */
}
/*
* 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.
*/
}
/*
* End of parse text, ...
*/
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 *
{
/* calc the size of the tree */
;
/* allocate the tree */
/* walk the tree and populate from list vector */
while (lvl >>= 1) {
} else {
}
if (inc) {
} else {
}
}
}
}
void
nl7c_http_init(void)
{
int n;
n = sizeof (Shttp_conn_cl) - 1;
http_conn_cl->b_wptr += n;
n = sizeof (Shttp_conn_ka) - 1;
http_conn_ka->b_wptr += n;
}
void
nl7c_http_free(void *arg)
{
}
#define STR_T_NOTCMP_OPT(a, b, m) ( \
#define STR_T_NOTCMP(a, b, m) ( \
STR_T_NOTCMP_OPT(a, b, m))
{
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 *
{
char *alloc;
char *cp;
int cnt;
char hdr_etag[] = "ETag: ";
/* Any optional header(s) */
/* Response has an ETag:, count it */
}
sz += 2;
/* Minimum temp uri initialization as needed by uri_response() */
/*
* Full response format.
*
* Copy to first sub char '#'.
*/
if (*proto == '#')
break;
}
/* Process the HTTP version substitutions */
proto++;
if (*proto == '#')
break;
}
proto++;
/* Copy to the next sub char '#' */
if (*proto == '#')
break;
}
/* Process the "Date: " substitution */
http_today(cp);
/* Skip to the next nonsub char '#' */
if (*proto != '#')
break;
cp++;
proto++;
}
/* Copy to the next sub char '#' */
if (*proto == '#')
break;
}
/* Process the NCA version substitutions */
proto++;
if (*proto == '#')
break;
}
proto++;
/* Copy remainder of HTTP header */
}
} else {
goto bad;
}
/* Any optional header(s) */
/* Response has an ETag:, add it */
*cp++ = '\r';
*cp++ = '\n';
}
/* Last, add empty line */
*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.
*/
return (NULL);
}
{
/*
* 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.
*/
sizeof (http_resp_304) - 1);
/* New response uri */
return (uri);
}
return (res);
/*
* 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.
*/
sizeof (http_resp_412) - 1);
/* New response uri */
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 *
{
if (flags & HTTP_CONN_CL)
else if (flags & HTTP_CONN_KA)
else
return (mp);
}
/*
* Parse the buffer *p of size len and update the uri_desc_t *uri and our
* http_t *http with the results.
*/
{
char *hp;
char *HTTP = "HTTP/";
goto bad;
}
/*
*/
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.
*/
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>
*/
HTTP++;
cp++;
}
if (*HTTP != 0) {
goto more;
goto bad;
}
goto more;
goto bad;
goto more;
if (*cp++ != '.')
goto bad;
goto more;
goto bad;
goto more;
if (*cp++ != '\r')
goto bad;
goto more;
if (*cp++ != '\n')
goto bad;
/*
* Initialize persistent state based on HTTP version.
*/
/* 1.1 persistent by default */
} else {
/* 1.0 isn't persistent by default */
}
/* Before 1.0 no persistent connections */
} else {
/* >= 2.0 not supported (yet) */
goto bad;
}
/*
* Parse HTTP headers through the EOH
* (End Of Header, i.e. an empty line).
*/
/* Get the next line */
/*
* Header field text is used to qualify this
* optionally convert and store *http.
*/
char c;
int n = 0;
c = *hp++;
if (! isdigit(c))
goto bad;
n *= 10;
n += c - '0';
}
}
case Qhdr_Accept_Charset:
break;
case Qhdr_Accept_Encoding:
break;
case Qhdr_Accept_Language:
break;
case Qhdr_Accept:
break;
case Qhdr_Authorization:
goto pass;
case Qhdr_Connection_close:
break;
break;
case Qhdr_Date:
break;
case Qhdr_ETag:
break;
case Qhdr_Host:
break;
case Qhdr_If_Modified_Since:
case Qhdr_If_Unmodified_Since:
break;
case Qhdr_Keep_Alive:
break;
case Qhdr_User_Agent:
break;
default:
break;
};
}
/*
* Filter header, do a copyover the header
* text, guarenteed to be at least 1 byte.
*/
char filter[] = "NL7C-Filtered";
if (n > 0)
cop += n;
*cop++ = ':';
*cop++ = ' ';
}
}
goto done;
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)
else
if (! persist)
} else {
if (persist)
else
}
}
/*
* Last, update parse consumed text pointer.
*/
return (B_TRUE);
pass:
return (B_TRUE);
bad:
more:
return (B_FALSE);
}
{
char *hp;
char *HTTP = "HTTP/";
int status = 0;
#ifdef NOT_YET
#endif
/* Chunked response */
goto chunked;
}
/* Already parsed, nothing todo */
return (B_TRUE);
}
/*
* for the actual response major nor minor values as only the
* request values are used.
*/
HTTP++;
cp++;
}
if (*HTTP != 0) {
goto more;
goto bad;
}
goto more;
goto bad;
#ifdef NOT_YET
#else
cp++;
#endif
goto more;
if (*cp++ != '.')
goto bad;
goto more;
goto bad;
#ifdef NOT_YET
#else
cp++;
#endif
goto more;
/*
* Get the response code.
*/
if (*cp++ != ' ')
goto bad;
goto more;
do {
if (*cp == ' ')
break;
goto bad;
if (status)
status *= 10;
switch (status) {
case 200:
/*
* The only response status we continue to process.
*/
break;
case 304:
goto pass;
case 307:
goto pass;
case 400:
/*
* Special case some response status codes, just mark
* as nocache and no response length and pass on the
*/
goto pass;
default:
/*
* All other response codes result in a parse failure.
*/
goto bad;
}
/*
* Initialize persistent state based on request HTTP version.
*/
/* 1.1 persistent by default */
} else {
/* 1.0 isn't persistent by default */
}
/* Before 1.0 no persistent connections */
} else {
/* >= 2.0 not supported (yet) */
goto bad;
}
/*
* Parse HTTP headers through the EOH
* (End Of Header, i.e. an empty line).
*/
/* Get the next line */
/*
* Header field text is used to qualify this
* optionally convert and store *http.
*/
char c;
int n = 0;
c = *hp++;
hd2i(c, n);
if (n == -1)
goto bad;
} else {
if (! isdigit(c))
goto bad;
n *= 10;
n += c - '0';
}
}
}
break;
break;
break;
case Shdr_Connection_close:
break;
break;
case Shdr_Chunked:
break;
case Shdr_Content_Length:
break;
case Shdr_Date:
break;
case Shdr_ETag:
break;
case Shdr_Expires:
break;
case Shdr_Keep_Alive:
break;
case Shdr_Last_Modified:
break;
case Shdr_Set_Cookie:
break;
case Shdr_Server:
break;
default:
break;
};
}
/*
* Filter header, do a copyover the header
* text, guarenteed to be at least 1 byte.
*/
char filter[] = "NL7C-Filtered";
if (n > 0)
cop += n;
*cop++ = ':';
*cop++ = ' ';
}
}
goto done;
goto more;
}
}
/* No EOH found */
goto more;
done:
/* Parse completed */
/* Save the HTTP header length */
goto pass;
}
}
/* Add header length to URI response length */
/* Set socket persist state */
if (persist)
else
if (! persist)
} else {
if (persist)
else
}
}
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.
*/
/* Chunked response */
goto chunked;
}
/* Nothing more todo */
goto parsed;
}
/* ??? just pass */
goto pass;
}
/* Have a valid expire and date so calc an lbolt expire */
} else if (nl7c_uri_ttl != -1) {
/* No valid expire speced and we have a TTL */
}
/*
* 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.
*/
/* Skip trailing "\r\n" */
goto more;
if (*cp++ != '\r')
goto bad;
goto more;
if (*cp++ != '\n')
goto bad;
}
/* Parse a chunklen "[0-9A-Fa-f]+" */
char c;
int n = 0;
goto more;
hd2i(c, n);
if (n == -1)
goto bad;
}
goto more;
if (*cp++ != '\n')
goto bad;
if (n == 0) {
/* Last chunk, skip trailing "\r\n" */
goto more;
if (*cp++ != '\r')
goto bad;
goto more;
if (*cp++ != '\n')
goto bad;
break;
}
}
/* Consume some bytes for the current chunk */
/* End of chunk, skip trailing "\r\n" */
goto more;
}
if (*cp++ != '\r')
goto bad;
goto more;
if (*cp++ != '\n')
goto bad;
goto more;
}
}
}
return (B_TRUE);
pass:
return (B_TRUE);
bad:
return (B_FALSE);
more:
return (B_FALSE);
}
{
int sz;
*(*wp)++ = 0;
sz++;
}
} else {
}
} else {
}
return (B_FALSE);
full:
return (B_TRUE);
}