/*
 * base64.h
 * 
 * 本檔案是基於
 * base64.c
 * Base64 encoding/decoding command line filter
 * Copyright (c) 2002 Matthias Gaertner 29.06.2002
 * 所改寫的base64編碼函式。
 * Rewrite encodeB64 by Jao Hao Chieh (05.06.2005)
 * Update decodeB64 function at (05.07.2006)
 * 原始程式為符合GNU開放源碼之base64編碼/解碼程式，
 * 這裡精簡為方便使用的標頭檔。
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * 
 * 函式說明：
 * char *encodeB64(char* fileName) - 將檔案路徑傳入，編碼後傳回檔案之base64字串
 * 
 */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include "general.h"

#ifndef _BASE64_H_
#define _BASE64_H_

#ifndef TRUE
#define TRUE 1
#endif

#ifndef FALSE
#define FALSE 0
#endif

#define ENCODE_BUFFER_SIZE_IN (8192*18)
#define ENCODE_BUFFER_SIZE_OUT (8192*36)
#define DECODE_BUFFER_SIZE_IN (8192*24)
#define DECODE_BUFFER_SIZE_OUT (8192*18)

static int g_fUseCRLF= FALSE;
static unsigned int g_nCharsPerLine = 64;
static const char* to_b64 =
		"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
char* encodeB64(char* fileName);

/**
 * 將檔案編碼為base64字串
 * 
 * @param fileName 檔案路徑字串
 * @return pbBufferOut 經編碼後之字串內容
 **/

char* encodeB64(char* fileName) {
	FILE *g_fIn = fopen(fileName, "rb");

	char *pbBufferIn = (char*) malloc( ENCODE_BUFFER_SIZE_IN);
	char *pbBufferOut = (char*) malloc( ENCODE_BUFFER_SIZE_OUT);
	for (;;) {
		unsigned long nDiv = 0;
		unsigned long nRem = 0;
		unsigned long nChars = 0;
		unsigned char* pIn = (unsigned char*) pbBufferIn;
		unsigned int nOut = 0;
		size_t nWritten = 0;

		size_t nRead = fread( (void*)pIn, 1, ENCODE_BUFFER_SIZE_IN, g_fIn);

		nDiv = ((unsigned long)nRead) / 3;
		nRem = ((unsigned long)nRead) % 3;
		nChars = 0;

		while (nDiv > 0) {
			pbBufferOut[nOut+0] = to_b64[ (pIn[0] >> 2) & 0x3f];
			pbBufferOut[nOut+1] = to_b64[((pIn[0] << 4) & 0x30) + ((pIn[1] >> 4) & 0xf)];
			pbBufferOut[nOut+2] = to_b64[((pIn[1] << 2) & 0x3c) + ((pIn[2] >> 6) & 0x3)];
			pbBufferOut[nOut+3] = to_b64[ pIn[2] & 0x3f];
			pIn += 3;
			nOut += 4;
			nDiv--;
			nChars += 4;
		}

		switch (nRem) {
			case 2:
				pbBufferOut[nOut+0] = to_b64[ (pIn[0] >> 2) & 0x3f];
				pbBufferOut[nOut+1] = to_b64[((pIn[0] << 4) & 0x30) + ((pIn[1] >> 4) & 0xf)];
				pbBufferOut[nOut+2] = to_b64[ (pIn[1] << 2) & 0x3c];
				pbBufferOut[nOut+3] = '=';
				nOut += 4;
				nChars += 4;
				if (nChars >= g_nCharsPerLine && g_nCharsPerLine != 0) {
					nChars = 0;
					if (g_fUseCRLF) {
						pbBufferOut[nOut++] = '\r';
					}
					pbBufferOut[nOut++] = '\n';
				}
				break;
			case 1:
				pbBufferOut[nOut+0] = to_b64[ (pIn[0] >> 2) & 0x3f];
				pbBufferOut[nOut+1] = to_b64[ (pIn[0] << 4) & 0x30];
				pbBufferOut[nOut+2] = '=';
				pbBufferOut[nOut+3] = '=';
				nOut += 4;
				nChars += 4;
				if (nChars >= g_nCharsPerLine && g_nCharsPerLine != 0) {
					nChars = 0;
					if (g_fUseCRLF) {
						pbBufferOut[nOut++] = '\r';
					}
					pbBufferOut[nOut++] = '\n';
				}
				break;
		}

		if (nRem > 0 || feof(g_fIn) ) {
			if (nChars > 0) {
				nChars = 0;
				if (g_fUseCRLF) {
					pbBufferOut[nOut++] = '\r';
				}
				pbBufferOut[nOut++] = '\n';
			}
		}

		if (nRem > 0 || feof(g_fIn) ) {
			break;
		}
	}
	fclose(g_fIn);
	free(pbBufferIn);
	return pbBufferOut;
}

/**
 * 將base64字串解碼為指定檔名之檔案
 * 
 * @param fileName 檔案路徑字串
 * @return pbBufferOut 經編碼後之字串內容
 **/
int decodeB64(char *filename, char *encBuf) {
	FILE *g_fOut = fopen(filename, "wb");
	int nIn = 0;
	int bufLength = strlen(encBuf);
	int z = 0; // 0 Normal, 1 skip MIME separator (---) to end of line
	char c = '\0';
	unsigned char data[3];
	unsigned int nData = 0;

	for (;; nIn++) {
		unsigned char bits = 'z';
		if (nIn >= bufLength) {
			break;
		}
		c = encBuf[nIn];
		if (z > 0) {
			if (c == '\n') {
				z = 0;
			}
		} else if (c >= 'A' && c <= 'Z') {
			bits = (unsigned char) (c - 'A');
		} else if (c >= 'a' && c <= 'z') {
			bits = (unsigned char) (c - 'a' + (char)26);
		} else if (c >= '0' && c <= '9') {
			bits = (unsigned char) (c - '0' + (char)52);
		} else if (c == '+') {
			bits = (unsigned char) 62;
		} else if (c == '/') {
			bits = (unsigned char) 63;
		} else if (c == '-') {
			z = 1;
		} else if (c == '=') {
			break;
		} else {
			bits = (unsigned char) 'y';
		}

		if (bits < (unsigned char) 64 ) {
			switch (nData++) {
				case 0:
					data[0] = (bits << 2) & 0xfc;
					break;
				case 1:
					data[0] |= (bits >> 4) & 0x03;
					data[1] = (bits << 4) & 0xf0;
					break;
				case 2:
					data[1] |= (bits >> 2) & 0x0f;
					data[2] = (bits << 6) & 0xc0;
					break;
				case 3:
					data[2] |= bits & 0x3f;
					break;
			}

			if (nData == 4) {
				size_t n = fwrite( (void*)data, 1, 3, g_fOut);
				if (ferror(g_fOut) || n < 3) {
					printf("error 202: Can not write file.\n");
					break;
				}
				nData = 0;
			}
		}
	}

	if (nData > 0) {
		if (nData == 1) {
			printf("error 212: May not Base64 format.\n");
		} else {
			size_t n = fwrite( (void*)data, 1, nData-1, g_fOut);
			if (ferror(g_fOut) || n < (nData-1)) {
				printf("error 216: Can not write file.\n");
			}
		}
	}
	fclose(g_fOut);
	return 0;
}

/**
 * 將base64字串解碼為指定檔名之檔案，並以附加方式寫入該檔案
 * 
 * @param fileName 檔案路徑字串
 * @return pbBufferOut 經編碼後之字串內容
 **/
int decodeB64a(char *filename, char * encBuf) {
	FILE *g_fOut = fopen(filename, "ab");
	int nIn = 0;
	int bufLength = strlen(encBuf);
	int z = 0; // 0 Normal, 1 skip MIME separator (---) to end of line
	char c = '\0';
	unsigned char data[3];
	unsigned int nData = 0;

	for (;; nIn++) {
		unsigned char bits = 'z';
		if (nIn >= bufLength) {
			break;
		}
		c = encBuf[nIn];
		if (z > 0) {
			if (c == '\n') {
				z = 0;
			}
		} else if (c >= 'A' && c <= 'Z') {
			bits = (unsigned char) (c - 'A');
		} else if (c >= 'a' && c <= 'z') {
			bits = (unsigned char) (c - 'a' + (char)26);
		} else if (c >= '0' && c <= '9') {
			bits = (unsigned char) (c - '0' + (char)52);
		} else if (c == '+') {
			bits = (unsigned char) 62;
		} else if (c == '/') {
			bits = (unsigned char) 63;
		} else if (c == '-') {
			z = 1;
		} else if (c == '=') {
			break;
		} else {
			bits = (unsigned char) 'y';
		}

		if (bits < (unsigned char) 64 ) {
			switch (nData++) {
				case 0:
					data[0] = (bits << 2) & 0xfc;
					break;
				case 1:
					data[0] |= (bits >> 4) & 0x03;
					data[1] = (bits << 4) & 0xf0;
					break;
				case 2:
					data[1] |= (bits >> 2) & 0x0f;
					data[2] = (bits << 6) & 0xc0;
					break;
				case 3:
					data[2] |= bits & 0x3f;
					break;
			}

			if (nData == 4) {
				size_t n = fwrite( (void*)data, 1, 3, g_fOut);
				if (ferror(g_fOut) || n < 3) {
					printf("error 202: Can not write file.\n");
					break;
				}
				nData = 0;
			}
		}
	}

	if (nData > 0) {
		if (nData == 1) {
			printf("error 212: May not Base64 format.\n");
		} else {
			size_t n = fwrite( (void*)data, 1, nData-1, g_fOut);
			if (ferror(g_fOut) || n < (nData-1)) {
				printf("error 216: Can not write file.\n");
			}
		}
	}
	fclose(g_fOut);
	return 0;
}

/**
 * 將base64字串解碼並填入指定字串
 * 
 * @param context 欲寫入之字串
 * @return pbBufferOut 經編碼後之字串內容
 **/
int decodeB64c(char *filename, char * encBuf) {
	FILE *g_fOut = fopen(filename, "ab");
	int nIn = 0;
	int bufLength = strlen(encBuf);
	int z = 0; // 0 Normal, 1 skip MIME separator (---) to end of line
	char c = '\0';
	unsigned char data[3];
	unsigned int nData = 0;

	for (;; nIn++) {
		unsigned char bits = 'z';
		if (nIn >= bufLength) {
			break;
		}
		c = encBuf[nIn];
		if (z > 0) {
			if (c == '\n') {
				z = 0;
			}
		} else if (c >= 'A' && c <= 'Z') {
			bits = (unsigned char) (c - 'A');
		} else if (c >= 'a' && c <= 'z') {
			bits = (unsigned char) (c - 'a' + (char)26);
		} else if (c >= '0' && c <= '9') {
			bits = (unsigned char) (c - '0' + (char)52);
		} else if (c == '+') {
			bits = (unsigned char) 62;
		} else if (c == '/') {
			bits = (unsigned char) 63;
		} else if (c == '-') {
			z = 1;
		} else if (c == '=') {
			break;
		} else {
			bits = (unsigned char) 'y';
		}

		if (bits < (unsigned char) 64 ) {
			switch (nData++) {
				case 0:
					data[0] = (bits << 2) & 0xfc;
					break;
				case 1:
					data[0] |= (bits >> 4) & 0x03;
					data[1] = (bits << 4) & 0xf0;
					break;
				case 2:
					data[1] |= (bits >> 2) & 0x0f;
					data[2] = (bits << 6) & 0xc0;
					break;
				case 3:
					data[2] |= bits & 0x3f;
					break;
			}

			if (nData == 4) {
				size_t n = fwrite( (void*)data, 1, 3, g_fOut);
				if (ferror(g_fOut) || n < 3) {
					printf("error 202: Can not write file.\n");
					break;
				}
				nData = 0;
			}
		}
	}

	if (nData > 0) {
		if (nData == 1) {
			printf("error 212: May not Base64 format.\n");
		} else {
			size_t n = fwrite( (void*)data, 1, nData-1, g_fOut);
			if (ferror(g_fOut) || n < (nData-1)) {
				printf("error 216: Can not write file.\n");
			}
		}
	}
	fclose(g_fOut);
	return 0;
}
#endif //_BASE64_H_
