/*
 * Part of DNS zone file validator `validns`.
 *
 * Copyright 2025-2026 OARC, Inc.
 * Copyright 2011-2025 Anton Berezin <tobez@tobez.org>
 * Modified BSD license.
 * (See LICENSE file in the distribution.)
 *
 */
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <ctype.h>

#include "base64.h"

/*
 * Very straightforward, ugly, unoptimized,
 * not much in the way of error handling.
 * But it works.
 */

int decode_base64(void* dest, char* src, size_t dstsize)
{
    size_t         processed  = 0;
    int            full_bytes = 0;
    unsigned char* dst        = dest;

    while (*src) {
        int v;
        if (*src >= 'A' && *src <= 'Z')
            v = *src - 'A';
        else if (*src >= 'a' && *src <= 'z')
            v = 26 + *src - 'a';
        else if (*src >= '0' && *src <= '9')
            v = 52 + *src - '0';
        else if (*src == '+')
            v = 62;
        else if (*src == '/')
            v = 63;
        else if (isspace(*src) || *src == '=') {
            src++;
            continue;
        } else {
            /* any junk chars means input is corrupted */
            errno = EINVAL;
            return -1;
        }
        src++;
        if (processed % 4 == 0) {
            if (dstsize <= 0) {
                errno = EINVAL;
                return -1;
            }
            dst[0] &= 0x03;
            dst[0] |= (v << 2) & 0xFC;
            processed++;
        } else if (processed % 4 == 1) {
            if (dstsize < 1) {
                errno = EINVAL;
                return -1;
            }
            dst[0] &= 0xFC;
            dst[0] |= (v >> 4) & 0x03;
            if (dstsize >= 2) {
                dst[1] &= 0x0F;
                dst[1] |= (v << 4) & 0xF0;
            }
            processed++;
            full_bytes++;
        } else if (processed % 4 == 2) {
            if (dstsize < 2) {
                errno = EINVAL;
                return -1;
            }
            dst[1] &= 0xF0;
            dst[1] |= (v >> 2) & 0x0F;
            if (dstsize >= 3) {
                dst[2] &= 0x3F;
                dst[2] |= (v << 6) & 0xC0;
            }
            processed++;
            full_bytes++;
        } else {
            if (dstsize <= 2) {
                errno = EINVAL;
                return -1;
            }
            dst[2] &= 0xC0;
            dst[2] |= v & 0x3F;
            processed++;
            dst += 3;
            dstsize -= 3;
            full_bytes++;
        }
    }
    return full_bytes;
}

#ifdef TEST_PROGRAM

static int verbose = 0;

static int ok_string_test(int testnum, char* src, char* expect)
{
    unsigned char dstbuf[512];
    int           r, r0, i;
    int           expect_sz = strlen(expect);

    if (expect_sz >= 512) {
        printf("test %d: NOT OK: internal *test* error, buffer too small for proper testing, FIXME\n", testnum);
        return 1;
    }
    memset(dstbuf, 0xAA, 512);
    r = decode_base64(dstbuf, src, expect_sz);
    if (r != expect_sz) {
        printf("test %d: NOT OK: expect size %d, got %d\n", testnum, expect_sz, r);
        return 1;
    } else if (memcmp(dstbuf, expect, r) != 0) {
        printf("test %d: NOT OK: unexpected buffer content\n", testnum);
        return 1;
    }
    if (dstbuf[expect_sz] != 0xAA) {
        printf("test %d: NOT OK: corrupts memory with \"just enough\" bufsize\n", testnum);
        return 1;
    }
    memset(dstbuf, 0xAA, 512);
    for (i = 0; i < expect_sz; i++) {
        r0 = decode_base64(dstbuf, src, i);
        if (r0 > 0) {
            printf("test %d: NOT OK: buffer size %d should not be enough\n", testnum, i);
            return 1;
        }
        if (dstbuf[i] != 0xAA) {
            printf("test %d: NOT OK: corrupts memory with bufsize %d\n", testnum, i);
            return 1;
        }
    }
    if (verbose)
        printf("test %d: ok\n", testnum);
    return 0;
}

static int expect_junk_error(int testnum, char* src)
{
    char* buf[20];
    int   r;

    r = decode_base64(buf, src, 20);
    if (r != -1) {
        printf("test %d: NOT OK: junk input not recognized\n", testnum);
        return 1;
    }
    if (verbose)
        printf("test %d: ok\n", testnum);
    return 0;
}

int main(int argc, char** argv)
{
    int ret         = 0;
    int total_tests = 31;
    int i;

    for (i = 1; i < argc; i++) {
        if (strcmp(argv[i], "-v") == 0)
            verbose = 1;
    }

    /* from http://en.wikipedia.org/wiki/Base64 */
    ret |= ok_string_test(1, "bGVhc3VyZS4=", "leasure.");
    ret |= ok_string_test(2, "bGVhc3VyZS4", "leasure.");
    ret |= ok_string_test(3, "ZWFzdXJlLg==", "easure.");
    ret |= ok_string_test(4, "ZWFzdXJlLg=", "easure.");
    ret |= ok_string_test(5, "ZWFzdXJlLg", "easure.");
    ret |= ok_string_test(6, "YXN1cmUu", "asure.");
    ret |= ok_string_test(7, "c3VyZS4=", "sure.");
    ret |= ok_string_test(8, "c3VyZS4", "sure.");
    ret |= ok_string_test(9, "TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlz\n"
                             "IHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2Yg\n"
                             "dGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGlu\n"
                             "dWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRo\n"
                             "ZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4=",
        "Man is distinguished, not only by his reason, but by this singular passion from other animals, which is a lust of the mind, that by a perseverance of delight in the continued and indefatigable generation of knowledge, exceeds the short vehemence of any carnal pleasure.");
    /* from http://tools.ietf.org/html/rfc4648#section-10 */
    ret |= ok_string_test(10, "", "");
    ret |= ok_string_test(11, "Zg==", "f");
    ret |= ok_string_test(12, "Zg=", "f");
    ret |= ok_string_test(13, "Zg", "f");
    ret |= ok_string_test(14, "Zm8=", "fo");
    ret |= ok_string_test(15, "Zm8", "fo");
    ret |= ok_string_test(16, "Zm9v", "foo");
    ret |= ok_string_test(17, "Zm9vYg==", "foob");
    ret |= ok_string_test(18, "Zm9vYg=", "foob");
    ret |= ok_string_test(19, "Zm9vYg", "foob");
    ret |= ok_string_test(20, "Zm9vYmE=", "fooba");
    ret |= ok_string_test(21, "Zm9vYmE", "fooba");
    ret |= ok_string_test(22, "Zm9vYmFy", "foobar");

    ret |= expect_junk_error(23, "?Zm9vYmFy");
    ret |= expect_junk_error(24, "Z%m9vYmFy");
    ret |= expect_junk_error(25, "Zm&9vYmFy");
    ret |= expect_junk_error(26, "Zm9-vYmFy");
    ret |= expect_junk_error(27, "Zm9v*YmFy");
    ret |= expect_junk_error(28, "Zm9vY#mFy");
    ret |= expect_junk_error(29, "Zm9vYm\x01Fy");
    ret |= expect_junk_error(30, "Zm9vYmF!y");
    ret |= expect_junk_error(31, "Zm9vYmFy.");

    if (ret) {
        printf("%d/%d tests failed.\n", ret, total_tests);
    } else {
        printf("All %d tests successful.\n", total_tests);
    }

    return ret;
}
#endif
