C语言实现base64编码与解码

程序源代码共三个文件:base64.c、base64.h、base64_main.c。

该程序能够将指定文件进行base64编码与解码。

base64.c:

/***********************************************************************************************************************************************************************
** @file:       base64.c
** @author:     huixuan.li guoqiang.zhu
** @date:       2024-11-16 10:42:25
** @brief:      实现base64编码和解码
***********************************************************************************************************************************************************************/
#include "base64.h"

static const unsigned char encode_dict[64] = {
    'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P',
    'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f',
    'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v',
    'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/'
};
static const unsigned char decode_dict[] = {
    ['A'] = 0,['B'] = 1,['C'] = 2,['D'] = 3,['E'] = 4,['F'] = 5,['G'] = 6,['H'] = 7,
    ['I'] = 8,['J'] = 9,['K'] = 10,['L'] = 11,['M'] = 12,['N'] = 13,['O'] = 14,['P'] = 15,
    ['Q'] = 16,['R'] = 17,['S'] = 18,['T'] = 19,['U'] = 20,['V'] = 21,['W'] = 22,['X'] = 23,
    ['Y'] = 24,['Z'] = 25,['a'] = 26,['b'] = 27,['c'] = 28,['d'] = 29,['e'] = 30,['f'] = 31,
    ['g'] = 32,['h'] = 33,['i'] = 34,['j'] = 35,['k'] = 36,['l'] = 37,['m'] = 38,['n'] = 39,
    ['o'] = 40,['p'] = 41,['q'] = 42,['r'] = 43,['s'] = 44,['t'] = 45,['u'] = 46,['v'] = 47,
    ['w'] = 48,['x'] = 49,['y'] = 50,['z'] = 51,['0'] = 52,['1'] = 53,['2'] = 54,['3'] = 55,
    ['4'] = 56,['5'] = 57,['6'] = 58,['7'] = 59,['8'] = 60,['9'] = 61,['+'] = 62,['/'] = 63
};

unsigned long long get_encode_len(unsigned long long data_len){
    return ((data_len + 2ull) / 3ull) << 2;
}

unsigned long long get_decode_len(const unsigned char *base64_code, unsigned long long base64_code_len){
    // 计算等号数量
    int equal_num = 0;
    for (const unsigned char *p = base64_code + base64_code_len;*--p == (unsigned char)'='; equal_num++);
    return (base64_code_len >> 2) * 3 - equal_num;
}

bool is_valid_base64_code(const unsigned char *base64_code, unsigned long long base64_code_len){
    // 若base_code_len%4!=0则非法
    if (base64_code_len & 0x03ull)
        return false;

    //从末尾开始检测
    const unsigned char *p = base64_code + base64_code_len;

    //首先跳过连续等号,若等号数量超过两个,则非法
    int equal_num = 0;
    while (*--p == '='){
        equal_num++;
        if (equal_num > 2)
            return false;
    }

    // 若base64_code中存在大于122u的元素或(decode_dict[element]==0&&element!='A')则非法
    while (p >= base64_code){
        if (*p > 'z' || (decode_dict[*p] == 0 && *p != 'A') || *p == '='){
            return false;
        }
        p--;
    }

    return true;
}

void base64_encode(const unsigned char *data, unsigned long long data_len, unsigned char *result){
    const unsigned char *q = data + data_len - data_len % 3;

    // 对前最大3的倍数个字节进行处理
    while (data != q){
        *result++ = encode_dict[*data >> 2];
        *result++ = encode_dict[((*data << 4) | (*++data >> 4)) & 0x3fu];
        *result++ = encode_dict[((*data << 2) | (*++data >> 6)) & 0x3fu];
        *result++ = encode_dict[*data++ & 0x3fu];
    }
    switch (data_len % 3){
        case 1:
            *result++ = encode_dict[*data >> 2];
            *result++ = encode_dict[*data << 4 & 0x3fu];
            *result++ = (unsigned char)'=';
            *result = (unsigned char)'=';
            break;
        case 2:
            *result++ = encode_dict[*data >> 2];
            *result++ = encode_dict[((*data << 4) | (*++data >> 4)) & 0x3fu];
            *result++ = encode_dict[*data << 2 & 0x3fu];;
            *result = (unsigned char)'=';
            break;
    }
}

void base64_decode(const unsigned char *base64_code, unsigned long long base64_code_len, unsigned char *result){
    // 计算等号数量
    int equal_num = 0;
    const unsigned char *p = base64_code + base64_code_len;
    while (*--p == '=')
        equal_num++;
    // 设置锚点
    p = equal_num ? base64_code + base64_code_len - 4ull : base64_code + base64_code_len;

    while (base64_code < p){
        *result++ = (decode_dict[*base64_code] << 2) | (decode_dict[*++base64_code] >> 4);
        *result++ = (decode_dict[*base64_code] << 4) | (decode_dict[*++base64_code] >> 2);
        *result++ = (decode_dict[*base64_code] << 6) | (decode_dict[*++base64_code]);
        base64_code++;
    }

    switch (equal_num){
        case 2:
            *result = (decode_dict[*base64_code] << 2) | (decode_dict[*++base64_code] >> 4);
            break;
        case 1:
            *result++ = (decode_dict[*base64_code] << 2) | (decode_dict[*++base64_code] >> 4);
            *result = (decode_dict[*base64_code] << 4) | (decode_dict[*++base64_code] >> 2);
            break;
    }
}

base64.h:

/***********************************************************************************************************************************************************************
** @file:       base64.h
** @author:     huixuan.li guoqiang.zhu
** @date:       2024-06-06 15:10:46
** @brief:      base64头文件
***********************************************************************************************************************************************************************/
#ifndef __CUSTOM_BASE64_H__
#define __CUSTOM_BASE64_H__

#include <stdbool.h>

// 获取对指定长度数据进行base64编码后,base64编码的长度
unsigned long long get_encode_len(unsigned long long data_len);

// 获取对指定base64码解码后,解码后的数据长度
unsigned long long get_decode_len(const unsigned char *base64_code, unsigned long long base64_code_len);

// 检测数据是否为合法的base64编码
bool is_valid_base64_code(const unsigned char *base64_code,unsigned long long  base64_code_len);

// 对指定数据进行base64编码
void base64_encode(const unsigned char *data, unsigned long long data_len, unsigned char *result);

// 对指定base64码进行解码
void base64_decode(const unsigned char *base64_code, unsigned long long base64_code_len, unsigned char *result);

#endif

base64_main.c:

/***********************************************************************************************************************************************************************
** @file:       base64_main.c
** @author:     huixuan.li
** @date:       2024-11-22 11:09:11
** @brief:      对文件进行base64编码或解码
***********************************************************************************************************************************************************************/
// 设置读取大文件所需的宏
#ifndef _LARGEFILE64_SOURCE
#define _LARGEFILE64_SOURCE
#endif

#ifndef _FILE_OFFSET_BITS
#define _FILE_OFFSET_BITS 64
#endif

#include "base64.h"
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>

/* 返回码定义 */
static enum{ SUCCESS, ARGS_ERROR, HANDLE_ERROR } EXITCODE;

/* 错误信息与帮助信息 */
static const char ARG_ERROR_INFO[] =
"ERROR! Couldn't recognize argument %s, please use option \"--help\" for instructions\n";
static const char HELP_INFO[] =
"The program is capable of base64 encoding and decoding.\n"
"Encoded command:\n"
"\tbase64 -e <inputfile> [-o <outputfile>]\n"
"Decoded command:\n"
"\tbase64 -d <inputfile> -o <outputfile>\n";


/* 全局参数 */
static enum{ UNDEFINE, DECODE, ENCODE } mode=UNDEFINE;

static char *input_file_path = NULL;
static char *output_file_path = NULL;
static FILE *input_file_stream;
static FILE *output_file_stream;
static unsigned long long input_file_size;
static unsigned long long  BUFFER_SIZE;
static unsigned char *buffer;

/* 读取参数并根据参数修改全局参数 */
static void analyze_arguments(int argc, char **argv);

/* 根据全局参数设置输入流 */
static void set_input_stream(void);

/* 根据全局参数设置输出流 */
static void set_output_stream(void);

/* 进行转化输出 */
static void handle(void);

/* 主函数 */
int main(int argc, char *argv[]){
    analyze_arguments(argc, argv);
    set_input_stream();
    set_output_stream();
    handle();
    exit(EXITCODE = SUCCESS);
}


/* 读取参数并根据参数修改全局参数 */
static void analyze_arguments(int argc, char **argv){
    if (argc == 1){
        printf("%s", HELP_INFO);
        exit(EXITCODE = SUCCESS);
    }
    // 对参数遍历处理
    for (int i = 1;i < argc;i++){
        char *p = argv[i];
        // 若以-开头,则判定为选项
        if (*p == '-'){
            // -d选项
            if (strcmp(p, "-d") == 0){
                if (mode){
                    fputs("Error! Only one of the -d and -e options can be selected!\n", stderr);
                    exit(EXITCODE = ARGS_ERROR);
                }
                mode = DECODE;
            }
            // -e选项
            else if (strcmp(p, "-e") == 0){
                if (mode){
                    fputs("Error! Only one of the -d and -e options can be selected!\n", stderr);
                    exit(EXITCODE = ARGS_ERROR);
                }
                mode = ENCODE;
            }
            // --help选项
            else if (strcmp(p, "--help") == 0){
                fputs(HELP_INFO, stdout);
                exit(EXITCODE = SUCCESS);
            }
            // -o选项
            else if (strcmp(p, "-o") == 0){
                // 若outputfile已存在
                if (output_file_path != NULL){
                    fprintf(stderr, "Error! An outputfile %s is already given!\n", output_file_path);
                    exit(EXITCODE = ARGS_ERROR);
                }
                // 若为参数末尾
                if (i == argc - 1){
                    fputs("Error! Couldn't find outputfile in arguments!\n", stderr);
                    exit(EXITCODE = ARGS_ERROR);
                }
                output_file_path = argv[++i];
            }
            // 未知选项
            else{
                fprintf(stderr, ARG_ERROR_INFO, p);
                exit(EXITCODE = ARGS_ERROR);
            }
        }
        // 若参数不以-开头,则判定为输入文件名
        else{
            // 若存在多个文件名则报错
            if (input_file_path != NULL){
                fprintf(stderr, "Error! Multiple input_file set: %s and %s\n", input_file_path, p);
                exit(EXITCODE = ARGS_ERROR);
            }
            input_file_path = p;
        }
    }

    // 检测是否给出mode
    if(mode==UNDEFINE){
        fputs("Error! An option must be given from -e and -d!\n", stderr);
        exit(EXITCODE = ARGS_ERROR);
    }
    // 检测是否有inputfile
    if (input_file_path == NULL){
        fputs("Error! An inputfile must be given!\n", stderr);
        exit(EXITCODE = ARGS_ERROR);
    }
    // 检测-d选项下是否有outputfile
    if (mode == DECODE && output_file_path == NULL){
        fputs("Error! An outputfile must be given under -d option!\n", stderr);
        exit(EXITCODE = ARGS_ERROR);
    }

}

/* 根据全局参数设置输入流 */
static void set_input_stream(void){
    if (mode == ENCODE)
        input_file_stream = fopen(input_file_path, "rb");
    else
        input_file_stream = fopen(input_file_path, "r");
    if (input_file_stream == NULL){
        fprintf(stderr, "Error occur in open input_file %s!\n", input_file_path);
        exit(EXITCODE = HANDLE_ERROR);
    }

    // 获取文件大小
    struct stat input_file_info;
    if (fstat(fileno(input_file_stream), &input_file_info) != 0){
        fprintf(stderr, "Couldn't get info for file '%s'!\n", input_file_path);
        fclose(input_file_stream);
        exit(EXITCODE = HANDLE_ERROR);
    }
    input_file_size = (unsigned long long)input_file_info.st_size;

    //文件小于1M,1G,1T时,分别设置缓冲区
    if (input_file_size < 1048576ull){
        BUFFER_SIZE = 1024ull;
    } else if (input_file_size < 1073741824ull){
        BUFFER_SIZE = 1048576ull;
    } else if (input_file_size < 1099511627776ull){
        BUFFER_SIZE = 1073741824ull;
    } else{
        fprintf(stderr, "file '%s' is to big!\n", input_file_path);
        fclose(input_file_stream);
        exit(EXITCODE = HANDLE_ERROR);
    }

    buffer = malloc(BUFFER_SIZE);
    if (setvbuf(input_file_stream, buffer, _IOFBF, BUFFER_SIZE) != 0){
        fprintf(stderr, "Couldn't set read buffer for file '%s'!\n", input_file_path);
        fclose(input_file_stream);
        exit(EXITCODE = HANDLE_ERROR);
    }
}

/* 根据全局参数设置输出流 */
static void set_output_stream(void){
    // 设置输出流
    if (output_file_path != NULL){
        output_file_stream = fopen(output_file_path, "wb");
        if (output_file_stream == NULL){
            fprintf(stderr, "Error occur in open output_file %s, please check whether the path exists!\n", output_file_path);
            fclose(input_file_stream);
            free(buffer);
            exit(EXITCODE = HANDLE_ERROR);
        }
    } else{
        output_file_stream = stdout;
    }
    // 设置缓冲区
    if (setvbuf(output_file_stream, buffer, _IOFBF, BUFFER_SIZE) != 0){
        fputs("Couldn't set read buffer for output!", stderr);
        fclose(input_file_stream);
        fclose(output_file_stream);
        free(buffer);
        exit(EXITCODE = HANDLE_ERROR);
    }
}

/* 进行base64编码或解码 */
static void handle(void){
    unsigned char *input_file_content;
    unsigned char *output_file_content;
    unsigned long long output_file_size;

    if ((input_file_content = malloc(input_file_size)) == NULL){
        fputs("Insufficient memory space!", stderr);
        fclose(input_file_stream);
        fclose(output_file_stream);
        free(buffer);
    }

    switch (mode){
        case ENCODE:
            {
                fread(input_file_content, 1, input_file_size, input_file_stream);
                fclose(input_file_stream);
                output_file_size = get_encode_len(input_file_size);
                if ((output_file_content = malloc(output_file_size)) == NULL){
                    fputs("Insufficient memory space!", stderr);
                    free(input_file_content);
                    fclose(output_file_stream);
                    free(buffer);
                }

                base64_encode(input_file_content, input_file_size, output_file_content);
                fwrite(output_file_content, 1, output_file_size, output_file_stream);
            }
            break;
        case DECODE:
            {
                unsigned char *p = input_file_content;
                char c;
                while ((c = fgetc(input_file_stream)) != EOF){
                    if (c != '\n')
                        *p++ = (unsigned char)c;
                }
                fclose(input_file_stream);

                unsigned long long effective_input_file_size = p - input_file_content;
                // 进行解码前首先判断base64码的合法性
                if (!is_valid_base64_code(input_file_content, effective_input_file_size)){
                    fputs("Invalid base64 code!\n", stderr);
                    free(input_file_content);
                    fclose(output_file_stream);
                    free(buffer);
                }
                output_file_size = get_decode_len(input_file_content, effective_input_file_size);
                if ((output_file_content = malloc(output_file_size)) == NULL){
                    fputs("Insufficient memory space!\n", stderr);
                    free(input_file_content);
                    fclose(output_file_stream);
                    free(buffer);
                }
                base64_decode(input_file_content, effective_input_file_size, output_file_content);
                fwrite(output_file_content, 1, output_file_size, output_file_stream);
            }
            break;
    }

    free(input_file_content);
    free(output_file_content);
    fclose(output_file_stream);
    free(buffer);
}

我使用的是编译环境是MinGW64 13.2.0,编译命令:

gcc base64.c base64_main.c -o base64 -O3

运行结果:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值