/**
 * XML Security Library (http://www.aleksey.com/xmlsec).
 *
 * Base64 encode/decode transform and utility functions.
 *
 * This is free software; see Copyright file in the source
 * distribution for preciese wording.
 * 
 * Copyright (C) 2002-2003 Aleksey Sanin <aleksey@aleksey.com>
 * Portion Copyright © 2009 Nokia Corporation and/or its subsidiary(-ies). All rights reserved. 
 */
#include "xmlsec_globals.h"

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include <libxml2_tree.h>
#include <libxml2_globals.h>


#include "xmlsec_xmlsec.h"
#include "xmlsec_keys.h"
#include "xmlsec_transforms.h"
#include "xmlsec_base64.h"
#include "xmlsec_errors.h"


/* 
 * the table to map numbers to base64 
 */
static const xmlSecByte base64[] =
{  
/*   0    1    2    3    4    5    6    7   */
    'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', /* 0 */
    'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', /* 1 */
    'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', /* 2 */
    'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', /* 3 */
    'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', /* 4 */
    'o', 'p', 'q', 'r', 's', 't', 'u', 'v', /* 5 */
    'w', 'x', 'y', 'z', '0', '1', '2', '3', /* 6 */
    '4', '5', '6', '7', '8', '9', '+', '/'  /* 7 */
};


/* few macros to simplify the code */
#define xmlSecBase64Encode1(a) 		(((a) >> 2) & 0x3F)
#define xmlSecBase64Encode2(a, b) 	((((a) << 4) & 0x30) + (((b) >> 4) & 0x0F))
#define xmlSecBase64Encode3(b, c) 	((((b) << 2) & 0x3c) + (((c) >> 6) & 0x03))
#define xmlSecBase64Encode4(c)		((c) & 0x3F)

#define xmlSecBase64Decode1(a, b)	(((a) << 2) | (((b) & 0x3F) >> 4))
#define xmlSecBase64Decode2(b, c)	(((b) << 4) | (((c) & 0x3F) >> 2))
#define xmlSecBase64Decode3(c, d)	(((c) << 6) | ((d) & 0x3F))
	
#define xmlSecIsBase64Char(ch) 		((((ch) >= 'A') && ((ch) <= 'Z')) || \
					 (((ch) >= 'a') && ((ch) <= 'z')) || \
					 (((ch) >= '0') && ((ch) <= '9')) || \
					 ((ch) == '+') || ((ch) == '/')) 
#define xmlSecIsBase64Space(ch)		(((ch) == ' ') || ((ch) == '\t') || \
					 ((ch) == '\x0d') || ((ch) == '\x0a'))



/***********************************************************************
 *
 * Base64 Context
 *
 ***********************************************************************/
typedef enum {
    xmlSecBase64StatusConsumeAndNext  = 0,
    xmlSecBase64StatusConsumeAndRepeat,
    xmlSecBase64StatusNext,
    xmlSecBase64StatusDone,
    xmlSecBase64StatusFailed
} xmlSecBase64Status;

struct _xmlSecBase64Ctx {
    int			encode;    
    int                 inByte;
    int                 inPos;
    xmlSecSize		linePos;
    xmlSecSize		columns;  
    int                 finished;
};

static xmlSecBase64Status	xmlSecBase64CtxEncodeByte	(xmlSecBase64CtxPtr ctx, 
								 xmlSecByte  inByte, 
								 xmlSecByte* outByte);
static xmlSecBase64Status	xmlSecBase64CtxEncodeByteFinal	(xmlSecBase64CtxPtr ctx, 
								 xmlSecByte* outByte);
static xmlSecBase64Status	xmlSecBase64CtxDecodeByte	(xmlSecBase64CtxPtr ctx, 
								 xmlSecByte inByte, 
								 xmlSecByte* outByte);
static int			xmlSecBase64CtxEncode		(xmlSecBase64CtxPtr ctx, 
                						 const xmlSecByte* inBuf, 
								 xmlSecSize inBufSize, 
								 xmlSecSize* inBufResSize,
                						 xmlSecByte* outBuf, 
								 xmlSecSize outBufSize, 
								 xmlSecSize* outBufResSize);
static int			xmlSecBase64CtxEncodeFinal	(xmlSecBase64CtxPtr ctx, 
                						 xmlSecByte* outBuf, 
								 xmlSecSize outBufSize, 
								 xmlSecSize* outBufResSize);
static int			xmlSecBase64CtxDecode		(xmlSecBase64CtxPtr ctx, 
                						 const xmlSecByte* inBuf, 
								 xmlSecSize inBufSize, 
								 xmlSecSize* inBufResSize,
                						 xmlSecByte* outBuf, 
								 xmlSecSize outBufSize, 
								 xmlSecSize* outBufResSize);
static int			xmlSecBase64CtxDecodeIsFinished	(xmlSecBase64CtxPtr ctx);

/**
 * xmlSecBase64CtxCreate:
 * @encode:		the encode/decode flag (1 - encode, 0 - decode) 
 * @columns: 		the max line length.
 *
 * Allocates and initializes new base64 context.
 *
 * Returns a pointer to newly created #xmlSecBase64Ctx structure
 * or NULL if an error occurs.
 */
EXPORT_C
xmlSecBase64CtxPtr	
xmlSecBase64CtxCreate(int encode, int columns) {
    xmlSecBase64CtxPtr ctx;
    int ret;
    
    /*
     * Allocate a new xmlSecBase64CtxPtr and fill the fields.
     */
    ctx = (xmlSecBase64CtxPtr) xmlMalloc(sizeof(xmlSecBase64Ctx));    
    if (ctx == NULL) {
	xmlSecError(XMLSEC_ERRORS_HERE,
		    NULL,
		    NULL,
		    XMLSEC_ERRORS_R_MALLOC_FAILED,
		    "sizeof(xmlSecBase64Ctx)=%d", 
		    sizeof(xmlSecBase64Ctx));
	return(NULL);
    }
    
    ret = xmlSecBase64CtxInitialize(ctx, encode, columns);
    if(ret < 0) {
	xmlSecError(XMLSEC_ERRORS_HERE,
		    NULL,
		    "xmlSecBase64CtxInitialize",
		    XMLSEC_ERRORS_R_XMLSEC_FAILED,
		    XMLSEC_ERRORS_NO_MESSAGE);
	xmlSecBase64CtxDestroy(ctx);
	return(NULL);
    }
    return(ctx);
}

/**
 * xmlSecBase64CtxDestroy:
 * @ctx: 		the pointer to #xmlSecBase64Ctx structure.
 * 
 * Destroys base64 context.
 */
EXPORT_C
void
xmlSecBase64CtxDestroy(xmlSecBase64CtxPtr ctx) {
    xmlSecAssert(ctx != NULL);
    
    xmlSecBase64CtxFinalize(ctx);
    xmlFree(ctx);
}

/**
 * xmlSecBase64CtxInitialize:
 * @ctx:		the pointer to #xmlSecBase64Ctx structure,
 * @encode:		the encode/decode flag (1 - encode, 0 - decode) 
 * @columns: 		the max line length.
 *
 * Initializes new base64 context.
 *
 * Returns 0 on success and a negative value otherwise.
 */
EXPORT_C
int 
xmlSecBase64CtxInitialize(xmlSecBase64CtxPtr ctx, int encode, int columns) {    
    xmlSecAssert2(ctx != NULL, -1);

    memset(ctx, 0, sizeof(xmlSecBase64Ctx));

    ctx->encode     = encode;
    ctx->columns    = columns;
    return(0);
}

/**
 * xmlSecBase64CtxFinalize:
 * @ctx:		the pointer to #xmlSecBase64Ctx structure,
 *
 * Frees all the resources allocated by @ctx.
 */
EXPORT_C
void 
xmlSecBase64CtxFinalize(xmlSecBase64CtxPtr ctx) {    
    xmlSecAssert(ctx != NULL);

    memset(ctx, 0, sizeof(xmlSecBase64Ctx)); 
}	

/**
 * xmlSecBase64CtxUpdate:
 * @ctx: 		the pointer to #xmlSecBase64Ctx structure
 * @in:			the input buffer
 * @inSize: 		the input buffer size
 * @out: 		the output buffer
 * @outSize: 		the output buffer size
 *
 * Encodes or decodes the next piece of data from input buffer.
 * 
 * Returns the number of bytes written to output buffer or 
 * -1 if an error occurs.
 */
EXPORT_C
int
xmlSecBase64CtxUpdate(xmlSecBase64CtxPtr ctx,
		     const xmlSecByte *in, xmlSecSize inSize, 
		     xmlSecByte *out, xmlSecSize outSize) {
    xmlSecSize inResSize = 0, outResSize = 0;
    int ret;
    
    xmlSecAssert2(ctx != NULL, -1);
    xmlSecAssert2(in != NULL, -1);
    xmlSecAssert2(out != NULL, -1);

    if(ctx->encode != 0) {
	ret = xmlSecBase64CtxEncode(ctx, in, inSize, &inResSize, 
				    out, outSize, &outResSize);
	if((ret < 0) || (inResSize != inSize)) {
	    xmlSecError(XMLSEC_ERRORS_HERE, 
			NULL,
			"xmlSecBase64CtxEncode",
			XMLSEC_ERRORS_R_XMLSEC_FAILED,
			XMLSEC_ERRORS_NO_MESSAGE);
    	    return(-1);
	}
    } else {
	ret = xmlSecBase64CtxDecode(ctx, in, inSize, &inResSize, 
				    out, outSize, &outResSize);
	if((ret < 0) || (inResSize != inSize)) {
	    xmlSecError(XMLSEC_ERRORS_HERE, 
			NULL,
			"xmlSecBase64CtxDecode",
			XMLSEC_ERRORS_R_XMLSEC_FAILED,
			XMLSEC_ERRORS_NO_MESSAGE);
    	    return(-1);
	}
    }

    return(outResSize);
}

/**
 * xmlSecBase64CtxFinal:
 * @ctx: 		the pointer to #xmlSecBase64Ctx structure
 * @out: 		the output buffer
 * @outSize: 		the output buffer size
 *
 * Encodes or decodes the last piece of data stored in the context
 * and finalizes the result.
 *
 * Returns the number of bytes written to output buffer or 
 * -1 if an error occurs.
 */
EXPORT_C
int
xmlSecBase64CtxFinal(xmlSecBase64CtxPtr ctx, 
		    xmlSecByte *out, xmlSecSize outSize) {
    xmlSecSize outResSize = 0;
    int ret;
        
    xmlSecAssert2(ctx != NULL, -1);
    xmlSecAssert2(out != NULL, -1);    
    xmlSecAssert2(outSize > 0, -1);    

    if(ctx->encode != 0) {    
	ret = xmlSecBase64CtxEncodeFinal(ctx, out, outSize, &outResSize);
        if(ret < 0) {
	    xmlSecError(XMLSEC_ERRORS_HERE, 
			NULL,
			"xmlSecBase64CtxEncodeFinal",
			XMLSEC_ERRORS_R_XMLSEC_FAILED,
			"outSize=%d", outSize);
	    return(-1);
	}
    } else {
	if(!xmlSecBase64CtxDecodeIsFinished(ctx)) {
	    xmlSecError(XMLSEC_ERRORS_HERE, 
			NULL,
			"xmlSecBase64CtxIsFinished",
			XMLSEC_ERRORS_R_XMLSEC_FAILED,
			XMLSEC_ERRORS_NO_MESSAGE);
    	    return(-1);
	}	
    }
    
    /* add \0 */
    if((outResSize + 1) < outSize) {
	out[outResSize] = '\0';
    }
    return(outResSize);
}

static xmlSecBase64Status
xmlSecBase64CtxEncodeByte(xmlSecBase64CtxPtr ctx, xmlSecByte inByte, xmlSecByte* outByte) {
    xmlSecAssert2(ctx != NULL, xmlSecBase64StatusFailed);
    xmlSecAssert2(outByte != NULL, xmlSecBase64StatusFailed);

    if((ctx->columns > 0) && (ctx->linePos >= ctx->columns)) {
        (*outByte) = '\n';
        ctx->linePos = 0;
        return(xmlSecBase64StatusConsumeAndRepeat);
    } else if(ctx->inPos == 0) {
        /* we just started new block */
	(*outByte) = base64[xmlSecBase64Encode1(inByte)];
        ctx->inByte = inByte;
        ++ctx->linePos;
        ++ctx->inPos;
        return(xmlSecBase64StatusConsumeAndNext);
    } else if(ctx->inPos == 1) {
	(*outByte) = base64[xmlSecBase64Encode2(ctx->inByte, inByte)];
        ctx->inByte = inByte;
        ++ctx->linePos;
        ++ctx->inPos;
        return(xmlSecBase64StatusConsumeAndNext);
    } else if(ctx->inPos == 2) {
	(*outByte) = base64[xmlSecBase64Encode3(ctx->inByte, inByte)];
        ctx->inByte = inByte;
        ++ctx->linePos;
        ++ctx->inPos;
        return(xmlSecBase64StatusConsumeAndRepeat);
    } else if(ctx->inPos == 3) {
	(*outByte) = base64[xmlSecBase64Encode4(ctx->inByte)];
        ++ctx->linePos;
        ctx->inByte = 0;
        ctx->inPos  = 0;
        return(xmlSecBase64StatusConsumeAndNext);
    }

    xmlSecError(XMLSEC_ERRORS_HERE, 
		NULL,
		NULL,
		XMLSEC_ERRORS_R_INVALID_DATA,
		"ctx->inPos=%d", ctx->inPos);
    return(xmlSecBase64StatusFailed);
}

static xmlSecBase64Status
xmlSecBase64CtxEncodeByteFinal(xmlSecBase64CtxPtr ctx, xmlSecByte* outByte) {
    xmlSecAssert2(ctx != NULL, xmlSecBase64StatusFailed);
    xmlSecAssert2(outByte != NULL, xmlSecBase64StatusFailed);

    if(ctx->inPos == 0) {
        return(xmlSecBase64StatusDone);
    } else if((ctx->columns > 0) && (ctx->linePos >= ctx->columns)) {
        (*outByte) = '\n';
        ctx->linePos = 0;
        return(xmlSecBase64StatusConsumeAndRepeat);
    } else if(ctx->finished == 0) {
        ctx->finished = 1;
        return(xmlSecBase64CtxEncodeByte(ctx, 0, outByte));
    } else if(ctx->inPos < 3) {
        (*outByte) = '=';
        ++ctx->inPos;
        ++ctx->linePos;
        return(xmlSecBase64StatusConsumeAndRepeat);
    } else if(ctx->inPos == 3) {
        (*outByte) = '=';
        ++ctx->linePos;
        ctx->inPos = 0;
        return(xmlSecBase64StatusConsumeAndRepeat);
    }

    xmlSecError(XMLSEC_ERRORS_HERE, 
		NULL,
		NULL,
		XMLSEC_ERRORS_R_INVALID_DATA,
		"ctx->inPos=%d", ctx->inPos);
    return(xmlSecBase64StatusFailed);
}

static xmlSecBase64Status
xmlSecBase64CtxDecodeByte(xmlSecBase64CtxPtr ctx, xmlSecByte inByte, xmlSecByte* outByte) {
    xmlSecAssert2(ctx != NULL, xmlSecBase64StatusFailed);
    xmlSecAssert2(outByte != NULL, xmlSecBase64StatusFailed);    

    if((ctx->finished != 0) && (ctx->inPos == 0)) {
        return(xmlSecBase64StatusDone);
    } if(inByte == '=') {
        ctx->finished = 1;
        if(ctx->inPos < 2) {
	    xmlSecError(XMLSEC_ERRORS_HERE, 
			NULL,
			NULL,
			XMLSEC_ERRORS_R_INVALID_DATA,
			"ctx->inPos=%d", ctx->inPos);
            return(xmlSecBase64StatusFailed);
        } else if(ctx->inPos == 2) {
            ++ctx->inPos;
            return(xmlSecBase64StatusNext);            
        } else if(ctx->inPos == 3) {
            ctx->inPos = 0;
            return(xmlSecBase64StatusNext);
        } else {
	    xmlSecError(XMLSEC_ERRORS_HERE, 
			NULL,
			NULL,
			XMLSEC_ERRORS_R_INVALID_DATA,
			"ctx->inPos=%d", ctx->inPos);
            return(xmlSecBase64StatusFailed);
        }
    } else if(xmlSecIsBase64Space(inByte)) {
        return(xmlSecBase64StatusNext);
    } else if(!xmlSecIsBase64Char(inByte) || (ctx->finished != 0)) {
	xmlSecError(XMLSEC_ERRORS_HERE, 
		    NULL,
		    NULL,
		    XMLSEC_ERRORS_R_INVALID_DATA,
		    "inByte=0x%02x", inByte);
        return(xmlSecBase64StatusFailed);
    }

    /* convert from character to position in base64 array */
    if((inByte >= 'A') && (inByte <= 'Z')) {
	inByte = (inByte - 'A');
    } else if((inByte >= 'a') && (inByte <= 'z')) {
	inByte = 26 + (inByte - 'a');
    } else if((inByte >= '0') && (inByte <= '9')) {
	inByte = 52 + (inByte - '0'); 
    } else if(inByte == '+') {
    	inByte = 62;
    } else if(inByte == '/') {
	inByte = 63;
    }
        
    if(ctx->inPos == 0) {
        ctx->inByte = inByte;
        ++ctx->inPos;
        return(xmlSecBase64StatusNext);
    } else if(ctx->inPos == 1) {
        (*outByte) = xmlSecBase64Decode1(ctx->inByte, inByte);
        ctx->inByte = inByte;
        ++ctx->inPos;
        return(xmlSecBase64StatusConsumeAndNext);
    } else if(ctx->inPos == 2) {
        (*outByte) = xmlSecBase64Decode2(ctx->inByte, inByte);
        ctx->inByte = inByte;
        ++ctx->inPos;
        return(xmlSecBase64StatusConsumeAndNext);
    } else if(ctx->inPos == 3) {
        (*outByte) = xmlSecBase64Decode3(ctx->inByte, inByte);
        ctx->inByte = 0;
        ctx->inPos = 0;
        return(xmlSecBase64StatusConsumeAndNext);
    }

    xmlSecError(XMLSEC_ERRORS_HERE, 
		NULL,
		NULL,
		XMLSEC_ERRORS_R_INVALID_DATA,
		"ctx->inPos=%d", ctx->inPos);
    return(xmlSecBase64StatusFailed);
}


static int
xmlSecBase64CtxEncode(xmlSecBase64CtxPtr ctx, 
                     const xmlSecByte* inBuf, xmlSecSize inBufSize, xmlSecSize* inBufResSize,
                     xmlSecByte* outBuf, xmlSecSize outBufSize, xmlSecSize* outBufResSize) {
    xmlSecBase64Status status = xmlSecBase64StatusNext;
    xmlSecSize inPos, outPos;
    
    xmlSecAssert2(ctx != NULL, -1);
    xmlSecAssert2(inBuf != NULL, -1);
    xmlSecAssert2(inBufResSize != NULL, -1);
    xmlSecAssert2(outBuf != NULL, -1);
    xmlSecAssert2(outBufResSize != NULL, -1);

    /* encode */
    for(inPos = outPos = 0; (inPos < inBufSize) && (outPos < outBufSize); ) {
        status = xmlSecBase64CtxEncodeByte(ctx, inBuf[inPos], &(outBuf[outPos]));
        switch(status) {
            case xmlSecBase64StatusConsumeAndNext:
                ++inPos;
                ++outPos;
                break;
            case xmlSecBase64StatusConsumeAndRepeat:
                ++outPos;
                break;
            case xmlSecBase64StatusNext:
            case xmlSecBase64StatusDone:
            case xmlSecBase64StatusFailed:
		xmlSecError(XMLSEC_ERRORS_HERE, 
			    NULL,
			    "xmlSecBase64CtxEncodeByte",
			    XMLSEC_ERRORS_R_XMLSEC_FAILED,
			    "status=%d", status);
                return(-1);
        }
    }

    (*inBufResSize)  = inPos;
    (*outBufResSize) = outPos;

    return(0);
}

static int
xmlSecBase64CtxEncodeFinal(xmlSecBase64CtxPtr ctx, 
                     xmlSecByte* outBuf, xmlSecSize outBufSize, xmlSecSize* outBufResSize) {
    xmlSecBase64Status status = xmlSecBase64StatusNext;
    xmlSecSize outPos;
    
    xmlSecAssert2(ctx != NULL, -1);
    xmlSecAssert2(outBuf != NULL, -1);
    xmlSecAssert2(outBufResSize != NULL, -1);

    /* encode final bytes */
    for(outPos = 0; (outPos < outBufSize) && (status != xmlSecBase64StatusDone); ) {
        status = xmlSecBase64CtxEncodeByteFinal(ctx, &(outBuf[outPos]));
        switch(status) {
            case xmlSecBase64StatusConsumeAndNext:
            case xmlSecBase64StatusConsumeAndRepeat:
                ++outPos;
                break;
            case xmlSecBase64StatusDone:
                break;
            case xmlSecBase64StatusNext:
            case xmlSecBase64StatusFailed:
		xmlSecError(XMLSEC_ERRORS_HERE, 
			    NULL,
			    "xmlSecBase64CtxEncodeByteFinal",
			    XMLSEC_ERRORS_R_XMLSEC_FAILED,
			    "status=%d", status);
                return(-1);
        }
    }

    if(status != xmlSecBase64StatusDone) {
	xmlSecError(XMLSEC_ERRORS_HERE, 
		    NULL,
		    NULL,
		    XMLSEC_ERRORS_R_INVALID_SIZE,
		    "outBufSize=%d", outBufSize);
        return(-1);
    }
    if(outPos < outBufSize) {
        outBuf[outPos] = '\0'; /* just in case */
    }

    (*outBufResSize) = outPos;
    return(0);
}


static int
xmlSecBase64CtxDecode(xmlSecBase64CtxPtr ctx, 
                     const xmlSecByte* inBuf, xmlSecSize inBufSize, xmlSecSize* inBufResSize,
                     xmlSecByte* outBuf, xmlSecSize outBufSize, xmlSecSize* outBufResSize) {
    xmlSecBase64Status status = xmlSecBase64StatusNext;
    xmlSecSize inPos, outPos;
    
    xmlSecAssert2(ctx != NULL, -1);
    xmlSecAssert2(inBuf != NULL, -1);
    xmlSecAssert2(inBufResSize != NULL, -1);
    xmlSecAssert2(outBuf != NULL, -1);
    xmlSecAssert2(outBufResSize != NULL, -1);

    /* decode */
    for(inPos = outPos = 0; (inPos < inBufSize) && (outPos < outBufSize) && (status != xmlSecBase64StatusDone); ) {
        status = xmlSecBase64CtxDecodeByte(ctx, inBuf[inPos], &(outBuf[outPos]));
        switch(status) {
            case xmlSecBase64StatusConsumeAndNext:
                ++inPos;
                ++outPos;
                break;
            case xmlSecBase64StatusConsumeAndRepeat:
                ++outPos;
                break;
            case xmlSecBase64StatusNext:
                ++inPos;
                break;
            case xmlSecBase64StatusDone:
                break;
            case xmlSecBase64StatusFailed:
		xmlSecError(XMLSEC_ERRORS_HERE, 
			    NULL,
			    "xmlSecBase64CtxDecodeByte",
			    XMLSEC_ERRORS_R_XMLSEC_FAILED,
			    "status=%d", status);
                return(-1);
        }
    }

    /* skip spaces at the end */
    while((inPos < inBufSize) && xmlSecIsBase64Space(inBuf[inPos])) {
        ++inPos;
    }

    (*inBufResSize)  = inPos;
    (*outBufResSize) = outPos;

    return(0);
}

static int
xmlSecBase64CtxDecodeIsFinished(xmlSecBase64CtxPtr ctx) {
    xmlSecAssert2(ctx != NULL, -1);

    return((ctx->inPos == 0) ? 1 : 0);
}

/**
 * xmlSecBase64Encode:
 * @buf: 		the input buffer.
 * @len: 		the input buffer size.
 * @columns: 		the output max line length (if 0 then no line breaks
 *          		would be inserted)
 *
 * Encodes the data from input buffer and allocates the string for the result.
 * The caller is responsible for freeing returned buffer using
 * xmlFree() function.
 *
 * Returns newly allocated string with base64 encoded data 
 * or NULL if an error occurs.
 */
EXPORT_C
xmlChar*
xmlSecBase64Encode(const xmlSecByte *buf, xmlSecSize len, int columns) {
    xmlSecBase64Ctx ctx;
    xmlChar *ptr;
    xmlSecSize size;    
    int size_update, size_final;
    int ret;

    xmlSecAssert2(buf != NULL, NULL);

    ret = xmlSecBase64CtxInitialize(&ctx, 1, columns);
    if(ret < 0) {
	xmlSecError(XMLSEC_ERRORS_HERE,
		    NULL,
		    "xmlSecBase64CtxInitialize",
		    XMLSEC_ERRORS_R_XMLSEC_FAILED,
		    XMLSEC_ERRORS_NO_MESSAGE);
	return(NULL);
    }
    
    /* create result buffer */
    size = (4 * len) / 3 + 4;
    if(columns > 0) {
	size += (size / columns) + 4;
    }
    ptr = (xmlChar*) xmlMalloc(size);
    if(ptr == NULL) {
	xmlSecError(XMLSEC_ERRORS_HERE,
		    NULL,
		    NULL,
		    XMLSEC_ERRORS_R_MALLOC_FAILED,
		    "size=%d", size);
	xmlSecBase64CtxFinalize(&ctx);
	return(NULL);
    }

    ret = xmlSecBase64CtxUpdate(&ctx, buf, len, (xmlSecByte*)ptr, size);
    if(ret < 0) {
	xmlSecError(XMLSEC_ERRORS_HERE,
		    NULL,
		    "xmlSecBase64CtxUpdate",
		    XMLSEC_ERRORS_R_XMLSEC_FAILED,
		    "len=%d", len);
	xmlFree(ptr);
	xmlSecBase64CtxFinalize(&ctx);
	return(NULL);
    }
    size_update = ret;

    ret = xmlSecBase64CtxFinal(&ctx, ((xmlSecByte*)ptr) + size_update, size - size_update);
    if(ret < 0) {
	xmlSecError(XMLSEC_ERRORS_HERE,
		    NULL,
		    "xmlSecBase64CtxFinal",
		    XMLSEC_ERRORS_R_XMLSEC_FAILED,
		    XMLSEC_ERRORS_NO_MESSAGE);
	xmlFree(ptr);
	xmlSecBase64CtxFinalize(&ctx);
	return(NULL);
    }
    size_final = ret;
    ptr[size_update + size_final] = '\0';
    
    xmlSecBase64CtxFinalize(&ctx);
    return(ptr);
}

/**
 * xmlSecBase64Decode:
 * @str: 		the input buffer with base64 encoded string
 * @buf: 		the output buffer
 * @len: 		the output buffer size
 *
 * Decodes input base64 encoded string and puts result into
 * the output buffer.
 *
 * Returns the number of bytes written to the output buffer or 
 * a negative value if an error occurs 
 */
EXPORT_C
int
xmlSecBase64Decode(const xmlChar* str, xmlSecByte *buf, xmlSecSize len) {
    xmlSecBase64Ctx ctx;
    int size_update;
    int size_final;
    int ret;

    xmlSecAssert2(str != NULL, -1);
    xmlSecAssert2(buf != NULL, -1);

    ret = xmlSecBase64CtxInitialize(&ctx, 0, 0);
    if(ret < 0) {
	xmlSecError(XMLSEC_ERRORS_HERE,
		    NULL,
		    "xmlSecBase64CtxInitialize",
		    XMLSEC_ERRORS_R_XMLSEC_FAILED,
		    XMLSEC_ERRORS_NO_MESSAGE);
	return(-1);
    }
    
    ret = xmlSecBase64CtxUpdate(&ctx, (const xmlSecByte*)str, xmlStrlen(str), buf, len);
    if(ret < 0) {
	xmlSecError(XMLSEC_ERRORS_HERE,
		    NULL,
		    "xmlSecBase64CtxUpdate",
		    XMLSEC_ERRORS_R_XMLSEC_FAILED,
		    XMLSEC_ERRORS_NO_MESSAGE);
	xmlSecBase64CtxFinalize(&ctx);
	return(-1);
    }

    size_update = ret;
    ret = xmlSecBase64CtxFinal(&ctx, buf + size_update, len - size_update);
    if(ret < 0) {
	xmlSecError(XMLSEC_ERRORS_HERE,
		    NULL,
		    "xmlSecBase64CtxFinal",
		    XMLSEC_ERRORS_R_XMLSEC_FAILED,
		    XMLSEC_ERRORS_NO_MESSAGE);
	xmlSecBase64CtxFinalize(&ctx);
	return(-1);
    }
    size_final = ret;    

    xmlSecBase64CtxFinalize(&ctx);
    return(size_update + size_final);
}

/**************************************************************
 *
 * Base64 Transform
 *
 * xmlSecBase64Ctx is located after xmlSecTransform
 * 
 **************************************************************/
#define xmlSecBase64Size \
	(sizeof(xmlSecTransform) + sizeof(xmlSecBase64Ctx))
#define xmlSecBase64GetCtx(transform) \
    ((xmlSecTransformCheckSize((transform), xmlSecBase64Size)) ? \
	(xmlSecBase64CtxPtr)(((xmlSecByte*)(transform)) + sizeof(xmlSecTransform)) : \
	(xmlSecBase64CtxPtr)NULL)

static int		xmlSecBase64Initialize		(xmlSecTransformPtr transform);
static void		xmlSecBase64Finalize		(xmlSecTransformPtr transform);
static int 		xmlSecBase64Execute		(xmlSecTransformPtr transform, 
							 int last, 
							 xmlSecTransformCtxPtr transformCtx);

static xmlSecTransformKlass xmlSecBase64Klass = {
    /* klass/object sizes */
    sizeof(xmlSecTransformKlass),		/* xmlSecSize klassSize */
    xmlSecBase64Size,				/* xmlSecSize objSize */

    xmlSecNameBase64,				/* const xmlChar* name; */
    xmlSecHrefBase64,				/* const xmlChar* href; */
    xmlSecTransformUsageDSigTransform,		/* xmlSecAlgorithmUsage usage; */

    xmlSecBase64Initialize, 			/* xmlSecTransformInitializeMethod initialize; */
    xmlSecBase64Finalize,			/* xmlSecTransformFinalizeMethod finalize; */
    NULL,					/* xmlSecTransformNodeReadMethod readNode; */
    NULL,					/* xmlSecTransformNodeWriteMethod writeNode; */
    NULL,					/* xmlSecTransformSetKeyReqMethod setKeyReq; */
    NULL,					/* xmlSecTransformSetKeyMethod setKey; */
    NULL,					/* xmlSecTransformValidateMethod validate; */
    xmlSecTransformDefaultGetDataType,		/* xmlSecTransformGetDataTypeMethod getDataType; */
    xmlSecTransformDefaultPushBin,		/* xmlSecTransformPushBinMethod pushBin; */
    xmlSecTransformDefaultPopBin,		/* xmlSecTransformPopBinMethod popBin; */
    NULL,					/* xmlSecTransformPushXmlMethod pushXml; */
    NULL,					/* xmlSecTransformPopXmlMethod popXml; */
    xmlSecBase64Execute,			/* xmlSecTransformExecuteMethod execute; */

    NULL,					/* void* reserved0; */
    NULL,					/* void* reserved1; */
};

/**
 * xmlSecTransformBase64GetKlass:
 *
 * The Base64 transform klass (http://www.w3.org/TR/xmldsig-core/#sec-Base-64).
 * The normative specification for base64 decoding transforms is RFC 2045
 * (http://www.ietf.org/rfc/rfc2045.txt). The base64 Transform element has 
 * no content. The input is decoded by the algorithms. This transform is 
 * useful if an application needs to sign the raw data associated with 
 * the encoded content of an element.
 *
 * Returns base64 transform id.
 */
EXPORT_C
xmlSecTransformId 
xmlSecTransformBase64GetKlass(void) {
    return(&xmlSecBase64Klass);
}

/**
 * xmlSecTransformBase64SetLineSize:
 * @transform: 		the pointer to BASE64 encode transform.
 * @lineSize: 		the new max line size.
 *
 * Sets the max line size to @lineSize.
 */
EXPORT_C
void
xmlSecTransformBase64SetLineSize(xmlSecTransformPtr transform, xmlSecSize lineSize) {
    xmlSecBase64CtxPtr ctx;
    
    xmlSecAssert(xmlSecTransformCheckId(transform, xmlSecTransformBase64Id));
    
    ctx = xmlSecBase64GetCtx(transform);
    xmlSecAssert(ctx != NULL);
    
    ctx->columns = lineSize;    
}

static int
xmlSecBase64Initialize(xmlSecTransformPtr transform) {
    xmlSecBase64CtxPtr ctx;
    int ret;
    
    xmlSecAssert2(xmlSecTransformCheckId(transform, xmlSecTransformBase64Id), -1);

    ctx = xmlSecBase64GetCtx(transform);
    xmlSecAssert2(ctx != NULL, -1);

    transform->operation = xmlSecTransformOperationDecode;
    ret = xmlSecBase64CtxInitialize(ctx, 0, XMLSEC_BASE64_LINESIZE);
    if(ret < 0) {
	xmlSecError(XMLSEC_ERRORS_HERE,
		    xmlSecErrorsSafeString(xmlSecTransformGetName(transform)),
		    "xmlSecBase64CtxInitialize",
		    XMLSEC_ERRORS_R_XMLSEC_FAILED,
		    XMLSEC_ERRORS_NO_MESSAGE);
	return(-1);
    }
    
    return(0);
}

static void
xmlSecBase64Finalize(xmlSecTransformPtr transform) {
    xmlSecBase64CtxPtr ctx;
    
    xmlSecAssert(xmlSecTransformCheckId(transform, xmlSecTransformBase64Id));

    ctx = xmlSecBase64GetCtx(transform);
    xmlSecAssert(ctx != NULL);
    
    xmlSecBase64CtxFinalize(ctx);
}

static int 
xmlSecBase64Execute(xmlSecTransformPtr transform, int last, xmlSecTransformCtxPtr transformCtx) {
    xmlSecBase64CtxPtr ctx;
    xmlSecBufferPtr in, out;
    xmlSecSize inSize, outSize, outLen;
    int ret;

    xmlSecAssert2(xmlSecTransformCheckId(transform, xmlSecTransformBase64Id), -1);
    xmlSecAssert2((transform->operation == xmlSecTransformOperationEncode) || (transform->operation == xmlSecTransformOperationDecode), -1);
    xmlSecAssert2(transformCtx != NULL, -1);
    
    ctx = xmlSecBase64GetCtx(transform);
    xmlSecAssert2(ctx != NULL, -1);
    
    in = &(transform->inBuf);
    out = &(transform->outBuf);

    if(transform->status == xmlSecTransformStatusNone) {
	ctx->encode = (transform->operation == xmlSecTransformOperationEncode) ? 1 : 0;
	transform->status = xmlSecTransformStatusWorking;
    }

    switch(transform->status) {
	case xmlSecTransformStatusWorking:
	    inSize = xmlSecBufferGetSize(in);
	    outSize = xmlSecBufferGetSize(out);
	    if(inSize > 0) {
		if(ctx->encode != 0) {
		    outLen = 4 * inSize / 3 + 8;
		    if(ctx->columns > 0) {
			outLen += inSize / ctx->columns + 4;
		    }
		} else {
		    outLen = 3 * inSize / 4 + 8;
		}
		ret = xmlSecBufferSetMaxSize(out, outSize + outLen);
		if(ret < 0) {
		    xmlSecError(XMLSEC_ERRORS_HERE, 
				xmlSecErrorsSafeString(xmlSecTransformGetName(transform)),
				"xmlSecBufferSetMaxSize",
				XMLSEC_ERRORS_R_XMLSEC_FAILED,
				"size=%d", outSize + outLen);
		    return(-1);
		}

		/* encode/decode the next chunk */
		ret = xmlSecBase64CtxUpdate(ctx, xmlSecBufferGetData(in), inSize,
					    xmlSecBufferGetData(out) + outSize, 
					    outLen);
		if(ret < 0) {
		    xmlSecError(XMLSEC_ERRORS_HERE, 
				xmlSecErrorsSafeString(xmlSecTransformGetName(transform)),
				"xmlSecBase64CtxUpdate",
				XMLSEC_ERRORS_R_XMLSEC_FAILED,
				XMLSEC_ERRORS_NO_MESSAGE);
		    return(-1);
		}
		outLen = ret;
		
		/* set correct size */
		ret = xmlSecBufferSetSize(out, outSize + outLen);
		if(ret < 0) {
		    xmlSecError(XMLSEC_ERRORS_HERE, 
				xmlSecErrorsSafeString(xmlSecTransformGetName(transform)),
				"xmlSecBufferSetSize",
				XMLSEC_ERRORS_R_XMLSEC_FAILED,
				"size=%d", outSize + outLen);
		    return(-1);
		}
		
		/* remove chunk from input */
		ret = xmlSecBufferRemoveHead(in, inSize);
		if(ret < 0) {
		    xmlSecError(XMLSEC_ERRORS_HERE, 
				xmlSecErrorsSafeString(xmlSecTransformGetName(transform)),
				"xmlSecBufferRemoveHead",
				XMLSEC_ERRORS_R_XMLSEC_FAILED,
				"size=%d", inSize);
		    return(-1);
		}
	    }
	    
	    if(last) {
	        outSize = xmlSecBufferGetSize(out);

		ret = xmlSecBufferSetMaxSize(out, outSize + 16);
		if(ret < 0) {
		    xmlSecError(XMLSEC_ERRORS_HERE, 
				xmlSecErrorsSafeString(xmlSecTransformGetName(transform)),
				"xmlSecBufferSetMaxSize",
				XMLSEC_ERRORS_R_XMLSEC_FAILED,
				"size=%d", outSize + 16);
		    return(-1);
		}
	
		/* add from ctx buffer */
		ret = xmlSecBase64CtxFinal(ctx, xmlSecBufferGetData(out) + outSize, 16);
		if(ret < 0) {
		    xmlSecError(XMLSEC_ERRORS_HERE, 
				xmlSecErrorsSafeString(xmlSecTransformGetName(transform)),
				"xmlSecBase64CtxFinal",
				XMLSEC_ERRORS_R_XMLSEC_FAILED,
				XMLSEC_ERRORS_NO_MESSAGE);
		    return(-1);
		}
		outLen = ret;
		
		/* set correct size */
		ret = xmlSecBufferSetSize(out, outSize + outLen);
		if(ret < 0) {
		    xmlSecError(XMLSEC_ERRORS_HERE, 
				xmlSecErrorsSafeString(xmlSecTransformGetName(transform)),
				"xmlSecBufferSetSize",
				XMLSEC_ERRORS_R_XMLSEC_FAILED,
				"size=%d", outSize + outLen);
		    return(-1);
		}
		transform->status = xmlSecTransformStatusFinished;
	    }
	    break;
	case xmlSecTransformStatusFinished:
	    /* the only way we can get here is if there is no input */
	    xmlSecAssert2(xmlSecBufferGetSize(in) == 0, -1);
	    break;
	default:
	    xmlSecError(XMLSEC_ERRORS_HERE, 
			xmlSecErrorsSafeString(xmlSecTransformGetName(transform)),
			NULL,
			XMLSEC_ERRORS_R_INVALID_STATUS,
			"status=%d", transform->status);
	    return(-1);
    }
    return(0);
}


