/*
 * help - display help for calc
 *
 * Copyright (C) 1999-2007,2014,2018,2021,2026  Landon Curt Noll
 *
 * Calc is open software; you can redistribute it and/or modify it under
 * the terms of the version 2.1 of the GNU Lesser General Public License
 * as published by the Free Software Foundation.
 *
 * Calc 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 Lesser General
 * Public License for more details.
 *
 * A copy of version 2.1 of the GNU Lesser General Public License is
 * distributed with calc under the filename COPYING-LGPL.  You should have
 * received a copy with calc; if not, write to Free Software Foundation, Inc.
 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 * Under source code control:   1997/09/14 10:58:30
 * File existed as early as:    1997
 *
 * chongo <was here> /\oo/\     http://www.isthe.com/chongo/
 * Share and enjoy!  :-)        http://www.isthe.com/chongo/tech/comp/calc/
 */

/*
 * important <system> header includes
 */
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include <signal.h>
#include <setjmp.h>
#include <sys/types.h>
#include <stdint.h>
#include <stdbool.h>

/*
 * calc local src includes
 */
#include "value.h"
#include "calc.h"
#include "conf.h"
#include "lib_calc.h"

#if defined(_WIN32) || defined(_WIN64)
#  define popen _popen
#  define pclose _pclose
#endif

#include "banned.h" /* include after all other includes */

/*
 * some help topics are symbols, so we alias them to nice filenames
 */
static struct help_alias {
    char *topic;
    char *filename;
} halias[] = {
    /* We sort the list below via: sort -d -u */
    {"=", "address"},
    {"->", "arrow"},
    {"=", "assign"},
    {"*", "dereference"},
    {"%", "mod"},
    {".", "oldvalue"},
    {"#", "pound"},
    {"//", "quo"},
    {"COPYING_LGPL", "COPYING-LGPL"},
    {"COPYLEFT", "copyright"},
    {"COPYRIGHT", "copyright"},
    {"Copyleft", "copyright"},
    {"Copyright", "copyright"},
    {"abort", "command"},
    {"cd", "command"},
    {"copy", "blkcpy"},
    {"copying", "COPYING"},
    {"copying-lgpl", "COPYING-LGPL"},
    {"define", "command"},
    {"dm2d", "dms2d"},
    {"exit", "command"},
    {"gm2g", "gms2g"},
    {"hm2d", "hms2d"},
    {"man", "calc"},
    {"question", "questions"},
    {"quit", "command"},
    {"read", "command"},
    {"release", "releases"},
    {"show", "command"},
    {"stdlib", "resource"},
    {"usage", "calc"},
    {"write", "command"},

    /* MUST BE LAST! */
    {NULL, NULL}};

/*
 * external values
 */
extern char *pager; /* $PAGER or default */

/*
 * page_file - output an open file thru a pager
 *
 * The popen() below is fairly safe.  The $PAGER environment variable
 * (supplied by the user) is the command we need to run without args.
 * True, the user could set $PAGER is a bogus prog ... but if -m is
 * 5 or 7 (read and exec allowed), then the calc is given ``permission''
 * to open the help file for reading as well as to exec the pager!
 *
 * given:
 *      stream  open file stream of the file to send to the pager
 */
static void
page_file(FILE *stream)
{
    FILE *cmd;            /* pager command */
    char buf[BUFSIZ + 1]; /* I/O buffer */
    char *fgets_ret;      /* fgets() return value */

    /*
     * flush any pending I/O
     */
    fflush(stderr);
    fflush(stdout);
    fflush(stdin);

    /*
     * form a write pipe to a pager
     */
    if (pager == NULL || pager[0] == '\0') {
        cmd = stdout;
    } else {
        cmd = popen(pager, "w");
    }
    if (cmd == NULL) {
        fprintf(stderr, "unable form pipe to pager: %s", pager);
        return;
    }

    /*
     * read the help file and send non-## lines to the pager
     * until EOF or error
     */
    buf[BUFSIZ] = '\0';
    do {

        /*
         * read the next line that does not begin with ##
         */
        buf[0] = '\0';
        while ((fgets_ret = fgets(buf, BUFSIZ, stream)) != NULL && buf[0] == '#' && buf[1] == '#') {
        }

        /*
         * stop reading when we reach EOF (or error) on help file
         */
        if (fgets_ret == NULL) {
            fflush(cmd);
            break;
        }

        /*
         * write the line to pager, if possible
         *
         * We have to use fprintf() instead of fputs() because
         * on NetBSD, fputs() function returns 0 on success and
         # EOF on error.  *sigh*
         */
    } while (fprintf(cmd, "%s", buf) > 0);

    /*
     * all done, EOF or error, so just clean up
     */
    (void)pclose(cmd);
    fflush(stderr);
    fflush(stdout);
    fflush(stdin);
    return;
}

/*
 * givehelp - display a help file
 *
 * given:
 *      type            the type of help to give, NULL => index
 */
void
givehelp(char *type)
{
    struct help_alias *p; /* help alias being considered */
    FILE *stream;         /* help file stream */
    char *helppath;       /* path to the help file */
    char *c;
    size_t snprintf_len; /* malloced snprintf buffer length */

    /*
     * check permissions to see if we are allowed to help
     */
    if (!allow_exec || !allow_read) {
        fprintf(stderr, "sorry, help is only allowed with -m mode 5 or 7\n");
        return;
    }

    /* catch the case where we just print the index */
    if (type == NULL) {
        type = DEFAULTCALCHELP; /* the help index file */
    }

    /* alias the type of help, if needed */
    for (p = halias; p->topic; ++p) {
        if (strcmp(type, p->topic) == 0) {
            type = p->filename;
            break;
        }
    }

    /*
     * sanity check on name
     */
    /* look for /. or a leading . */
    if (strstr(type, "/.") != NULL || type[0] == '.') {
        fprintf(stderr, "bad help name\n");
        return;
    }
    /* look for chars that could be shell meta chars */
    for (c = type; *c; ++c) {
        switch ((int)*c) {
        case '+':
        case ',':
        case '-':
        case '.':
        case '/':
        case '_':
            break;
        default:
            if (!isascii((int)*c) || !isalnum((int)*c)) {
                fprintf(stderr, "bogus char in help name\n");
                return;
            }
            break;
        }
    }

    /*
     * special case for Copyright and Copyleft
     */
    if (strcmp(type, "copyright") == 0) {
        fputs(Copyright, stdout);
        fflush(stdout);
        return;
    }

    /*
     * open the helpfile (looking in HELPDIR first)
     */
    snprintf_len = strlen(calc_helpdir) + 1 + strlen(type) + 1;
    helppath = (char *)calloc(snprintf_len + 1, 1);
    if (helppath == NULL) {
        fprintf(stderr, "malloc failure in givehelp #0\n");
        return;
    }
    snprintf(helppath, snprintf_len, "%s/%s", calc_helpdir, type);
    helppath[snprintf_len] = '\0'; /* paranoia */
    errno = 0;
    stream = fopen(helppath, "r");
    if (stream != NULL) {

        /*
         * we have the help file open, now display it
         */
        page_file(stream);
        (void)fclose(stream);

#if defined(CUSTOM)

        /*
         * open the helpfile (looking in CUSTOMHELPDIR last)
         */
    } else {
        char *cust_helppath;      /* path to the custom help file */
        size_t cust_snprintf_len; /* malloced custom snprintf buf len */

        cust_snprintf_len = strlen(calc_customhelpdir) + 1 + strlen(type) + 1;
        cust_helppath = (char *)calloc(cust_snprintf_len + 1, 1);
        if (cust_helppath == NULL) {
            fprintf(stderr, "malloc failure for givehelp #1\n");
            return;
        }
        snprintf(cust_helppath, cust_snprintf_len, "%s/%s", calc_customhelpdir, type);
        cust_helppath[cust_snprintf_len] = '\0'; /* paranoia */
        errno = 0;
        stream = fopen(cust_helppath, "r");
        if (stream != NULL) {

            /*
             * we have the help file open, now display it
             */
            page_file(stream);
            (void)fclose(stream);

            /* unable to open help file */
        } else {
            fprintf(stderr, "unable to open help file: %s - %s\n", type, strerror(errno));
        }
        free(cust_helppath);
        cust_helppath = NULL;

#else

        /* unable to open help file */
    } else {
        fprintf(stderr, "unable to open help file: %s - %s\n", type, strerror(errno));

#endif
    }

    /*
     * cleanup
     */
    free(helppath);
    helppath = NULL;
    return;
}
