
/*
 * This source file is part of the bstring string library.  This code was
 * written by Paul Hsieh in 2002-2015, and is covered by the BSD open source
 * license and the GPL. Refer to the accompanying documentation for details
 * on usage and license.
 */

/*
 * bstraux.c
 *
 * This file is not necessarily part of the core bstring library itself, but
 * is just an auxilliary module which includes miscellaneous or trivial
 * functions.
 */

#if defined (_MSC_VER)
# define _CRT_SECURE_NO_WARNINGS
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <ctype.h>
#include "bstrlib.h"
#include "bstraux.h"

#ifndef UNUSED
#define UNUSED(x) (void)(x)
#endif

/*  bstring bTail (bstring b, int n)
 *
 *  Return with a string of the last n characters of b.
 */
bstring bTail (bstring b, int n) {
	if (b == NULL || n < 0 || (b->mlen < b->slen && b->mlen > 0)) return NULL;
	if (n >= b->slen) return bstrcpy (b);
	return bmidstr (b, b->slen - n, n);
}

/*  bstring bHead (bstring b, int n)
 *
 *  Return with a string of the first n characters of b.
 */
bstring bHead (bstring b, int n) {
	if (b == NULL || n < 0 || (b->mlen < b->slen && b->mlen > 0)) return NULL;
	if (n >= b->slen) return bstrcpy (b);
	return bmidstr (b, 0, n);
}

/*  int bFill (bstring a, char c, int len)
 *
 *  Fill a given bstring with the character in parameter c, for a length n.
 */
int bFill (bstring b, char c, int len) {
	if (b == NULL || len < 0 || (b->mlen < b->slen && b->mlen > 0)) return -__LINE__;
	b->slen = 0;
	return bsetstr (b, len, NULL, c);
}

/*  int bReplicate (bstring b, int n)
 *
 *  Replicate the contents of b end to end n times and replace it in b.
 */
int bReplicate (bstring b, int n) {
	return bpattern (b, n * b->slen);
}

/*  int bReverse (bstring b)
 *
 *  Reverse the contents of b in place.
 */
int bReverse (bstring b) {
int i, n, m;
unsigned char t;

	if (b == NULL || b->slen < 0 || b->mlen < b->slen) return -__LINE__;
	n = b->slen;
	if (2 <= n) {
		m = ((unsigned)n) >> 1;
		n--;
		for (i=0; i < m; i++) {
			t = b->data[n - i];
			b->data[n - i] = b->data[i];
			b->data[i] = t;
		}
	}
	return 0;
}

/*  int bInsertChrs (bstring b, int pos, int len, unsigned char c, unsigned char fill)
 *
 *  Insert a repeated sequence of a given character into the string at 
 *  position pos for a length len.
 */
int bInsertChrs (bstring b, int pos, int len, unsigned char c, unsigned char fill) {
	if (b == NULL || b->slen < 0 || b->mlen < b->slen || pos < 0 || len <= 0) return -__LINE__;

	if (pos > b->slen 
	 && 0 > bsetstr (b, pos, NULL, fill)) return -__LINE__;

	if (0 > balloc (b, b->slen + len)) return -__LINE__;
	if (pos < b->slen) memmove (b->data + pos + len, b->data + pos, b->slen - pos);
	memset (b->data + pos, c, len);
	b->slen += len;
	b->data[b->slen] = (unsigned char) '\0';
	return BSTR_OK;
}

/*  int bJustifyLeft (bstring b, int space)
 *
 *  Left justify a string.
 */
int bJustifyLeft (bstring b, int space) {
int j, i, s, t;
unsigned char c = (unsigned char) space;

	if (b == NULL || b->slen < 0 || b->mlen < b->slen) return -__LINE__;
	if (space != (int) c) return BSTR_OK;

	for (s=j=i=0; i < b->slen; i++) {
		t = s;
		s = c != (b->data[j] = b->data[i]);
		j += (t|s);
	}
	if (j > 0 && b->data[j-1] == c) j--;

	b->data[j] = (unsigned char) '\0';
	b->slen = j;
	return BSTR_OK;
}

/*  int bJustifyRight (bstring b, int width, int space)
 *
 *  Right justify a string to within a given width.
 */
int bJustifyRight (bstring b, int width, int space) {
int ret;
	if (width <= 0) return -__LINE__;
	if (0 > (ret = bJustifyLeft (b, space))) return ret;
	if (b->slen <= width)
		return bInsertChrs (b, 0, width - b->slen, (unsigned char) space, (unsigned char) space);
	return BSTR_OK;
}

/*  int bJustifyCenter (bstring b, int width, int space)
 *
 *  Center a string's non-white space characters to within a given width by
 *  inserting whitespaces at the beginning.
 */
int bJustifyCenter (bstring b, int width, int space) {
int ret;
	if (width <= 0) return -__LINE__;
	if (0 > (ret = bJustifyLeft (b, space))) return ret;
	if (b->slen <= width)
		return bInsertChrs (b, 0, (width - b->slen + 1) >> 1, (unsigned char) space, (unsigned char) space);
	return BSTR_OK;
}

/*  int bJustifyMargin (bstring b, int width, int space)
 *
 *  Stretch a string to flush against left and right margins by evenly
 *  distributing additional white space between words.  If the line is too
 *  long to be margin justified, it is left justified.
 */
int bJustifyMargin (bstring b, int width, int space) {
struct bstrList * sl;
int i, l, c;

	if (b == NULL || b->slen < 0 || b->mlen == 0 || b->mlen < b->slen) return -__LINE__;
	if (NULL == (sl = bsplit (b, (unsigned char) space))) return -__LINE__;
	for (l=c=i=0; i < sl->qty; i++) {
		if (sl->entry[i]->slen > 0) {
			c ++;
			l += sl->entry[i]->slen;
		}
	}

	if (l + c >= width || c < 2) {
		bstrListDestroy (sl);
		return bJustifyLeft (b, space);
	}

	b->slen = 0;
	for (i=0; i < sl->qty; i++) {
		if (sl->entry[i]->slen > 0) {
			if (b->slen > 0) {
				int s = (width - l + (c / 2)) / c;
				bInsertChrs (b, b->slen, s, (unsigned char) space, (unsigned char) space);
				l += s;
			}
			bconcat (b, sl->entry[i]);
			c--;
			if (c <= 0) break;
		}
	}

	bstrListDestroy (sl);
	return BSTR_OK;
}

static size_t readNothing (void *buff, size_t elsize, size_t nelem, void *parm) {
	UNUSED(buff);
	UNUSED(elsize);
	UNUSED(nelem);
	UNUSED(parm);
	return 0; /* Immediately indicate EOF. */
}

/*  struct bStream * bsFromBstr (const_bstring b);
 *
 *  Create a bStream whose contents are a copy of the bstring passed in.
 *  This allows the use of all the bStream APIs with bstrings.
 */
struct bStream * bsFromBstr (const_bstring b) {
struct bStream * s = bsopen ((bNread) readNothing, NULL);
	bsunread (s, b); /* Push the bstring data into the empty bStream. */
	return s;
}

static size_t readRef (void *buff, size_t elsize, size_t nelem, void *parm) {
struct tagbstring * t = (struct tagbstring *) parm;
size_t tsz = elsize * nelem;

	if (tsz > (size_t) t->slen) tsz = (size_t) t->slen;
	if (tsz > 0) {
		memcpy (buff, t->data, tsz);
		t->slen -= (int) tsz;
		t->data += tsz;
		return tsz / elsize;
	}
	return 0;
}

/*  The "by reference" version of the above function.  This function puts
 *  a number of restrictions on the call site (the passed in struct 
 *  tagbstring *will* be modified by this function, and the source data
 *  must remain alive and constant for the lifetime of the bStream).  
 *  Hence it is not presented as an extern.
 */
static struct bStream * bsFromBstrRef (struct tagbstring * t) {
	if (!t) return NULL;
	return bsopen ((bNread) readRef, t);
}

/*  char * bStr2NetStr (const_bstring b)
 *
 *  Convert a bstring to a netstring.  See 
 *  http://cr.yp.to/proto/netstrings.txt for a description of netstrings.
 *  Note: 1) The value returned should be freed with a call to bcstrfree() at 
 *           the point when it will no longer be referenced to avoid a memory 
 *           leak.
 *        2) If the returned value is non-NULL, then it also '\0' terminated
 *           in the character position one past the "," terminator.
 */
char * bStr2NetStr (const_bstring b) {
char strnum[sizeof (b->slen) * 3 + 1];
bstring s;
unsigned char * buff;

	if (b == NULL || b->data == NULL || b->slen < 0) return NULL;
	sprintf (strnum, "%d:", b->slen);
	if (NULL == (s = bfromcstr (strnum))
	 || bconcat (s, b) == BSTR_ERR || bconchar (s, (char) ',') == BSTR_ERR) {
		bdestroy (s);
		return NULL;
	}
	buff = s->data;
	bcstrfree ((char *) s);
	return (char *) buff;
}

/*  bstring bNetStr2Bstr (const char * buf)
 *
 *  Convert a netstring to a bstring.  See 
 *  http://cr.yp.to/proto/netstrings.txt for a description of netstrings.
 *  Note that the terminating "," *must* be present, however a following '\0'
 *  is *not* required.
 */
bstring bNetStr2Bstr (const char * buff) {
int i, x;
bstring b;
	if (buff == NULL) return NULL;
	x = 0;
	for (i=0; buff[i] != ':'; i++) {
		unsigned int v = buff[i] - '0';
		if (v > 9 || x > ((INT_MAX - (signed int)v) / 10)) return NULL;
		x = (x * 10) + v;
	}

	/* This thing has to be properly terminated */
	if (buff[i + 1 + x] != ',') return NULL;

	if (NULL == (b = bfromcstr (""))) return NULL;
	if (balloc (b, x + 1) != BSTR_OK)  {
		bdestroy (b);
		return NULL;
	}
	memcpy (b->data, buff + i + 1, x);
	b->data[x] = (unsigned char) '\0';
	b->slen = x;
	return b;
}

static char b64ETable[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

/*  bstring bBase64Encode (const_bstring b)
 *
 *  Generate a base64 encoding.  See: RFC1341
 */
bstring bBase64Encode (const_bstring b) {
int i, c0, c1, c2, c3;
bstring out;

	if (b == NULL || b->slen < 0 || b->data == NULL) return NULL;

	out = bfromcstr ("");
	for (i=0; i + 2 < b->slen; i += 3) {
		if (i && ((i % 57) == 0)) {
			if (bconchar (out, (char) '\015') < 0 || bconchar (out, (char) '\012') < 0) {
				bdestroy (out);
				return NULL;
			}
		}
		c0 = b->data[i] >> 2;
		c1 = ((b->data[i] << 4) |
		      (b->data[i+1] >> 4)) & 0x3F;
		c2 = ((b->data[i+1] << 2) |
		      (b->data[i+2] >> 6)) & 0x3F;
		c3 = b->data[i+2] & 0x3F;
		if (bconchar (out, b64ETable[c0]) < 0 ||
			bconchar (out, b64ETable[c1]) < 0 ||
			bconchar (out, b64ETable[c2]) < 0 ||
			bconchar (out, b64ETable[c3]) < 0) {
			bdestroy (out);
			return NULL;
		}
	}

	if (i && ((i % 57) == 0)) {
		if (bconchar (out, (char) '\015') < 0 || bconchar (out, (char) '\012') < 0) {
			bdestroy (out);
			return NULL;
		}
	}

	switch (i + 2 - b->slen) {
		case 0:	c0 = b->data[i] >> 2;
				c1 = ((b->data[i] << 4) |
					  (b->data[i+1] >> 4)) & 0x3F;
				c2 = (b->data[i+1] << 2) & 0x3F;
			if (bconchar (out, b64ETable[c0]) < 0 ||
				bconchar (out, b64ETable[c1]) < 0 ||
				bconchar (out, b64ETable[c2]) < 0 ||
				bconchar (out, (char) '=') < 0) {
				bdestroy (out);
				return NULL;
			}
			break;
		case 1:	c0 =  b->data[i] >> 2;
				c1 = (b->data[i] << 4) & 0x3F;
			if (bconchar (out, b64ETable[c0]) < 0 ||
				bconchar (out, b64ETable[c1]) < 0 ||
				bconchar (out, (char) '=') < 0 ||
				bconchar (out, (char) '=') < 0) {
				bdestroy (out);
				return NULL;
			}
			break;
		case 2: break;
	}

	return out;
}

#define B64_PAD (-2)
#define B64_ERR (-1)

static int base64DecodeSymbol (unsigned char alpha) {
   if      ((alpha >= 'A') && (alpha <= 'Z')) return (int)(alpha - 'A');
   else if ((alpha >= 'a') && (alpha <= 'z'))
        return 26 + (int)(alpha - 'a');
   else if ((alpha >= '0') && (alpha <= '9'))
        return 52 + (int)(alpha - '0');
   else if (alpha == '+') return 62;
   else if (alpha == '/') return 63;
   else if (alpha == '=') return B64_PAD;
   else                   return B64_ERR;
}

/*  bstring bBase64DecodeEx (const_bstring b, int * boolTruncError)
 *
 *  Decode a base64 block of data.  All MIME headers are assumed to have been
 *  removed.  See: RFC1341
 */
bstring bBase64DecodeEx (const_bstring b, int * boolTruncError) {
int i, v;
unsigned char c0, c1, c2;
bstring out;

	if (b == NULL || b->slen < 0 || b->data == NULL) return NULL;
	if (boolTruncError) *boolTruncError = 0;
	out = bfromcstr ("");
	i = 0;
	for (;;) {
		do {
			if (i >= b->slen) return out;
			if (b->data[i] == '=') {	/* Bad "too early" truncation */
				if (boolTruncError) {
					*boolTruncError = 1;
					return out;
				}
				bdestroy (out);
				return NULL;
			}
			v = base64DecodeSymbol (b->data[i]);
			i++;
		} while (v < 0);
		c0 = (unsigned char) (v << 2);
		do {
			if (i >= b->slen || b->data[i] == '=') {	/* Bad "too early" truncation */
				if (boolTruncError) {
					*boolTruncError = 1;
					return out;
				}
				bdestroy (out);
				return NULL;
			}
			v = base64DecodeSymbol (b->data[i]);
			i++;
		} while (v < 0);
		c0 |= (unsigned char) (v >> 4);
		c1  = (unsigned char) (v << 4);
		do {
			if (i >= b->slen) {
				if (boolTruncError) {
					*boolTruncError = 1;
					return out;
				}
				bdestroy (out);
				return NULL;
			}
			if (b->data[i] == '=') {
				i++;
				if (i >= b->slen || b->data[i] != '=' || bconchar (out, c0) < 0) {
					if (boolTruncError) {
						*boolTruncError = 1;
						return out;
					}
					bdestroy (out); /* Missing "=" at the end. */
					return NULL;
				}
				return out;
			}
			v = base64DecodeSymbol (b->data[i]);
			i++;
		} while (v < 0);
		c1 |= (unsigned char) (v >> 2);
		c2  = (unsigned char) (v << 6);
		do {
			if (i >= b->slen) {
				if (boolTruncError) {
					*boolTruncError = 1;
					return out;
				}
				bdestroy (out);
				return NULL;
			}
			if (b->data[i] == '=') {
				if (bconchar (out, c0) < 0 || bconchar (out, c1) < 0) {
					if (boolTruncError) {
						*boolTruncError = 1;
						return out;
					}
					bdestroy (out);
					return NULL;
				}
				if (boolTruncError) *boolTruncError = 0;
				return out;
			}
			v = base64DecodeSymbol (b->data[i]);
			i++;
		} while (v < 0);
		c2 |= (unsigned char) (v);
		if (bconchar (out, c0) < 0 ||
			bconchar (out, c1) < 0 ||
			bconchar (out, c2) < 0) {
			if (boolTruncError) {
				*boolTruncError = -1;
				return out;
			}
			bdestroy (out);
			return NULL;
		}
	}
}

#define UU_DECODE_BYTE(b) (((b) == (signed int)'`') ? 0 : (b) - (signed int)' ')

struct bUuInOut {
	bstring src, dst;
	int * badlines;
};

#define UU_MAX_LINELEN 45

static int bUuDecLine (void * parm, int ofs, int len) {
struct bUuInOut * io = (struct bUuInOut *) parm;
bstring s = io->src;
bstring t = io->dst;
int i, llen, otlen, ret, c0, c1, c2, c3, d0, d1, d2, d3;

	if (len == 0) return 0;
	llen = UU_DECODE_BYTE (s->data[ofs]);
	ret = 0;

	otlen = t->slen;

	if (((unsigned) llen) > UU_MAX_LINELEN) { ret = -__LINE__; 
		goto bl;
	}

	llen += t->slen;

	for (i=1; i < s->slen && t->slen < llen;i += 4) {
		unsigned char outoctet[3];
		c0 = UU_DECODE_BYTE (d0 = (int) bchare (s, i+ofs+0, ' ' - 1));
		c1 = UU_DECODE_BYTE (d1 = (int) bchare (s, i+ofs+1, ' ' - 1));
		c2 = UU_DECODE_BYTE (d2 = (int) bchare (s, i+ofs+2, ' ' - 1));
		c3 = UU_DECODE_BYTE (d3 = (int) bchare (s, i+ofs+3, ' ' - 1));

		if (((unsigned) (c0|c1) >= 0x40)) { if (!ret) ret = -__LINE__;
			if (d0 > 0x60 || (d0 < (' ' - 1) && !isspace (d0)) ||
			    d1 > 0x60 || (d1 < (' ' - 1) && !isspace (d1))) {
				t->slen = otlen;
				goto bl;
			}
			c0 = c1 = 0;
		}
		outoctet[0] = (unsigned char) ((c0 << 2) | ((unsigned) c1 >> 4));
		if (t->slen+1 >= llen) {
			if (0 > bconchar (t, (char) outoctet[0])) return -__LINE__;
			break;
		}
		if ((unsigned) c2 >= 0x40) { if (!ret) ret = -__LINE__;
			if (d2 > 0x60 || (d2 < (' ' - 1) && !isspace (d2))) {
				t->slen = otlen;
				goto bl;
			}
			c2 = 0;
		}
		outoctet[1] = (unsigned char) ((c1 << 4) | ((unsigned) c2 >> 2));
		if (t->slen+2 >= llen) {
			if (0 > bcatblk (t, outoctet, 2)) return -__LINE__;
			break;
		}
		if ((unsigned) c3 >= 0x40) { if (!ret) ret = -__LINE__;
			if (d3 > 0x60 || (d3 < (' ' - 1) && !isspace (d3))) {
				t->slen = otlen;
				goto bl;
			}
			c3 = 0;
		}
		outoctet[2] = (unsigned char) ((c2 << 6) | ((unsigned) c3));
		if (0 > bcatblk (t, outoctet, 3)) return -__LINE__;
	}
	if (t->slen < llen) { if (0 == ret) ret = -__LINE__;
		t->slen = otlen;
	}
	bl:;
	if (ret && io->badlines) {
		(*io->badlines)++;
		return 0;
	}
	return ret;
}

/*  bstring bUuDecodeEx (const_bstring src, int * badlines)
 *
 *  Performs a UUDecode of a block of data.  If there are errors in the
 *  decoding, they are counted up and returned in "badlines", if badlines is
 *  not NULL. It is assumed that the "begin" and "end" lines have already 
 *  been stripped off.  The potential security problem of writing the 
 *  filename in the begin line is something that is beyond the scope of a 
 *  portable library.
 */

#ifdef _MSC_VER
#pragma warning(disable:4204)
#endif

bstring bUuDecodeEx (const_bstring src, int * badlines) {
struct tagbstring t;
struct bStream * s;
struct bStream * d;
bstring b;

	if (!src) return NULL;
	t = *src; /* Short lifetime alias to header of src */
	s = bsFromBstrRef (&t); /* t is undefined after this */
	if (!s) return NULL;
	d = bsUuDecode (s, badlines);
	b = bfromcstralloc (256, "");
	if (NULL == b || 0 > bsread (b, d, INT_MAX)) {
		bdestroy (b);
		b = NULL;
	}
	bsclose (d);
	bsclose (s);
	return b;
}

struct bsUuCtx {
	struct bUuInOut io;
	struct bStream * sInp;
};

static size_t bsUuDecodePart (void *buff, size_t elsize, size_t nelem, void *parm) {
static struct tagbstring eol = bsStatic ("\r\n");
struct bsUuCtx * luuCtx = (struct bsUuCtx *) parm;
size_t tsz;
int l, lret;

	if (NULL == buff || NULL == parm) return 0;
	tsz = elsize * nelem;

	CheckInternalBuffer:;
	/* If internal buffer has sufficient data, just output it */
	if (((size_t) luuCtx->io.dst->slen) > tsz) {
		memcpy (buff, luuCtx->io.dst->data, tsz);
		bdelete (luuCtx->io.dst, 0, (int) tsz);
		return nelem;
	}

	DecodeMore:;
	if (0 <= (l = binchr (luuCtx->io.src, 0, &eol))) {
		int ol = 0;
		struct tagbstring t;
		bstring s = luuCtx->io.src;
		luuCtx->io.src = &t;

		do {
			if (l > ol) {
				bmid2tbstr (t, s, ol, l - ol);
				lret = bUuDecLine (&luuCtx->io, 0, t.slen);
				if (0 > lret) {
					luuCtx->io.src = s;
					goto Done;
				}
			}
			ol = l + 1;
			if (((size_t) luuCtx->io.dst->slen) > tsz) break;
			l = binchr (s, ol, &eol);
		} while (BSTR_ERR != l);
		bdelete (s, 0, ol);
		luuCtx->io.src = s;
		goto CheckInternalBuffer;
	}

	if (BSTR_ERR != bsreada (luuCtx->io.src, luuCtx->sInp, bsbufflength (luuCtx->sInp, BSTR_BS_BUFF_LENGTH_GET))) {
		goto DecodeMore;
	}

	bUuDecLine (&luuCtx->io, 0, luuCtx->io.src->slen);

	Done:;
	/* Output any lingering data that has been translated */
	if (((size_t) luuCtx->io.dst->slen) > 0) {
		if (((size_t) luuCtx->io.dst->slen) > tsz) goto CheckInternalBuffer;
		memcpy (buff, luuCtx->io.dst->data, luuCtx->io.dst->slen);
		tsz = luuCtx->io.dst->slen / elsize;
		luuCtx->io.dst->slen = 0;
		if (tsz > 0) return tsz;
	}

	/* Deallocate once EOF becomes triggered */
	bdestroy (luuCtx->io.dst);
	bdestroy (luuCtx->io.src);
	free (luuCtx);
	return 0;
}

/*  bStream * bsUuDecode (struct bStream * sInp, int * badlines)
 *
 *  Creates a bStream which performs the UUDecode of an an input stream.  If
 *  there are errors in the decoding, they are counted up and returned in 
 *  "badlines", if badlines is not NULL. It is assumed that the "begin" and 
 *  "end" lines have already been stripped off.  The potential security 
 *  problem of writing the filename in the begin line is something that is 
 *  beyond the scope of a portable library.
 */

struct bStream * bsUuDecode (struct bStream * sInp, int * badlines) {
struct bsUuCtx * luuCtx = (struct bsUuCtx *) malloc (sizeof (struct bsUuCtx));
struct bStream * sOut;

	if (NULL == luuCtx) return NULL;

	luuCtx->io.src = bfromcstr ("");
	luuCtx->io.dst = bfromcstr ("");
	if (NULL == luuCtx->io.dst || NULL == luuCtx->io.src) {
		CleanUpFailureToAllocate:;
		bdestroy (luuCtx->io.dst);
		bdestroy (luuCtx->io.src);
		free (luuCtx);
		return NULL;
	}
	luuCtx->io.badlines = badlines;
	if (badlines) *badlines = 0;

	luuCtx->sInp = sInp;

	sOut = bsopen ((bNread) bsUuDecodePart, luuCtx);
	if (NULL == sOut) goto CleanUpFailureToAllocate;
	return sOut;
}

#define UU_ENCODE_BYTE(b) (char) (((b) == 0) ? '`' : ((b) + ' '))

/*  bstring bUuEncode (const_bstring src)
 *
 *  Performs a UUEncode of a block of data.  The "begin" and "end" lines are 
 *  not appended.
 */
bstring bUuEncode (const_bstring src) {
bstring out;
int i, j, jm;
unsigned int c0, c1, c2;
	if (src == NULL || src->slen < 0 || src->data == NULL) return NULL;
	if ((out = bfromcstr ("")) == NULL) return NULL;
	for (i=0; i < src->slen; i += UU_MAX_LINELEN) {
		if ((jm = i + UU_MAX_LINELEN) > src->slen) jm = src->slen;
		if (bconchar (out, UU_ENCODE_BYTE (jm - i)) < 0) {
			bstrFree (out);
			break;
		}
		for (j = i; j < jm; j += 3) {
			c0 = (unsigned int) bchar (src, j    );
			c1 = (unsigned int) bchar (src, j + 1);
			c2 = (unsigned int) bchar (src, j + 2);
			if (bconchar (out, UU_ENCODE_BYTE ( (c0 & 0xFC) >> 2)) < 0 ||
			    bconchar (out, UU_ENCODE_BYTE (((c0 & 0x03) << 4) | ((c1 & 0xF0) >> 4))) < 0 ||
			    bconchar (out, UU_ENCODE_BYTE (((c1 & 0x0F) << 2) | ((c2 & 0xC0) >> 6))) < 0 ||
			    bconchar (out, UU_ENCODE_BYTE ( (c2 & 0x3F))) < 0) {
				bstrFree (out);
				goto End;
			}
		}
		if (bconchar (out, (char) '\r') < 0 || bconchar (out, (char) '\n') < 0) {
			bstrFree (out);
			break;
		}
	}
	End:;
	return out;
}

/*  bstring bYEncode (const_bstring src)
 *
 *  Performs a YEncode of a block of data.  No header or tail info is 
 *  appended.  See: http://www.yenc.org/whatis.htm and 
 *  http://www.yenc.org/yenc-draft.1.3.txt
 */
bstring bYEncode (const_bstring src) {
int i;
bstring out;
unsigned char c;

	if (src == NULL || src->slen < 0 || src->data == NULL) return NULL;
	if ((out = bfromcstr ("")) == NULL) return NULL;
	for (i=0; i < src->slen; i++) {
		c = (unsigned char)(src->data[i] + 42);
		if (c == '=' || c == '\0' || c == '\r' || c == '\n') {
			if (0 > bconchar (out, (char) '=')) {
				bdestroy (out);
				return NULL;
			}
			c += (unsigned char) 64;
		}
		if (0 > bconchar (out, c)) {
			bdestroy (out);
			return NULL;
		}
	}
	return out;
}

/*  bstring bYDecode (const_bstring src)
 *
 *  Performs a YDecode of a block of data.  See: 
 *  http://www.yenc.org/whatis.htm and http://www.yenc.org/yenc-draft.1.3.txt
 */
#define MAX_OB_LEN (64)

bstring bYDecode (const_bstring src) {
int i;
bstring out;
unsigned char c;
unsigned char octetbuff[MAX_OB_LEN];
int obl;

	if (src == NULL || src->slen < 0 || src->data == NULL) return NULL;
	if ((out = bfromcstr ("")) == NULL) return NULL;

	obl = 0;

	for (i=0; i < src->slen; i++) {
		if ('=' == (c = src->data[i])) { /* The = escape mode */
			i++;
			if (i >= src->slen) {
				bdestroy (out);
				return NULL;
			}
			c = (unsigned char) (src->data[i] - 64);
		} else {
			if ('\0' == c) {
				bdestroy (out);
				return NULL;
			}

			/* Extraneous CR/LFs are to be ignored. */
			if (c == '\r' || c == '\n') continue;
		}

		octetbuff[obl] = (unsigned char) ((int) c - 42);
		obl++;

		if (obl >= MAX_OB_LEN) {
			if (0 > bcatblk (out, octetbuff, obl)) {
				bdestroy (out);
				return NULL;
			}
			obl = 0;
		}
	}

	if (0 > bcatblk (out, octetbuff, obl)) {
		bdestroy (out);
		out = NULL;
	}
	return out;
}

/*  int bSGMLEncode (bstring b)
 *
 *  Change the string into a version that is quotable in SGML (HTML, XML).
 */
int bSGMLEncode (bstring b) {
static struct tagbstring fr[4][2] = {
	{ bsStatic("&"), bsStatic("&amp;") },
	{ bsStatic("\""), bsStatic("&quot;") },
	{ bsStatic("<"), bsStatic("&lt;") },
	{ bsStatic(">"), bsStatic("&gt;") } };
int i;
	for (i = 0; i < 4; i++) {
		int ret = bfindreplace (b, &fr[i][0], &fr[i][1], 0);
		if (0 > ret) return ret;
	}
	return 0;
}

/*  bstring bStrfTime (const char * fmt, const struct tm * timeptr)
 *
 *  Takes a format string that is compatible with strftime and a struct tm
 *  pointer, formats the time according to the format string and outputs
 *  the bstring as a result. Note that if there is an early generation of a 
 *  '\0' character, the bstring will be truncated to this end point.
 */
bstring bStrfTime (const char * fmt, const struct tm * timeptr) {
#if defined (__TURBOC__) && !defined (__BORLANDC__)
static struct tagbstring ns = bsStatic ("bStrfTime Not supported");
	fmt = fmt;
	timeptr = timeptr;
	return &ns;
#else
bstring buff;
int n;
size_t r;

	if (fmt == NULL) return NULL;

	/* Since the length is not determinable beforehand, a search is
	   performed using the truncating "strftime" call on increasing 
	   potential sizes for the output result. */

	if ((n = (int) (2*strlen (fmt))) < 16) n = 16;
	buff = bfromcstralloc (n+2, "");

	for (;;) {
		if (BSTR_OK != balloc (buff, n + 2)) {
			bdestroy (buff);
			return NULL;
		}

		r = strftime ((char *) buff->data, n + 1, fmt, timeptr);

		if (r > 0) {
			buff->slen = (int) r;
			break;
		}

		n += n;
	}

	return buff;
#endif
}

/*  int bSetCstrChar (bstring a, int pos, char c)
 *
 *  Sets the character at position pos to the character c in the bstring a.
 *  If the character c is NUL ('\0') then the string is truncated at this
 *  point.  Note: this does not enable any other '\0' character in the bstring
 *  as terminator indicator for the string.  pos must be in the position 
 *  between 0 and b->slen inclusive, otherwise BSTR_ERR will be returned.
 */
int bSetCstrChar (bstring b, int pos, char c) {
	if (NULL == b || b->mlen <= 0 || b->slen < 0 || b->mlen < b->slen)
		return BSTR_ERR;
	if (pos < 0 || pos > b->slen) return BSTR_ERR;

	if (pos == b->slen) {
		if ('\0' != c) return bconchar (b, c);
		return 0;
	}

	b->data[pos] = (unsigned char) c;
	if ('\0' == c) b->slen = pos;

	return 0;
}

/*  int bSetChar (bstring b, int pos, char c)
 *
 *  Sets the character at position pos to the character c in the bstring a.
 *  The string is not truncated if the character c is NUL ('\0').  pos must
 *  be in the position between 0 and b->slen inclusive, otherwise BSTR_ERR
 *  will be returned.
 */
int bSetChar (bstring b, int pos, char c) {
	if (NULL == b || b->mlen <= 0 || b->slen < 0 || b->mlen < b->slen)
		return BSTR_ERR;
	if (pos < 0 || pos > b->slen) return BSTR_ERR;

	if (pos == b->slen) {
		return bconchar (b, c);
	}

	b->data[pos] = (unsigned char) c;
	return 0;
}

#define INIT_SECURE_INPUT_LENGTH (256)

/*  bstring bSecureInput (int maxlen, int termchar, 
 *                        bNgetc vgetchar, void * vgcCtx)
 *
 *  Read input from an abstracted input interface, for a length of at most
 *  maxlen characters.  If maxlen <= 0, then there is no length limit put
 *  on the input.  The result is terminated early if vgetchar() return EOF
 *  or the user specified value termchar.
 *
 */
bstring bSecureInput (int maxlen, int termchar, bNgetc vgetchar, void * vgcCtx) {
int i, m, c;
bstring b, t;

	if (!vgetchar) return NULL;

	b = bfromcstralloc (INIT_SECURE_INPUT_LENGTH, "");
	if ((c = UCHAR_MAX + 1) == termchar) c++;

	for (i=0; ; i++) {
		if (termchar == c || (maxlen > 0 && i >= maxlen)) break;
		c = vgetchar (vgcCtx);
		if (EOF == c) break;

		if (i+1 >= b->mlen) {

			/* Double size, and deal with numeric overflows */

			if (b->mlen <= INT_MAX / 2) m = b->mlen << 1;
			else if (b->mlen <= INT_MAX - 1024) m = b->mlen + 1024;
			else if (b->mlen <= INT_MAX - 16) m = b->mlen + 16;
			else if (b->mlen <= INT_MAX - 1) m = b->mlen + 1;
			else {
				bSecureDestroy (b); /* Cleanse partial buffer */
				return NULL;
			}

			t = bfromcstrrangealloc (b->mlen + 1, m, "");
			if (t) memcpy (t->data, b->data, i);
			bSecureDestroy (b);     /* Cleanse previous buffer */
			b = t;
			if (!b) return b;
		}

		b->data[i] = (unsigned char) c;
	}

	b->slen = i;
	b->data[i] = (unsigned char) '\0';
	return b;
}

#define BWS_BUFF_SZ (1024)

struct bwriteStream {
    bstring buff;    /* Buffer for underwrites                   */
    void * parm;     /* The stream handle for core stream        */
    bNwrite writeFn; /* fwrite work-a-like fnptr for core stream */
    int isEOF;       /* track stream's EOF state                 */
    int minBuffSz;
};

/*  struct bwriteStream * bwsOpen (bNwrite writeFn, void * parm)
 *
 *  Wrap a given open stream (described by a fwrite work-a-like function 
 *  pointer and stream handle) into an open bwriteStream suitable for write
 *  streaming functions.
 */
struct bwriteStream * bwsOpen (bNwrite writeFn, void * parm) {
struct bwriteStream * ws;

	if (NULL == writeFn) return NULL;
	ws = (struct bwriteStream *) malloc (sizeof (struct bwriteStream));
	if (ws) {
		if (NULL == (ws->buff = bfromcstr (""))) {
			free (ws);
			ws = NULL;
		} else {
			ws->parm = parm;
			ws->writeFn = writeFn;
			ws->isEOF = 0;
			ws->minBuffSz = BWS_BUFF_SZ;
		}
	}
	return ws;
}

#define internal_bwswriteout(ws,b) { \
	if ((b)->slen > 0) { \
		if (1 != (ws->writeFn ((b)->data, (b)->slen, 1, ws->parm))) { \
			ws->isEOF = 1; \
			return BSTR_ERR; \
		} \
	} \
}

/*  int bwsWriteFlush (struct bwriteStream * ws)
 *
 *  Force any pending data to be written to the core stream.
 */
int bwsWriteFlush (struct bwriteStream * ws) {
	if (NULL == ws || ws->isEOF || 0 >= ws->minBuffSz || 
	    NULL == ws->writeFn || NULL == ws->buff) return BSTR_ERR;
	internal_bwswriteout (ws, ws->buff);
	ws->buff->slen = 0;
	return 0;
}

/*  int bwsWriteBstr (struct bwriteStream * ws, const_bstring b)
 *
 *  Send a bstring to a bwriteStream.  If the stream is at EOF BSTR_ERR is
 *  returned.  Note that there is no deterministic way to determine the exact
 *  cut off point where the core stream stopped accepting data.
 */
int bwsWriteBstr (struct bwriteStream * ws, const_bstring b) {
struct tagbstring t;
int l;

	if (NULL == ws || NULL == b || NULL == ws->buff ||
	    ws->isEOF || 0 >= ws->minBuffSz || NULL == ws->writeFn)
		return BSTR_ERR;

	/* Buffer prepacking optimization */
	if (b->slen > 0 && ws->buff->mlen - ws->buff->slen > b->slen) {
		static struct tagbstring empty = bsStatic ("");
		if (0 > bconcat (ws->buff, b)) return BSTR_ERR;
		return bwsWriteBstr (ws, &empty);
	}

	if (0 > (l = ws->minBuffSz - ws->buff->slen)) {
		internal_bwswriteout (ws, ws->buff);
		ws->buff->slen = 0;
		l = ws->minBuffSz;
	}

	if (b->slen < l) return bconcat (ws->buff, b);

	if (0 > bcatblk (ws->buff, b->data, l)) return BSTR_ERR;
	internal_bwswriteout (ws, ws->buff);
	ws->buff->slen = 0;

	bmid2tbstr (t, (bstring) b, l, b->slen);

	if (t.slen >= ws->minBuffSz) {
		internal_bwswriteout (ws, &t);
		return 0;
	}

	return bassign (ws->buff, &t);
}

/*  int bwsWriteBlk (struct bwriteStream * ws, void * blk, int len)
 *
 *  Send a block of data a bwriteStream.  If the stream is at EOF BSTR_ERR is 
 *  returned.
 */
int bwsWriteBlk (struct bwriteStream * ws, void * blk, int len) {
struct tagbstring t;
	if (NULL == blk || len < 0) return BSTR_ERR;
	blk2tbstr (t, blk, len);
	return bwsWriteBstr (ws, &t);
}

/*  int bwsIsEOF (const struct bwriteStream * ws)
 *
 *  Returns 0 if the stream is currently writable, 1 if the core stream has 
 *  responded by not accepting the previous attempted write.
 */
int bwsIsEOF (const struct bwriteStream * ws) {
	if (NULL == ws || NULL == ws->buff || 0 > ws->minBuffSz || 
	    NULL == ws->writeFn) return BSTR_ERR;
	return ws->isEOF;
}

/*  int bwsBuffLength (struct bwriteStream * ws, int sz)
 *
 *  Set the length of the buffer used by the bwsStream.  If sz is zero, the 
 *  length is not set.  This function returns with the previous length.
 */
int bwsBuffLength (struct bwriteStream * ws, int sz) {
int oldSz;
	if (ws == NULL || sz < 0) return BSTR_ERR;
	oldSz = ws->minBuffSz;
	if (sz > 0) ws->minBuffSz = sz;
	return oldSz;
}

/*  void * bwsClose (struct bwriteStream * s)
 *
 *  Close the bwriteStream, and return the handle to the stream that was 
 *  originally used to open the given stream.  Note that even if the stream
 *  is at EOF it still needs to be closed with a call to bwsClose.
 */
void * bwsClose (struct bwriteStream * ws) {
void * parm;
	if (NULL == ws || NULL == ws->buff || 0 >= ws->minBuffSz || 
	    NULL == ws->writeFn) return NULL;
	bwsWriteFlush (ws);
	parm = ws->parm;
	ws->parm = NULL;
	ws->minBuffSz = -1;
	ws->writeFn = NULL;
	bstrFree (ws->buff);
	free (ws);
	return parm;
}
