/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "plgetopt.h"
#include "secutil.h"
#include "nssb64.h"
#include <errno.h>

#if defined(XP_WIN) || (defined(__sun) && !defined(SVR4))
#if !defined(WIN32)
extern int fread(char *, size_t, size_t, FILE *);
extern int fwrite(char *, size_t, size_t, FILE *);
extern int fprintf(FILE *, char *, ...);
#endif
#endif

#if defined(WIN32)
#include "fcntl.h"
#include "io.h"
#endif

static PRInt32
output_binary(void *arg, const unsigned char *obuf, PRInt32 size)
{
    FILE *outFile = arg;
    int nb;

    nb = fwrite(obuf, 1, size, outFile);
    if (nb != size) {
        PORT_SetError(SEC_ERROR_IO);
        return -1;
    }

    return nb;
}

static PRBool
isBase64Char(char c)
{
    return ((c >= 'A' && c <= 'Z') ||
            (c >= 'a' && c <= 'z') ||
            (c >= '0' && c <= '9') ||
            c == '+' || c == '/' ||
            c == '=');
}

static SECStatus
decode_file(FILE *outFile, FILE *inFile)
{
    NSSBase64Decoder *cx;
    SECStatus status = SECFailure;
    char ibuf[4096];
    const char *ptr;

    cx = NSSBase64Decoder_Create(output_binary, outFile);
    if (!cx) {
        return -1;
    }

    for (;;) {
        if (feof(inFile))
            break;
        if (!fgets(ibuf, sizeof(ibuf), inFile)) {
            if (ferror(inFile)) {
                PORT_SetError(SEC_ERROR_IO);
                goto loser;
            }
            /* eof */
            break;
        }
        for (ptr = ibuf; *ptr; ++ptr) {
            char c = *ptr;
            if (c == '\n' || c == '\r') {
                break; /* found end of line */
            }
            if (!isBase64Char(c)) {
                ptr = ibuf; /* ignore line */
                break;
            }
        }
        if (ibuf == ptr) {
            continue; /* skip empty or non-base64 line */
        }

        status = NSSBase64Decoder_Update(cx, ibuf, ptr - ibuf);
        if (status != SECSuccess)
            goto loser;
    }

    return NSSBase64Decoder_Destroy(cx, PR_FALSE);

loser:
    (void)NSSBase64Decoder_Destroy(cx, PR_TRUE);
    return status;
}

static void
Usage(char *progName)
{
    fprintf(stderr,
            "Usage: %s [-i input] [-o output]\n",
            progName);
    fprintf(stderr, "%-20s Define an input file to use (default is stdin)\n",
            "-i input");
    fprintf(stderr, "%-20s Define an output file to use (default is stdout)\n",
            "-o output");
}

int
main(int argc, char **argv)
{
    char *progName;
    SECStatus rv;
    FILE *inFile = NULL, *outFile = NULL;
    PRBool closeIn = PR_TRUE, closeOut = PR_TRUE;
    PLOptState *optstate = NULL;
    PLOptStatus status;
    int exitCode = -1;

    progName = strrchr(argv[0], '/');
    progName = progName ? progName + 1 : argv[0];

    /* Parse command line arguments */
    optstate = PL_CreateOptState(argc, argv, "?hi:o:");
    while ((status = PL_GetNextOpt(optstate)) == PL_OPT_OK) {
        switch (optstate->option) {
            case '?':
            case 'h':
                Usage(progName);
                goto loser;
                break;

            case 'i':
                inFile = fopen(optstate->value, "r");
                if (!inFile) {
                    fprintf(stderr, "%s: unable to open \"%s\" for reading\n",
                            progName, optstate->value);
                    goto loser;
                }
                break;

            case 'o':
                outFile = fopen(optstate->value, "wb");
                if (!outFile) {
                    fprintf(stderr, "%s: unable to open \"%s\" for writing\n",
                            progName, optstate->value);
                    goto loser;
                }
                break;
        }
    }
    if (!inFile) {
        inFile = stdin;
        closeIn = PR_FALSE;
    }
    if (!outFile) {
#if defined(WIN32)
        int smrv = _setmode(_fileno(stdout), _O_BINARY);
        if (smrv == -1) {
            fprintf(stderr,
                    "%s: Cannot change stdout to binary mode. Use -o option instead.\n",
                    progName);
            goto loser;
        }
#endif
        outFile = stdout;
        closeOut = PR_FALSE;
    }
    rv = decode_file(outFile, inFile);
    if (rv != SECSuccess) {
        fprintf(stderr, "%s: lossage: error=%d errno=%d\n",
                progName, PORT_GetError(), errno);
        goto loser;
    }
    exitCode = 0;
loser:
    if (optstate) {
        PL_DestroyOptState(optstate);
    }
    if (inFile && closeIn) {
        fclose(inFile);
    }
    if (outFile && closeOut) {
        fclose(outFile);
    }
    return exitCode;
}
