fastdfs/common/base64.c

394 lines
9.9 KiB
C

/**
* Copyright (C) 2008 Happy Fish / YuQing
*
* FastDFS may be copied only under the terms of the GNU General
* Public License V3, which may be found in the FastDFS source kit.
* Please visit the FastDFS Home Page http://www.csource.org/ for more detail.
**/
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include "base64.h"
/**
* Marker value for chars we just ignore, e.g. \n \r high ascii
*/
#define BASE64_IGNORE -1
/**
* Marker for = trailing pad
*/
#define BASE64_PAD -2
/**
* determines how long the lines are that are generated by encode.
* Ignored by decode.
* @param length 0 means no newlines inserted. Must be a multiple of 4.
*/
void base64_set_line_length(struct base64_context *context, const int length)
{
context->line_length = (length / 4) * 4;
}
/**
* How lines are separated.
* Ignored by decode.
* @param context->line_separator may be "" but not null.
* Usually contains only a combination of chars \n and \r.
* Could be any chars not in set A-Z a-z 0-9 + /.
*/
void base64_set_line_separator(struct base64_context *context, \
const char *pLineSeparator)
{
context->line_sep_len = snprintf(context->line_separator, \
sizeof(context->line_separator), "%s", pLineSeparator);
}
void base64_init_ex(struct base64_context *context, const int nLineLength, \
const unsigned char chPlus, const unsigned char chSplash, \
const unsigned char chPad)
{
int i;
memset(context, 0, sizeof(struct base64_context));
context->line_length = nLineLength;
context->line_separator[0] = '\n';
context->line_separator[1] = '\0';
context->line_sep_len = 1;
// build translate valueToChar table only once.
// 0..25 -> 'A'..'Z'
for (i=0; i<=25; i++)
{
context->valueToChar[i] = (char)('A'+i);
}
// 26..51 -> 'a'..'z'
for (i=0; i<=25; i++ )
{
context->valueToChar[i+26] = (char)('a'+i);
}
// 52..61 -> '0'..'9'
for (i=0; i<=9; i++ )
{
context->valueToChar[i+52] = (char)('0'+i);
}
context->valueToChar[62] = chPlus;
context->valueToChar[63] = chSplash;
memset(context->charToValue, BASE64_IGNORE, sizeof(context->charToValue));
for (i=0; i<64; i++ )
{
context->charToValue[context->valueToChar[i]] = i;
}
context->pad_ch = chPad;
context->charToValue[chPad] = BASE64_PAD;
}
int base64_get_encode_length(struct base64_context *context, const int nSrcLen)
{
// Each group or partial group of 3 bytes becomes four chars
// covered quotient
int outputLength;
outputLength = ((nSrcLen + 2) / 3) * 4;
// account for trailing newlines, on all but the very last line
if (context->line_length != 0)
{
int lines = (outputLength + context->line_length - 1) /
context->line_length - 1;
if ( lines > 0 )
{
outputLength += lines * context->line_sep_len;
}
}
return outputLength;
}
/**
* Encode an arbitrary array of bytes as base64 printable ASCII.
* It will be broken into lines of 72 chars each. The last line is not
* terminated with a line separator.
* The output will always have an even multiple of data characters,
* exclusive of \n. It is padded out with =.
*/
char *base64_encode_ex(struct base64_context *context, const char *src, \
const int nSrcLen, char *dest, int *dest_len, const bool bPad)
{
int linePos;
int leftover;
int combined;
char *pDest;
int c0, c1, c2, c3;
unsigned char *pRaw;
unsigned char *pEnd;
const char *ppSrcs[2];
int lens[2];
char szPad[3];
int k;
int loop;
if (nSrcLen <= 0)
{
*dest = '\0';
*dest_len = 0;
return dest;
}
linePos = 0;
lens[0] = (nSrcLen / 3) * 3;
lens[1] = 3;
leftover = nSrcLen - lens[0];
ppSrcs[0] = src;
ppSrcs[1] = szPad;
szPad[0] = szPad[1] = szPad[2] = '\0';
switch (leftover)
{
case 0:
default:
loop = 1;
break;
case 1:
loop = 2;
szPad[0] = src[nSrcLen-1];
break;
case 2:
loop = 2;
szPad[0] = src[nSrcLen-2];
szPad[1] = src[nSrcLen-1];
break;
}
pDest = dest;
for (k=0; k<loop; k++)
{
pEnd = (unsigned char *)ppSrcs[k] + lens[k];
for (pRaw=(unsigned char *)ppSrcs[k]; pRaw<pEnd; pRaw+=3)
{
// Start a new line if next 4 chars won't fit on the current line
// We can't encapsulete the following code since the variable need to
// be local to this incarnation of encode.
linePos += 4;
if (linePos > context->line_length)
{
if (context->line_length != 0)
{
memcpy(pDest, context->line_separator, context->line_sep_len);
pDest += context->line_sep_len;
}
linePos = 4;
}
// get next three bytes in unsigned form lined up,
// in big-endian order
combined = ((*pRaw) << 16) | ((*(pRaw+1)) << 8) | (*(pRaw+2));
// break those 24 bits into a 4 groups of 6 bits,
// working LSB to MSB.
c3 = combined & 0x3f;
combined >>= 6;
c2 = combined & 0x3f;
combined >>= 6;
c1 = combined & 0x3f;
combined >>= 6;
c0 = combined & 0x3f;
// Translate into the equivalent alpha character
// emitting them in big-endian order.
*pDest++ = context->valueToChar[c0];
*pDest++ = context->valueToChar[c1];
*pDest++ = context->valueToChar[c2];
*pDest++ = context->valueToChar[c3];
}
}
*pDest = '\0';
*dest_len = pDest - dest;
// deal with leftover bytes
switch (leftover)
{
case 0:
default:
// nothing to do
break;
case 1:
// One leftover byte generates xx==
if (bPad)
{
*(pDest-1) = context->pad_ch;
*(pDest-2) = context->pad_ch;
}
else
{
*(pDest-2) = '\0';
*dest_len -= 2;
}
break;
case 2:
// Two leftover bytes generates xxx=
if (bPad)
{
*(pDest-1) = context->pad_ch;
}
else
{
*(pDest-1) = '\0';
*dest_len -= 1;
}
break;
} // end switch;
return dest;
}
char *base64_decode_auto(struct base64_context *context, const char *src, \
const int nSrcLen, char *dest, int *dest_len)
{
int nRemain;
int nPadLen;
int nNewLen;
char tmpBuff[256];
char *pBuff;
nRemain = nSrcLen % 4;
if (nRemain == 0)
{
return base64_decode(context, src, nSrcLen, dest, dest_len);
}
nPadLen = 4 - nRemain;
nNewLen = nSrcLen + nPadLen;
if (nNewLen <= sizeof(tmpBuff))
{
pBuff = tmpBuff;
}
else
{
pBuff = (char *)malloc(nNewLen);
if (pBuff == NULL)
{
fprintf(stderr, "Can't malloc %d bytes\n", \
nSrcLen + nPadLen + 1);
*dest_len = 0;
*dest = '\0';
return dest;
}
}
memcpy(pBuff, src, nSrcLen);
memset(pBuff + nSrcLen, context->pad_ch, nPadLen);
base64_decode(context, pBuff, nNewLen, dest, dest_len);
if (pBuff != tmpBuff)
{
free(pBuff);
}
return dest;
}
/**
* decode a well-formed complete base64 string back into an array of bytes.
* It must have an even multiple of 4 data characters (not counting \n),
* padded out with = as needed.
*/
char *base64_decode(struct base64_context *context, const char *src, \
const int nSrcLen, char *dest, int *dest_len)
{
// tracks where we are in a cycle of 4 input chars.
int cycle;
// where we combine 4 groups of 6 bits and take apart as 3 groups of 8.
int combined;
// will be an even multiple of 4 chars, plus some embedded \n
int dummies;
int value;
unsigned char *pSrc;
unsigned char *pSrcEnd;
char *pDest;
cycle = 0;
combined = 0;
dummies = 0;
pDest = dest;
pSrcEnd = (unsigned char *)src + nSrcLen;
for (pSrc=(unsigned char *)src; pSrc<pSrcEnd; pSrc++)
{
value = context->charToValue[*pSrc];
switch (value)
{
case BASE64_IGNORE:
// e.g. \n, just ignore it.
break;
case BASE64_PAD:
value = 0;
dummies++;
// fallthrough
default:
/* regular value character */
switch (cycle)
{
case 0:
combined = value;
cycle = 1;
break;
case 1:
combined <<= 6;
combined |= value;
cycle = 2;
break;
case 2:
combined <<= 6;
combined |= value;
cycle = 3;
break;
case 3:
combined <<= 6;
combined |= value;
// we have just completed a cycle of 4 chars.
// the four 6-bit values are in combined in big-endian order
// peel them off 8 bits at a time working lsb to msb
// to get our original 3 8-bit bytes back
*pDest++ = (char)(combined >> 16);
*pDest++ = (char)((combined & 0x0000FF00) >> 8);
*pDest++ = (char)(combined & 0x000000FF);
cycle = 0;
break;
}
break;
}
} // end for
if (cycle != 0)
{
*dest = '\0';
*dest_len = 0;
fprintf(stderr, "Input to decode not an even multiple of " \
"4 characters; pad with %c\n", context->pad_ch);
return dest;
}
*dest_len = (pDest - dest) - dummies;
*(dest + (*dest_len)) = '\0';
return dest;
}