/*
 * help - display help for calc
 *
 * Copyright (C) 1999-2007,2014,2018,2021  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/
 */


#include <stdio.h>
#include <ctype.h>
#include <sys/types.h>
#include <signal.h>
#include <sys/errno.h>

#include "calc.h"
#include "conf.h"
#include "lib_calc.h"

#include "have_unistd.h"
#if defined(HAVE_UNISTD_H)
#include <unistd.h>
#endif

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


#include "banned.h"     /* include after system header <> 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
 */
S_FUNC 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 *)malloc(snprintf_len+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 *)malloc(cust_snprintf_len+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 /* CUSTOM */
        /* unable to open help file */
        } else {
                fprintf(stderr, "unable to open help file: %s - %s\n",
                                type, strerror(errno));
#endif /* CUSTOM */
        }

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