443 lines
9.8 KiB
C
443 lines
9.8 KiB
C
/**
|
|
* \file base64.c
|
|
*
|
|
* Base64 encoding/decoding, code.
|
|
*/
|
|
|
|
/*
|
|
* Copyright (c) 2000-2001 Virtual Unlimited B.V.
|
|
*
|
|
* Author: Bob Deblier <bob@virtualunlimited.com>
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*
|
|
*/
|
|
|
|
#define BEECRYPT_DLL_EXPORT
|
|
|
|
#include "base64.h"
|
|
|
|
/*@unchecked@*/
|
|
static int _debug = 0;
|
|
|
|
#if HAVE_STDLIB_H
|
|
# include <stdlib.h>
|
|
#endif
|
|
#if HAVE_STRING_H
|
|
# include <string.h>
|
|
#endif
|
|
#if HAVE_CTYPE_H
|
|
# include <ctype.h>
|
|
#endif
|
|
#if HAVE_UNISTD_H
|
|
# include <unistd.h>
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
|
|
/*@observer@*/ /*@unchecked@*/
|
|
static const char* to_b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
|
|
/* encode 64 characters per line */
|
|
#define CHARS_PER_LINE 64
|
|
|
|
char* b64enc(const memchunk* chunk)
|
|
{
|
|
int div = chunk->size / 3;
|
|
int rem = chunk->size % 3;
|
|
int chars = div*4 + rem + 1;
|
|
int newlines = (chars + CHARS_PER_LINE - 1) / CHARS_PER_LINE;
|
|
|
|
const byte* data = chunk->data;
|
|
char* string = (char*) malloc(chars + newlines + 1);
|
|
|
|
if (string)
|
|
{
|
|
register char* buf = string;
|
|
|
|
chars = 0;
|
|
|
|
while (div > 0)
|
|
{
|
|
buf[0] = to_b64[ ((unsigned)data[0] >> 2) & 0x3f];
|
|
buf[1] = to_b64[(((unsigned)data[0] << 4) & 0x30) | (((unsigned)data[1] >> 4) & 0xf)];
|
|
buf[2] = to_b64[(((unsigned)data[1] << 2) & 0x3c) | (((unsigned)data[2] >> 6) & 0x3)];
|
|
buf[3] = to_b64[ (unsigned)data[2] & 0x3f];
|
|
data += 3;
|
|
buf += 4;
|
|
div--;
|
|
chars += 4;
|
|
if (chars == CHARS_PER_LINE)
|
|
{
|
|
chars = 0;
|
|
*(buf++) = '\n';
|
|
}
|
|
}
|
|
|
|
switch (rem)
|
|
{
|
|
case 2:
|
|
buf[0] = to_b64[ ((unsigned)data[0] >> 2) & 0x3f];
|
|
buf[1] = to_b64[(((unsigned)data[0] << 4) & 0x30) + (((unsigned)data[1] >> 4) & 0xf)];
|
|
buf[2] = to_b64[ ((unsigned)data[1] << 2) & 0x3c];
|
|
buf[3] = '=';
|
|
buf += 4;
|
|
chars += 4;
|
|
break;
|
|
case 1:
|
|
buf[0] = to_b64[ ((unsigned)data[0] >> 2) & 0x3f];
|
|
buf[1] = to_b64[ ((unsigned)data[0] << 4) & 0x30];
|
|
buf[2] = '=';
|
|
buf[3] = '=';
|
|
buf += 4;
|
|
chars += 4;
|
|
break;
|
|
}
|
|
|
|
/* *(buf++) = '\n'; This would result in a buffer overrun */
|
|
*buf = '\0';
|
|
}
|
|
|
|
return string;
|
|
}
|
|
|
|
memchunk* b64dec(const char* string)
|
|
{
|
|
/* return a decoded memchunk, or a null pointer in case of failure */
|
|
|
|
memchunk* rc = 0;
|
|
|
|
if (string)
|
|
{
|
|
register int length = strlen(string);
|
|
|
|
/* do a format verification first */
|
|
if (length > 0)
|
|
{
|
|
register int count = 0, rem = 0;
|
|
register const char* tmp = string;
|
|
|
|
while (length > 0)
|
|
{
|
|
register int skip = strspn(tmp, to_b64);
|
|
count += skip;
|
|
length -= skip;
|
|
tmp += skip;
|
|
if (length > 0)
|
|
{
|
|
register int i, vrfy = strcspn(tmp, to_b64);
|
|
|
|
for (i = 0; i < vrfy; i++)
|
|
{
|
|
if (isspace(tmp[i]))
|
|
/*@innercontinue@*/ continue;
|
|
|
|
if (tmp[i] == '=')
|
|
{
|
|
/* we should check if we're close to the end of the string */
|
|
rem = count % 4;
|
|
|
|
/* rem must be either 2 or 3, otherwise no '=' should be here */
|
|
if (rem < 2)
|
|
return 0;
|
|
|
|
/* end-of-message recognized */
|
|
/*@innerbreak@*/ break;
|
|
}
|
|
else
|
|
{
|
|
/* Transmission error; RFC tells us to ignore this, but:
|
|
* - the rest of the message is going to even more corrupt since we're sliding bits out of place
|
|
* If a message is corrupt, it should be dropped. Period.
|
|
*/
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
length -= vrfy;
|
|
tmp += vrfy;
|
|
}
|
|
}
|
|
|
|
rc = memchunkAlloc((count / 4) * 3 + (rem ? (rem - 1) : 0));
|
|
|
|
if (rc)
|
|
{
|
|
if (count > 0)
|
|
{
|
|
register int i, qw = 0, tw = 0;
|
|
register byte* data = rc->data;
|
|
|
|
length = strlen(tmp = string);
|
|
|
|
for (i = 0; i < length; i++)
|
|
{
|
|
register char ch = string[i];
|
|
register byte bits;
|
|
|
|
if (isspace(ch))
|
|
continue;
|
|
|
|
bits = 0;
|
|
if ((ch >= 'A') && (ch <= 'Z'))
|
|
{
|
|
bits = (byte) (ch - 'A');
|
|
}
|
|
else if ((ch >= 'a') && (ch <= 'z'))
|
|
{
|
|
bits = (byte) (ch - 'a' + 26);
|
|
}
|
|
else if ((ch >= '0') && (ch <= '9'))
|
|
{
|
|
bits = (byte) (ch - '0' + 52);
|
|
}
|
|
else if (ch == '+')
|
|
{
|
|
bits = 62;
|
|
}
|
|
else if (ch == '/')
|
|
{
|
|
bits = 63;
|
|
}
|
|
else if (ch == '=')
|
|
break;
|
|
else
|
|
{};
|
|
|
|
switch (qw++)
|
|
{
|
|
case 0:
|
|
data[tw+0] = (bits << 2) & 0xfc;
|
|
/*@switchbreak@*/ break;
|
|
case 1:
|
|
data[tw+0] |= (bits >> 4) & 0x03;
|
|
data[tw+1] = (bits << 4) & 0xf0;
|
|
/*@switchbreak@*/ break;
|
|
case 2:
|
|
data[tw+1] |= (bits >> 2) & 0x0f;
|
|
data[tw+2] = (bits << 6) & 0xc0;
|
|
/*@switchbreak@*/ break;
|
|
case 3:
|
|
data[tw+2] |= bits & 0x3f;
|
|
/*@switchbreak@*/ break;
|
|
}
|
|
|
|
if (qw == 4)
|
|
{
|
|
qw = 0;
|
|
tw += 3;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
/*@unchecked@*/
|
|
int b64encode_chars_per_line = B64ENCODE_CHARS_PER_LINE;
|
|
|
|
/*@observer@*/ /*@unchecked@*/
|
|
const char * b64encode_eolstr = B64ENCODE_EOLSTR;
|
|
|
|
/*@-internalglobs -modfilesys @*/
|
|
char * b64encode (const void * data, int ns)
|
|
{
|
|
static char b64enc[] =
|
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
const char *e;
|
|
const unsigned char *s = data;
|
|
unsigned char *t, *te;
|
|
int nt;
|
|
int lc;
|
|
unsigned c;
|
|
|
|
if (s == NULL) return NULL;
|
|
if (*s == '\0') return calloc(1, sizeof(*t));
|
|
|
|
if (ns == 0) ns = strlen(s);
|
|
nt = ((ns + 2) / 3) * 4;
|
|
|
|
/* Add additional bytes necessary for eol string(s). */
|
|
if (b64encode_chars_per_line > 0 && b64encode_eolstr != NULL) {
|
|
lc = (nt + b64encode_chars_per_line - 1) / b64encode_chars_per_line;
|
|
if (((nt + b64encode_chars_per_line - 1) % b64encode_chars_per_line) != 0)
|
|
++lc;
|
|
nt += lc * strlen(b64encode_eolstr);
|
|
}
|
|
|
|
t = te = malloc(nt + 1);
|
|
|
|
lc = 0;
|
|
if (te)
|
|
while (ns) {
|
|
|
|
if (_debug)
|
|
fprintf(stderr, "%7u %02x %02x %02x -> %02x %02x %02x %02x\n",
|
|
(unsigned)ns, (unsigned)s[0], (unsigned)s[1], (unsigned)s[2],
|
|
(unsigned)(s[0] >> 2),
|
|
(unsigned)((s[0] & 0x3) << 4) | (s[1] >> 4),
|
|
(unsigned)((s[1] & 0xf) << 2) | (s[2] >> 6),
|
|
(unsigned)(s[2]& 0x3f));
|
|
c = *s++;
|
|
*te++ = b64enc[ (c >> 2) ], lc++;
|
|
*te++ = b64enc[ ((c & 0x3) << 4) | (*s >> 4) ], lc++;
|
|
if (--ns == 0) {
|
|
*te++ = '=';
|
|
*te++ = '=';
|
|
continue;
|
|
}
|
|
c = *s++;
|
|
*te++ = b64enc[ ((c & 0xf) << 2) | (*s >> 6) ], lc++;
|
|
if (--ns == 0) {
|
|
*te++ = '=';
|
|
continue;
|
|
}
|
|
*te++ = b64enc[ (int)(*s & 0x3f) ], lc++;
|
|
|
|
/* Append eol string if desired. */
|
|
if (b64encode_chars_per_line > 0 && b64encode_eolstr != NULL) {
|
|
if (lc >= b64encode_chars_per_line) {
|
|
for (e = b64encode_eolstr; *e != '\0'; e++)
|
|
*te++ = *e;
|
|
lc = 0;
|
|
}
|
|
}
|
|
s++;
|
|
--ns;
|
|
}
|
|
|
|
if (te) {
|
|
/* Append eol string if desired. */
|
|
if (b64encode_chars_per_line > 0 && b64encode_eolstr != NULL) {
|
|
if (lc != 0) {
|
|
for (e = b64encode_eolstr; *e != '\0'; e++)
|
|
*te++ = *e;
|
|
}
|
|
}
|
|
*te = '\0';
|
|
}
|
|
|
|
/*@-mustfree -compdef @*/
|
|
return t;
|
|
/*@=mustfree =compdef @*/
|
|
}
|
|
/*@=internalglobs =modfilesys @*/
|
|
|
|
/*@observer@*/ /*@unchecked@*/
|
|
const char * b64decode_whitespace = B64DECODE_WHITESPACE;
|
|
|
|
/*@-internalglobs -modfilesys @*/
|
|
int b64decode (const char * s, void ** datap, int *lenp)
|
|
{
|
|
unsigned char b64dec[256];
|
|
const unsigned char *t;
|
|
unsigned char *te;
|
|
int ns, nt;
|
|
unsigned a, b, c, d;
|
|
|
|
if (s == NULL) return 1;
|
|
|
|
/* Setup character lookup tables. */
|
|
memset(b64dec, 0x80, sizeof(b64dec));
|
|
for (c = 'A'; c <= 'Z'; c++)
|
|
b64dec[ c ] = 0 + (c - 'A');
|
|
for (c = 'a'; c <= 'z'; c++)
|
|
b64dec[ c ] = 26 + (c - 'a');
|
|
for (c = '0'; c <= '9'; c++)
|
|
b64dec[ c ] = 52 + (c - '0');
|
|
b64dec[(unsigned)'+'] = 62;
|
|
b64dec[(unsigned)'/'] = 63;
|
|
b64dec[(unsigned)'='] = 0;
|
|
|
|
/* Mark whitespace characters. */
|
|
if (b64decode_whitespace) {
|
|
const char *e;
|
|
for (e = b64decode_whitespace; *e != '\0'; e++) {
|
|
if (b64dec[ (unsigned)*e ] == 0x80)
|
|
b64dec[ (unsigned)*e ] = 0x81;
|
|
}
|
|
}
|
|
|
|
/* Validate input buffer */
|
|
ns = 0;
|
|
for (t = s; *t != '\0'; t++) {
|
|
switch (b64dec[ (unsigned)*t ]) {
|
|
case 0x80: /* invalid chararcter */
|
|
if (_debug)
|
|
fprintf(stderr, "--- b64decode %c(%02x) %02x\n", *t, (unsigned)(*t & 0xff), (unsigned)b64dec[ (unsigned)*t ]);
|
|
return 3;
|
|
/*@notreached@*/ /*@switchbreak@*/ break;
|
|
case 0x81: /* white space */
|
|
/*@switchbreak@*/ break;
|
|
default:
|
|
ns++;
|
|
/*@switchbreak@*/ break;
|
|
}
|
|
}
|
|
|
|
if (ns & 0x3) return 2;
|
|
|
|
nt = (ns / 4) * 3;
|
|
t = te = malloc(nt + 1);
|
|
|
|
while (ns > 0) {
|
|
|
|
/* Get next 4 characters, ignoring whitespace. */
|
|
while ((a = b64dec[ (unsigned)*s++ ]) == 0x81)
|
|
{};
|
|
while ((b = b64dec[ (unsigned)*s++ ]) == 0x81)
|
|
{};
|
|
while ((c = b64dec[ (unsigned)*s++ ]) == 0x81)
|
|
{};
|
|
while ((d = b64dec[ (unsigned)*s++ ]) == 0x81)
|
|
{};
|
|
|
|
if (_debug)
|
|
fprintf(stderr, "%7u %02x %02x %02x %02x -> %02x %02x %02x\n",
|
|
(unsigned)ns, a, b, c, d,
|
|
(((a << 2) | (b >> 4)) & 0xff),
|
|
(((b << 4) | (c >> 2)) & 0xff),
|
|
(((c << 6) | d) & 0xff));
|
|
|
|
ns -= 4;
|
|
*te++ = (a << 2) | (b >> 4);
|
|
if (s[-2] == '=') break;
|
|
*te++ = (b << 4) | (c >> 2);
|
|
if (s[-1] == '=') break;
|
|
*te++ = (c << 6) | d;
|
|
}
|
|
|
|
if (ns != 0) { /* XXX can't happen, just in case */
|
|
if (t) free((void *)t);
|
|
return 1;
|
|
}
|
|
if (lenp)
|
|
*lenp = (te - t);
|
|
|
|
if (datap)
|
|
*datap = (void *)t;
|
|
else
|
|
if (t) free((void *)t);
|
|
else
|
|
{};
|
|
|
|
return 0;
|
|
}
|
|
/*@=internalglobs =modfilesys @*/
|