File:  [ELWIX - Embedded LightWeight unIX -] / embedaddon / libpdel / io / base64.c
Revision 1.1: download - view: text, annotated - select for diffs - revision graph
Tue Feb 21 23:25:53 2012 UTC (12 years, 4 months ago) by misho
CVS tags: MAIN, HEAD
Initial revision


/*
 * Copyright (c) 2001-2002 Packet Design, LLC.
 * All rights reserved.
 * 
 * Subject to the following obligations and disclaimer of warranty,
 * use and redistribution of this software, in source or object code
 * forms, with or without modifications are expressly permitted by
 * Packet Design; provided, however, that:
 * 
 *    (i)  Any and all reproductions of the source or object code
 *         must include the copyright notice above and the following
 *         disclaimer of warranties; and
 *    (ii) No rights are granted, in any manner or form, to use
 *         Packet Design trademarks, including the mark "PACKET DESIGN"
 *         on advertising, endorsements, or otherwise except as such
 *         appears in the above copyright notice or in the software.
 * 
 * THIS SOFTWARE IS BEING PROVIDED BY PACKET DESIGN "AS IS", AND
 * TO THE MAXIMUM EXTENT PERMITTED BY LAW, PACKET DESIGN MAKES NO
 * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING
 * THIS SOFTWARE, INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED
 * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE,
 * OR NON-INFRINGEMENT.  PACKET DESIGN DOES NOT WARRANT, GUARANTEE,
 * OR MAKE ANY REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS
 * OF THE USE OF THIS SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY,
 * RELIABILITY OR OTHERWISE.  IN NO EVENT SHALL PACKET DESIGN BE
 * LIABLE FOR ANY DAMAGES RESULTING FROM OR ARISING OUT OF ANY USE
 * OF THIS SOFTWARE, INCLUDING WITHOUT LIMITATION, ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, PUNITIVE, OR CONSEQUENTIAL
 * DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, LOSS OF
 * USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
 * THE USE OF THIS SOFTWARE, EVEN IF PACKET DESIGN IS ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Author: Archie Cobbs <archie@freebsd.org>
 */

#include <sys/types.h>
#include <sys/param.h>

#include <assert.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <pthread.h>

#include "structs/structs.h"
#include "structs/type/array.h"

#include "io/base64.h"
#include "io/filter.h"
#include "util/typed_mem.h"

/************************************************************************
			BASE 64 ENCODER
************************************************************************/

#define ENCODER_MEM_TYPE	"base64_encoder"

/* Encoder state */
struct b64_encoder {
	struct filter	filter;
	pthread_mutex_t	mutex;
	char		*cmap;
	u_char		dbuf[3];
	int		doff;
	char		*obuf;
	int		olen;
	int		osize;
	u_char		done;
};

/* Internal functions */
static filter_read_t		b64_encoder_read;
static filter_write_t		b64_encoder_write;
static filter_end_t		b64_encoder_end;
static filter_convert_t		b64_encoder_convert;
static filter_destroy_t		b64_encoder_destroy;

static void	b64_encoder_encode(struct b64_encoder *enc, char *buf);
static int	b64_encoder_output(struct b64_encoder *enc, const char *buf);
static int	b64_cmap_check(const char *cmap);

/* Public variables */
const char b64_rfc2045_charset[] =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";

/*
 * Create a new base-64 encoder with optional custom character set.
 */
struct filter *
b64_encoder_create(const char *cmap)
{
	struct b64_encoder *enc;

	/* Default to HTTP charset */
	if (cmap == NULL)
		cmap = b64_rfc2045_charset;
	else if (b64_cmap_check(cmap) == -1)
		return (NULL);

	/* Create object */
	if ((enc = MALLOC(ENCODER_MEM_TYPE, sizeof(*enc))) == NULL)
		return (NULL);
	memset(enc, 0, sizeof(*enc));

	/* Copy character map */
	if ((enc->cmap = STRDUP(ENCODER_MEM_TYPE, cmap)) == NULL) {
		FREE(ENCODER_MEM_TYPE, enc);
		return (NULL);
	}

	/* Create mutex */
	if ((errno = pthread_mutex_init(&enc->mutex, NULL)) != 0) {
		FREE(ENCODER_MEM_TYPE, enc->cmap);
		FREE(ENCODER_MEM_TYPE, enc);
		return (NULL);
	}

	/* Set up methods */
	enc->filter.read = b64_encoder_read;
	enc->filter.write = b64_encoder_write;
	enc->filter.end = b64_encoder_end;
	enc->filter.convert = b64_encoder_convert;
	enc->filter.destroy = b64_encoder_destroy;

	/* Done */
	return (&enc->filter);
}

/*
 * Destroy a base 64 encoder.
 */
void
b64_encoder_destroy(struct filter **filterp)
{
	struct b64_encoder **const encp = (struct b64_encoder **)filterp;
	struct b64_encoder *const enc = *encp;

	if (enc != NULL) {
		pthread_mutex_destroy(&enc->mutex);
		FREE(ENCODER_MEM_TYPE, enc->cmap);
		FREE(ENCODER_MEM_TYPE, enc->obuf);
		FREE(ENCODER_MEM_TYPE, enc);
		*encp = NULL;
	}
}

/*
 * Write raw bytes into the encoder.
 */
int
b64_encoder_write(struct filter *filter, const void *data, int len)
{
	struct b64_encoder *const enc = (struct b64_encoder *)filter;
	char buf[4];
	int total;
	int chunk;
	int r;

	/* Lock encoder */
	r = pthread_mutex_lock(&enc->mutex);
	assert(r == 0);

	/* Check if closed */
	if (enc->done) {
		r = pthread_mutex_unlock(&enc->mutex);
		assert(r == 0);
		errno = EPIPE;
		return (-1);
	}

	/* Process bytes */
	for (total = 0; len > 0; total += chunk) {

		/* Read in next chunk of raw data */
		chunk = MIN(len, 3 - enc->doff);
		memcpy(enc->dbuf + enc->doff, data, chunk);
		data = (char *)data + chunk;
		len -= chunk;

		/* Process data if data buffer full */
		if ((enc->doff += chunk) == 3) {
			b64_encoder_encode(enc, buf);
			if (b64_encoder_output(enc, buf) == -1) {
				total = (total == 0) ? -1 : total;
				break;
			}
			enc->doff = 0;
		}

		/* Unlock encoder in case there is a reader */
		r = pthread_mutex_unlock(&enc->mutex);
		assert(r == 0);
		r = pthread_mutex_lock(&enc->mutex);
		assert(r == 0);
	}

	/* Done */
	r = pthread_mutex_unlock(&enc->mutex);
	assert(r == 0);
	return (total);
}

/*
 * Read out encoded data.
 */
int
b64_encoder_read(struct filter *filter, void *data, int len)
{
	struct b64_encoder *const enc = (struct b64_encoder *)filter;
	int r;

	r = pthread_mutex_lock(&enc->mutex);
	assert(r == 0);
	len = MIN(len, enc->olen);
	memcpy(data, enc->obuf, len);
	memmove(enc->obuf, enc->obuf + len, enc->olen - len);
	enc->olen -= len;
	r = pthread_mutex_unlock(&enc->mutex);
	assert(r == 0);
	return (len);
}

/*
 * Mark end of written data.
 */
int
b64_encoder_end(struct filter *filter)
{
	struct b64_encoder *const enc = (struct b64_encoder *)filter;
	char buf[4];
	int r;

	/* Lock encoder */
	r = pthread_mutex_lock(&enc->mutex);
	assert(r == 0);

	/* Only do this once */
	if (enc->done)
		goto done;

	/* Pad with termination character(s) */
	if (enc->doff > 0) {
		memset(enc->dbuf + enc->doff, 0, 3 - enc->doff);
		b64_encoder_encode(enc, buf);
		switch (enc->doff) {
		case 1:
			buf[2] = enc->cmap[64];
			/* fall through */
		case 2:
			buf[3] = enc->cmap[64];
			break;
		}
		if (b64_encoder_output(enc, buf) == -1) {
			r = pthread_mutex_unlock(&enc->mutex);
			assert(r == 0);
			return (-1);
		}
	}

done:
	/* Done */
	enc->done = 1;
	r = pthread_mutex_unlock(&enc->mutex);
	assert(r == 0);
	return (0);
}

/*
 * Convert byte count to read before and after filter.
 */
static int
b64_encoder_convert(struct filter *filter, int num, int forward)
{
	if (forward)
		return (((num * 4) + 2) / 3);
	else
		return (((num * 3) + 3) / 4);
}

/*
 * Encode one chunk of data (3 bytes -> 4 characters).
 *
 * This assumes the encoder is locked.
 */
static void
b64_encoder_encode(struct b64_encoder *enc, char *buf)
{
	buf[0] = (enc->dbuf[0] >> 2) & 0x3f;
	buf[0] = enc->cmap[(u_char)buf[0]];
	buf[1] = ((enc->dbuf[0] << 4) & 0x30) | ((enc->dbuf[1] >> 4) & 0x0f);
	buf[1] = enc->cmap[(u_char)buf[1]];
	buf[2] = ((enc->dbuf[1] << 2) & 0x3c) | ((enc->dbuf[2] >> 6) & 0x03);
	buf[2] = enc->cmap[(u_char)buf[2]];
	buf[3] = enc->dbuf[2] & 0x3f;
	buf[3] = enc->cmap[(u_char)buf[3]];
}

/*
 * Append encoded characters to the output buffer.
 *
 * This assumes the encoder is locked.
 */
static int
b64_encoder_output(struct b64_encoder *enc, const char *buf)
{
	if (enc->olen + 4 > enc->osize) {
		const int new_osize = (enc->olen * 2) + 31;
		char *new_obuf;

		if ((new_obuf = REALLOC(ENCODER_MEM_TYPE,
		    enc->obuf, new_osize)) == NULL)
			return (-1);
		enc->obuf = new_obuf;
		enc->osize = new_osize;
	}
	memcpy(enc->obuf + enc->olen, buf, 4);
	enc->olen += 4;
	return (0);
}

/*
 * Sanity check character map.
 */
static int
b64_cmap_check(const char *cmap)
{
	int i;
	int j;

	if (strlen(cmap) != 65)
		goto bogus;
	for (i = 0; i < 64; i++) {
		for (j = i + 1; j < 65; j++) {
			if (cmap[i] == cmap[j]) {
bogus:				errno = EINVAL;
				return (-1);
			}
		}
	}
	return (0);
}

/************************************************************************
			BASE 64 DECODER
************************************************************************/

#define DECODER_MEM_TYPE	"base64_decoder"

/* Encoder state */
struct b64_decoder {
	struct filter	filter;
	pthread_mutex_t	mutex;
	u_char		cmap[256];
	u_char		cbuf[4];
	int		coff;
	u_char		*obuf;
	int		olen;
	int		osize;
	char		pad;
	u_char		done;
	u_char		strict;
};

/* Internal functions */
static filter_read_t		b64_decoder_read;
static filter_write_t		b64_decoder_write;
static filter_end_t		b64_decoder_end;
static filter_convert_t		b64_decoder_convert;
static filter_destroy_t		b64_decoder_destroy;

static void	b64_decoder_decode(struct b64_decoder *dec, u_char *buf);
static int	b64_decoder_output(struct b64_decoder *dec,
			u_char *buf, int len);

/*
 * Create a new base-64 decoder with optional custom character set.
 */
struct filter *
b64_decoder_create(const char *cmap, int strict)
{
	struct b64_decoder *dec;
	const char *s;
	u_char byte;
	int i;

	/* Default to HTTP charset */
	if (cmap == NULL)
		cmap = b64_rfc2045_charset;
	else if (b64_cmap_check(cmap) == -1)
		return (NULL);

	/* Create object */
	if ((dec = MALLOC(DECODER_MEM_TYPE, sizeof(*dec))) == NULL)
		return (NULL);
	memset(dec, 0, sizeof(*dec));
	dec->strict = !!strict;
	dec->pad = cmap[64];

	/* Create mutex */
	if ((errno = pthread_mutex_init(&dec->mutex, NULL)) != 0) {
		FREE(DECODER_MEM_TYPE, dec);
		return (NULL);
	}

	/* Create inverse character map */
	memset(dec->cmap, 0xff, sizeof(dec->cmap));
	for (i = 1; i < sizeof(dec->cmap); i++) {
		if ((s = strchr(cmap, (char)i)) != NULL) {
			byte = (u_char)(s - cmap);
			dec->cmap[i] = (byte == 64) ? 0xfe : byte;
		}
	}

	/* Set up methods */
	dec->filter.read = b64_decoder_read;
	dec->filter.write = b64_decoder_write;
	dec->filter.end = b64_decoder_end;
	dec->filter.convert = b64_decoder_convert;
	dec->filter.destroy = b64_decoder_destroy;

	/* Done */
	return (&dec->filter);
}

/*
 * Destroy a base 64 decoder.
 */
void
b64_decoder_destroy(struct filter **filterp)
{
	struct b64_decoder **const decp = (struct b64_decoder **)filterp;
	struct b64_decoder *const dec = *decp;

	if (dec != NULL) {
		pthread_mutex_destroy(&dec->mutex);
		FREE(DECODER_MEM_TYPE, dec->obuf);
		FREE(DECODER_MEM_TYPE, dec);
		*decp = NULL;
	}
}

/*
 * Write encoded characters into the decoder.
 */
int
b64_decoder_write(struct filter *filter, const void *data, int len)
{
	struct b64_decoder *const dec = (struct b64_decoder *)filter;
	u_char buf[3];
	int total = 0;
	int r;

	/* Lock decoder */
	r = pthread_mutex_lock(&dec->mutex);
	assert(r == 0);

	/* Check if closed */
	if (dec->done) {
		r = pthread_mutex_unlock(&dec->mutex);
		assert(r == 0);
		errno = EPIPE;
		return (-1);
	}

	/* Process bytes */
	while (total < len) {

		/* Fill up input buffer */
		for ( ; dec->coff < 4 && total < len; total++) {
			const u_char val = dec->cmap[((u_char *)data)[total]];

			if (val == 0xff) {
				if (dec->strict) {
					errno = EINVAL;
					total = (total == 0) ? -1 : total;
					goto done;
				}
				continue;
			}
			if (val == 0xfe)			/* pad char */
				continue;
			assert(val < 64);
			dec->cbuf[dec->coff++] = val;
		}

		/* Process characters if character buffer is full */
		if (dec->coff == 4) {
			b64_decoder_decode(dec, buf);
			if (b64_decoder_output(dec, buf, 3) == -1) {
				total = (total == 0) ? -1 : total;
				goto done;
			}
			dec->coff = 0;
		}

		/* Take a breather */
		r = pthread_mutex_unlock(&dec->mutex);
		assert(r == 0);
		r = pthread_mutex_lock(&dec->mutex);
		assert(r == 0);
	}

done:
	/* Done */
	r = pthread_mutex_unlock(&dec->mutex);
	assert(r == 0);
	return (total);
}

/*
 * Read out decoded data.
 */
int
b64_decoder_read(struct filter *filter, void *buf, int len)
{
	struct b64_decoder *const dec = (struct b64_decoder *)filter;
	int r;

	r = pthread_mutex_lock(&dec->mutex);
	assert(r == 0);
	len = MIN(len, dec->olen);
	memcpy(buf, dec->obuf, len);
	memmove(dec->obuf, dec->obuf + len, dec->olen - len);
	dec->olen -= len;
	r = pthread_mutex_unlock(&dec->mutex);
	assert(r == 0);
	return (len);
}

/*
 * Mark end of written data.
 */
int
b64_decoder_end(struct filter *filter)
{
	struct b64_decoder *const dec = (struct b64_decoder *)filter;
	u_char buf[3];
	int len;
	int r;

	/* Lock decoder */
	r = pthread_mutex_lock(&dec->mutex);
	assert(r == 0);

	/* Only do this once */
	if (dec->done)
		goto done;

	/* Spit out any trailing characters */
	if (dec->coff > 0) {
		switch (dec->coff) {
		case 1:				/* really not valid */
		case 2:
			len = 1;
			break;
		case 3:
			len = 2;
			break;
		default:
			assert(0);
			len = 0;		/* silence gcc warning */
		}
		memset(dec->cbuf + dec->coff, 0, 4 - dec->coff);
		b64_decoder_decode(dec, buf);
		if (b64_decoder_output(dec, buf, len) == -1) {
			r = pthread_mutex_unlock(&dec->mutex);
			assert(r == 0);
			return (-1);
		}
		dec->coff = 0;
	}

done:
	/* Done */
	dec->done = 1;
	r = pthread_mutex_unlock(&dec->mutex);
	assert(r == 0);
	return (0);
}

/*
 * Convert byte count to read before and after filter.
 */
static int
b64_decoder_convert(struct filter *filter, int num, int forward)
{
	if (forward)
		return (((num * 3) + 3) / 4);
	else
		return (((num * 4) + 2) / 3);
}

/*
 * Decode one chunk of characters (4 characters -> 3 bytes).
 *
 * This assumes the decoder is locked.
 */
static void
b64_decoder_decode(struct b64_decoder *dec, u_char *buf)
{
	buf[0] = (dec->cbuf[0] << 2) | (dec->cbuf[1] >> 4);
	buf[1] = (dec->cbuf[1] << 4) | (dec->cbuf[2] >> 2);
	buf[2] = (dec->cbuf[2] << 6) | dec->cbuf[3];
}

/*
 * Append decoded characters to the output buffer.
 *
 * This assumes the decoder is locked.
 */
static int
b64_decoder_output(struct b64_decoder *dec, u_char *buf, int len)
{
	if (dec->olen + len > dec->osize) {
		const int new_osize = (dec->olen * 2) + 31;
		u_char *new_obuf;

		if ((new_obuf = REALLOC(DECODER_MEM_TYPE,
		    dec->obuf, new_osize)) == NULL)
			return (-1);
		dec->obuf = new_obuf;
		dec->osize = new_osize;
	}
	memcpy(dec->obuf + dec->olen, buf, len);
	dec->olen += len;
	return (0);
}

#ifdef BASE64_TEST

#include <err.h>
#include <unistd.h>

int
main(int ac, char **av)
{
	struct filter *filter;
	int do_encode = 1;			/* default encode */
	int do_input = 1;			/* default test input stream */
	int strict = 0;
	char buf[123];
	FILE *input;
	FILE *output;
	int len;
	int ch;

	/* Process command line */
	while ((ch = getopt(ac, av, "deiso")) != -1) {
		switch (ch) {
		case 'd':			/* decode */
			do_encode = 0;
			break;
		case 'e':			/* encode */
			do_encode = 1;
			break;
		case 'i':			/* test input stream */
			do_input = 1;
			break;
		case 'o':			/* test output stream */
			do_input = 0;
			break;
		case 's':			/* enforce strict decoding */
			strict = 1;
			break;
		default:
		usage:
			errx(1, "usage: base64 <-e|-d> <-i|-o> [-s]");
		}
	}
	ac -= optind;
	av += optind;

	/* Sanity */
	if (do_input == -1 || do_encode == -1)
		goto usage;

	/* Get filter */
	if (do_encode) {
		if ((filter = b64_encoder_create(NULL)) == NULL)
			err(1, "b64_encoder_create");
	} else {
		if ((filter = b64_decoder_create(NULL, strict)) == NULL)
			err(1, "b64_decoder_create");
	}

	/* Get filter stream */
	if (do_input) {
		if ((output = filter_fopen(filter, 0, stdout, "w")) == NULL)
			err(1, "filter_fopen");
		input = stdin;
	} else {
		if ((input = filter_fopen(filter, 0, stdin, "r")) == NULL)
			err(1, "filter_fopen");
		output = stdout;
	}

	/* Convert */
	while ((len = fread(buf, 1, sizeof(buf), input)) != 0)
		fwrite(buf, 1, len, output);

	/* Done */
	fclose(output);
	return (0);
}

#endif	/* BASE64_TEST */


FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>