/* ========================================================================== */
/*! \file
 * \brief External program delegation functions
 *
 * Copyright (c) 2012-2024 by the developers. See the LICENSE file for details.
 *
 * If nothing else is specified, functions return zero to indicate success
 * and a negative value to indicate an error.
 */


/* ========================================================================== */
/* Include headers */

#include "posix.h"  /* Include this first because of feature test macros */

#include <ctype.h>
#include <stdarg.h>
#include <string.h>

#include "conf.h"
#include "encoding.h"
#include "extutils.h"
#include "fileutils.h"
#include "http.h"
#include "main.h"
#include "sighandler.h"


/* ========================================================================== */
/*! \defgroup EXTUTILS EXT: Delegation to external utilities
 *
 * This module calls external programs to delegate functionality like sending
 * E-Mail or displaying HTML.
 *
 * \attention
 * It is required that 'PID_MAX' is not larger than 'LONG_MAX' (must be checked
 * by build system).
 */
/*! @{ */


/* ========================================================================== */
/* Constants */

/*! \brief Message prefix for EXTUTILS module */
#define MAIN_ERR_PREFIX  "EXT: "


/* ========================================================================== */
/* Verify and make data usable as parameter for xdg-utils
 *
 * \param[in] s     UTF-8 string to check
 * \param[in] conv  Allows modification if true
 *
 * This function verifies that the string \e s can be passed as parameter for
 * xdg-utils on the command line of a POSIX shell between single quotes.
 *
 * If the data contains apostrophe characters (U+0027) an error is returned if
 * \e conv is false. Otherwise all such characters will be replaced with the
 * Unicode codepoint RIGHT SINGLE QUOTATION MARK (U+2019).
 *
 * \note
 * It is allowed to pass \c NULL for parameter \e s (this is handled as a
 * regular error condition).
 *
 * \return
 * - Pointer to new memory block with UTF-8 string on success
 * - NULL on error
 */

static char*  ext_check_data(const char*  s,  int  conv)
{
   char*  res = NULL;
   const char*  rsqm = "\xE2\x80\x99";  /* U+2019 in UTF-8 format */
   size_t  len;
   size_t  i = 0;
   size_t  ri;
   int  error = 0;

   if(NULL != s)
   {
      len = strlen(s);
      /* Check for ' character */
      if(NULL == strchr(s, 0x27))
      {
         /* No => Copy data unchanged */
         res = (char*) api_posix_malloc(++len);  /* One byte for NUL */
         if(NULL != res)  { memcpy(res, s, len); }
      }
      else if(conv)
      {
         /* Yes => Convert the data as requested */
         printf("%s: %sApostrophe replacement U+0027 => U+2019 done\n",
                CFG_NAME, MAIN_ERR_PREFIX);
         while(s[i])
         {
            if(0x27 == (int) s[i++])
            {
               if(API_POSIX_SIZE_MAX - len < (size_t) 2)
               {
                  PRINT_ERROR("Aborted before \x27size_t\x27 overflow");
                  error = 1;
               }
               else  { len += 2; }
            }
         }
         if(!error)
         {
            res = (char*) api_posix_malloc(++len); /* One byte for NUL */
            if(NULL != res)
            {
               i = 0;  ri = 0;
               do
               {
                  if(0x27 != (int) s[i])  { res[ri++] = s[i]; }
                  else
                  {
                     res[ri++] = rsqm[0];
                     res[ri++] = rsqm[1];
                     res[ri++] = rsqm[2];
                  }
               }
               while(s[++i]);
               /* Terminate result string */
               res[ri] = 0;
            }
         }
      }
   }

   return(res);
}


/* ========================================================================== */
/*! \brief Send e-mail to single recipient
 *
 * \param[in] recipient  Recipient (URI or RFC 5322 conformant \c addr-spec )
 * \param[in] subject    Subject (UTF-8 NFC encoded) or \c NULL
 * \param[in] body       Cited content for body (UTF-8 NFC encoded) or \c NULL
 *
 * This function calls the external program \c xdg-email to handle the e-mail
 * processing.
 *
 * If \e recipient is an URI with \c mailto scheme, the parameters \e subject
 * and \e body should be \c NULL and are ignored otherwise.
 *
 * \attention
 * If \e subject or \e body contain line breaks, they must be in POSIX form
 * (single LF).
 * According to RFC 2368 (Section 5) this is different for an URI, therefore if
 * an URI is passed as \e recipient , line breaks inside parameters must be in
 * canonical form (CR+LF, %0D%0A with percent encoding).
 *
 * \attention
 * The Unicode data is expected to be valid (encoding and normalization must
 * be verified by the caller).
 *
 * \note
 * If \e recipient is \c NULL the functions return an error. Therefore this
 * must not be checked by the caller.
 *
 * \return
 * - 0 if e-mail program was successfully started
 * - Negative value if start of shell failed
 * - Positive value if shell reported an error
 */

int  ext_handler_email(const char*  recipient, const char*  subject,
                       const char*  body)
{
   int  res = -1;
   const char*  mailto = "mailto:";
   const char*  command = "xdg-email --utf8";
   const char*  opt_subject = " --subject \x27";
   const char*  opt_body = " --body \x27";
   int  error = 1;
   int  uri = 0;
   char*  buf = NULL;
   char*  tmp;
   size_t  len = 0;
   size_t  len_tmp;
   size_t  x;
   FILE*  fs;

   /* Check for URI with scheme 'mailto' */
   if(NULL != recipient)
   {
      len = strlen(mailto);
      if(strlen(recipient) >= len && !strncmp(recipient, mailto, len))
      {
         /* Found => This means the data should already have RFC 2368 format */
         uri = 1;
      }
   }

   /*
    * Verify data
    * The variables 'recipient', 'subject' and 'body' must be prepared so that
    * 'free()' can be called on them at the end.
    */
   if(NULL == recipient)
   {
      PRINT_ERROR("No e-mail recipient specified");
      subject = NULL;
      body = NULL;
   }
   else
   {
      /* Non US-ASCII characters are not allowed without percent encoding */
      if(uri && enc_ascii_check(recipient))
      {
         PRINT_ERROR("Invalid characters in URI");
         recipient = NULL;
         subject = NULL;
         body = NULL;
      }
      else
      {
         recipient = ext_check_data(recipient, 0);
         if(NULL == recipient)
         {
            if(uri)
            {
               PRINT_ERROR("URI with \x27mailto\x27 scheme cannot be handled");
            }
            else
            {
               PRINT_ERROR("e-mail recipient address cannot be handled");
            }
            subject = NULL;
            body = NULL;
         }
         else
         {
            /* Recipient looks good => Start processing */
            printf("%s: %se-mail delegation to xdg-email for: %s\n",
                   CFG_NAME, MAIN_ERR_PREFIX, recipient);
            error = 0;

            /* Ignore subject and body if not usable */
            if(NULL != subject)
            {
               subject = ext_check_data(subject, 1);
               if(NULL == subject)
               {
                  PRINT_ERROR("e-mail subject cannot be handled (ignored)");
               }
            }
            if(NULL != body)
            {
               body = ext_check_data(body, 1);
               if(NULL == body)
               {
                  PRINT_ERROR("e-mail body cannot be handled (ignored)");
               }
            }
         }
      }
   }

   /* Create command line */
   if(!error)
   {
      len = strlen(command);
      buf = (char*) api_posix_malloc(++len);  /* One byte for NUL */
      if(NULL == buf)  { error = 1; }
      else  { strncpy(buf, command, len); }
   }

   /* Append subject if present */
   if(!error && !uri && NULL != subject)
   {
      len_tmp = strlen(subject);
      x = strlen(opt_subject) + (size_t) 1;  /* One additional byte for ' */
      if(API_POSIX_SIZE_MAX - len_tmp < x)  { error = 1; }
      else
      {
         if(API_POSIX_SIZE_MAX - len < len_tmp + x)  { error = 1; }
         else
         {
            len += len_tmp + x;
            tmp = (char*) api_posix_realloc(buf, len);
            if(NULL == tmp)  { error = 1; }
            else
            {
               buf = tmp;
               strcat(buf, opt_subject);
               strncat(buf, subject, len_tmp);
               strcat(buf, "\x27");
            }
         }
      }
   }

   /* Append body if present */
   if(!error && !uri && NULL != body)
   {
      len_tmp = strlen(body);
      x = strlen(opt_body) + (size_t) 1;  /* One additional byte for ' */
      if(API_POSIX_SIZE_MAX - len_tmp < x)  { error = 1; }
      else
      {
         if(API_POSIX_SIZE_MAX - len < len_tmp + x)  { error = 1; }
         else
         {
            len += len_tmp + x;
            tmp = (char*) api_posix_realloc(buf, len);
            if(NULL == tmp)  { error = 1; }
            else
            {
               buf = tmp;
               strcat(buf, opt_body);
               strncat(buf, body, len_tmp);
               strcat(buf, "\x27");
            }
         }
      }
   }

   /* Append recipient */
   if(!error)
   {
      len_tmp = strlen(recipient);
      if(API_POSIX_SIZE_MAX - len_tmp < (size_t) 3)  { error = 1; }
      else
      {
         if(API_POSIX_SIZE_MAX - len < len_tmp + (size_t) 3)  { error = 1; }
         else
         {
            /* 3 additional bytes for space, leading and trailing ' */
            len += len_tmp + (size_t) 3;
            tmp = (char*) api_posix_realloc(buf, len);
            if(NULL == tmp)  { error = 1; }
            else
            {
               buf = tmp;
               strcat(buf, " \x27");
               strncat(buf, recipient, len_tmp);
               strcat(buf, "\x27");
            }
         }
      }
   }


   /* Spawn new process for e-mail handling */
   if(!error)
   {
      fs = api_posix_popen(buf, "w");
      if(NULL != fs)  { res = api_posix_pclose(fs); }
   }

   /* Release memory */
   api_posix_free((void*) body);
   api_posix_free((void*) subject);
   api_posix_free((void*) recipient);
   api_posix_free((void*) buf);

   return(res);
}


/* ========================================================================== */
/*! \brief Start external handler for URI
 *
 * \param[in]  uri      Pointer to URI string
 * \param[out] invalid  Pointer to invalid encoding flag
 *
 * If the URI encoding of \e uri is invalid, a negative value is returned and
 * a nonzero value is written to the location pointed to by \e invalid .
 * Otherwise zero is written to the location pointed to by \e invalid .
 *
 * This function calls the external program \c xdg-open to handle the URI.
 *
 * \attention
 * Because the current version of \c xdg-open starts a WWW browser, call this
 * function only for URIs that typically can be handled by such programs
 * (like \c http:// or \c ftp:// types).
 *
 * \note
 * On Apple platform, the program \c open is used to handle the URI.
 *
 * \return
 * - 0 if external URI handler was successfully started
 * - Negative value if start of shell failed
 * - Positive value if shell reported an error
 */

int  ext_handler_uri(const char*  uri, int*  invalid)
{
   int  res = -1;
#ifdef __APPLE__
   const char*  command = "open";
#else  /* __APPLE__ */
   const char*  command = "xdg-open";
#endif  /* __APPLE__ */
   int  error = 1;
   char*  buf = NULL;
   char*  tmp;
   size_t  len = 0;
   size_t  len_tmp;
   FILE*  fs;

   /*
    * Verify data
    * The variable 'uri' must be prepared so that 'free()' can be called on it
    * at the end.
    */
   *invalid = 1;
   if(NULL == uri)  { PRINT_ERROR("No URI specified"); }
   else
   {
      /* Non US-ASCII characters are not allowed without percent encoding */
      if(enc_ascii_check(uri))
      {
         PRINT_ERROR("Invalid characters in URI");
         uri = NULL;
      }
      else
      {
         uri = ext_check_data(uri, 0);
         if(NULL == uri)
         {
            PRINT_ERROR("URI cannot be handled");
         }
         else
         {
            /* URI passed verification */
            *invalid = 0;
            error = 0;
         }
      }
   }

   /* Create command line */
   if(!error)
   {
      printf("%s: %sURI delegation to %s for: %s\n",
             CFG_NAME, MAIN_ERR_PREFIX, command, uri);
      len = strlen(command);
      buf = (char*) api_posix_malloc(++len);  /* One byte for NUL */
      if(NULL == buf)  { error = 1; }
      else  { strncpy(buf, command, len); }
   }

   /* Append recipient */
   if(!error)
   {
      len_tmp = strlen(uri);
      if(API_POSIX_SIZE_MAX - len_tmp < (size_t) 5)  { error = 1; }
      else
      {
         if(API_POSIX_SIZE_MAX - len < len_tmp + (size_t) 5)  { error = 1; }
         else
         {
            /* 5 additional bytes for spaces, leading/trailing ' and & */
            len += len_tmp + (size_t) 5;
            tmp = (char*) api_posix_realloc(buf, len);
            if(NULL == tmp)  { error = 1; }
            else
            {
               buf = tmp;
               strcat(buf, " \x27");
               strcat(buf, uri);
               strcat(buf, "\x27 &");
            }
         }
      }
   }

   /* Spawn new process for URI handling */
   if(!error)
   {
      fs = api_posix_popen(buf, "w");
      if(NULL != fs)  { res = api_posix_pclose(fs); }
   }

   /* Release memory */
   api_posix_free((void*) uri);
   api_posix_free((void*) buf);

   return(res);
}


/* ========================================================================== */
/*! \brief Start external editor for article composition
 *
 * \param[in] tfpn       Pathname of temporary file (with UTF-8 content) to edit
 * \param[in] async      Flag indicating that function should return immediately
 *
 * The name of the external editor is taken from the configfile. It may not be
 * a full pathname, the editor is searched via \c $PATH in this case.
 *
 * If \e async is zero, the calling thread is blocked until the process with
 * the editor has terminated. No additional parameter should be passed.
 *
 * If \e async is nonzero, a third parameter of type (long int*) must be passed
 * by the caller. This function will write the PID of the editor process to this
 * location and the editor will be started asynchronously.
 * If this function returns success, the status of the external editor can be
 * polled with the function \ref ext_editor_status() using the returned PID.
 *
 * \attention
 * The caller must ensure that the file associated with \e tfpn is not modifed
 * by the caller until editing is finished (either synchronous or asynchronous).
 *
 * \return
 * - 0 if the file was successfully edited or asynchronous processing has been
 *   started successfully
 * - Negative value on local error
 * - Positive value if external editor reported error
 */

int  ext_editor(const char*  tfpn, int  async, ...)
{
   va_list  ap;                       /* Object for argument list handling */
   int  res = -1;
   const char*  epn = config[CONF_EDITOR].val.s;
   size_t  len = strlen(epn);
   api_posix_pid_t  pid;
   int  rv;
   api_posix_pid_t  rv2;
   int  status = -1;

   if(len && NULL != tfpn)
   {
      if(main_debug)
      {
         printf("%s: %sExternal editor (path)name: \x22%s\x22\n",
                CFG_NAME, MAIN_ERR_PREFIX, epn);
      }
      /* Spawn child process for editor */
      pid = api_posix_fork();
      if(!pid)
      {
         /* Executed by child process */
         rv = sighandler_exec_prepare();
         if(!rv)  { api_posix_execlp(epn, epn, tfpn, (char*) NULL); }
         PRINT_ERROR("Executing external editor failed");
         exit(API_POSIX_EXIT_FAILURE);
      }
      else if(-1 != pid)
      {
         res = 0;
         if(async)
         {
            /* Return PID to caller */
            va_start(ap, async);
            *(va_arg(ap, long int*)) = (long int) pid;
            va_end(ap);
         }
         else
         {
            /* Get exit status of editor */
            do  { rv2 = api_posix_waitpid(pid, &status, 0); }
            while((api_posix_pid_t) -1 == rv2
                  && API_POSIX_EINTR == api_posix_errno);
            if(rv2 != pid)
            {
               PRINT_ERROR("Child process not found (bug)");
               res = -1;
            }
            else
            {
               if(!status)  { res = 0; }
               else
               {
                  PRINT_ERROR("External editor reported error");
                  if(!API_POSIX_WIFEXITED(status))
                  {
                     res = API_POSIX_EXIT_FAILURE;
                  }
                  else  { res = API_POSIX_WEXITSTATUS(status); }
               }
            }
         }
      }
   }

   return(res);
}


/* ========================================================================== */
/*! \brief Poll status of external editor
 *
 * \param[in] editor_pid  PID of external editor
 *
 * \attention
 * The value \e editor_pid must correspond to a value returned by
 * \ref ext_editor() in asynchronous mode.
 *
 * \attention
 * It is not allowed to call this function again for the same value of
 * \e editor_pid after either success or error was returned.
 *
 * \return
 * - 0 Success
 * - -1 if external editor is still running
 * - Other negative value on local error
 * - Positive value if external editor reported error
 */

int  ext_editor_status(long int  editor_pid)
{
   int  res = -1;
   api_posix_pid_t  pid = (api_posix_pid_t) editor_pid;
   api_posix_pid_t  rv2;
   int  status = -1;

   /* Get exit status of editor */
   do  { rv2 = api_posix_waitpid(pid, &status, API_POSIX_WNOHANG); }
   while((api_posix_pid_t) -1 == rv2 && API_POSIX_EINTR == api_posix_errno);
   if(rv2)
   {
      if(rv2 != pid)
      {
         PRINT_ERROR("Child process not found (bug)");
         res = -2;
      }
      else
      {
         if(!status)  { res = 0; }
         else
         {
            PRINT_ERROR("External editor reported error");
            if(!API_POSIX_WIFEXITED(status))  { res = API_POSIX_EXIT_FAILURE; }
            else  { res = API_POSIX_WEXITSTATUS(status); }
         }
      }
   }

   return(res);
}


/* ========================================================================== */
/*! \brief Terminate external editor
 *
 * \param[in] editor_pid  PID of external editor
 *
 * \attention
 * The value \e editor_pid must correspond to a value returned by
 * \ref ext_editor() in asynchronous mode.
 *
 * \attention
 * It is not allowed to call this function multiple times for the same editor.
 */

void  ext_editor_terminate(long int  editor_pid)
{
   api_posix_kill((api_posix_pid_t) editor_pid, API_POSIX_SIGTERM);
}


/* ========================================================================== */
/*! \brief External post processing filter for outgoing articles
 *
 * \param[in] article    Pointer to article (in canonical form)
 *
 * \note
 * The body of \e article is still in Unicode and the MIME content type is not
 * added to the header yet.
 *
 * The pathname of the external filter is taken from the configfile.
 *
 * \attention
 * The external postprocessor is not allowed to add MIME related header fields
 * like \c Content-Type and \c Content-Transfer-Encoding and must always create
 * valid UTF-8 encoded data.
 *
 * The Unicode normalization and conversion to the target character set is done
 * after the postprocessing.
 *
 * The caller is responsible to free the memory allocated for the result.
 *
 * \return
 * - Pointer to postprocessed article.
 *   If the result is not equal to \e article , a new memory block was allocated
 * - \c NULL on error
 */

const char*  ext_pp_filter(const char*  article)
{
   const char*  res = article;
   const char*  filter_pathname = config[CONF_PPROC].val.s;
   int  rv;
   api_posix_struct_stat  state;
   int  fd_in[2];
   int  fd_out[2];
   api_posix_pid_t  pid;
   api_posix_pid_t  rv2;
   int  error = 0;
   int  pushed = 0;
   int  finished = 0;
   size_t  len = strlen(article);
   api_posix_ssize_t  rv3;
   size_t  i = 0;
   int  status = -1;
   char*  buf = NULL;
   size_t  blen = 0;
   size_t  bi = 0;
   char*  p;

   if(API_POSIX_SIZE_MAX != len && strlen(filter_pathname))
   {
      res = NULL;
      if(main_debug)
      {
         printf("%s: %sArticle post processor pathname: \x22%s\x22\n",
                CFG_NAME, MAIN_ERR_PREFIX, filter_pathname);
      }
      rv = fu_check_file(filter_pathname, &state);
      if(rv)
      {
         PRINT_ERROR("Article post processor not found");
      }
      else
      {
         if(API_POSIX_S_ISDIR(state.st_mode))
         {
            PRINT_ERROR("Directory specified for article postprocessor");
         }
         else
         {
            rv = api_posix_pipe(fd_in);
            if(!rv)
            {
               rv = api_posix_pipe(fd_out);
               if(!rv)
               {
                  /* Spawn child process for filter */
                  pid = api_posix_fork();
                  if(!pid)
                  {
                     /* Executed by child process */
                     api_posix_close(fd_out[1]);
                     api_posix_close(fd_in[0]);
                     rv = api_posix_dup2(fd_out[0], API_POSIX_STDIN_FILENO);
                     if(-1 != rv)
                     {
                        rv = api_posix_dup2(fd_in[1], API_POSIX_STDOUT_FILENO);
                        if(-1 != rv)
                        {
                           rv = sighandler_exec_prepare();
                           if(!rv)
                           {
                              api_posix_execl(filter_pathname, filter_pathname,
                                              (char*) NULL);
                           }
                        }
                     }
                     PRINT_ERROR("Executing article post processor failed");
                     exit(API_POSIX_EXIT_FAILURE);
                  }
                  else if(-1 != pid)
                  {
                     api_posix_close(fd_out[0]);
                     api_posix_close(fd_in[1]);
                     /* Set pipes to nonblocking mode */
                     rv = api_posix_fcntl(fd_out[1], API_POSIX_F_SETFL,
                                          API_POSIX_O_NONBLOCK);
                     if(-1 != rv)
                     {
                        rv = api_posix_fcntl(fd_in[0], API_POSIX_F_SETFL,
                                             API_POSIX_O_NONBLOCK);
                     }
                     if(-1 == rv)
                     {
                        PRINT_ERROR("Configuration of pipes failed");
                        error = 1;
                     }
                     else
                     {
                        while(!finished && !error)
                        {
                           /* Push article into postprocessor */
                           if(i < len)
                           {
                              rv3 = api_posix_write(fd_out[1],
                                                    (void*) &article[i],
                                                    len - i);
                              if(0 > rv3)
                              {
                                 if(API_POSIX_EINTR == api_posix_errno)
                                 {
                                    continue;
                                 }
                                 if(API_POSIX_EAGAIN != api_posix_errno)
                                 {
                                    error = 1;
                                    break;
                                 }
                              }
                              else  { i += (size_t) rv3; }
                           }
                           else
                           {
                              /* Transfer to stdin of postprocessor complete */
                              if(!pushed)
                              {
                                 pushed = 1;
                                 api_posix_close(fd_out[1]);
                              }
                           }
                           /* Read result */
                           while(!finished)
                           {
                              if((size_t) 4096 > blen - bi)
                              {
                                 /* Allocate more memory for result */
                                 if(!blen)  { blen = 4096; }
                                 else  { blen *= 2; }
                                 p = (char*) api_posix_realloc(buf, blen);
                                 if(NULL == p)  { error = 1;  break; }
                                 else  { buf = p; }
                              }
                              /* Leave one byte left for NUL termination */
                              rv3 = api_posix_read(fd_in[0], (void*) &buf[bi],
                                                   (size_t) 4095);
                              if(0 > rv3)
                              {
                                 if(API_POSIX_EINTR == api_posix_errno)
                                 {
                                    continue;
                                 }
                                 if(API_POSIX_EAGAIN != api_posix_errno)
                                 {
                                    error = 1;
                                 }
                                 break;
                              }
                              else if(0 < rv3)  { bi += (size_t) rv3; }
                              /* Check whether operation is complete */
                              else
                              {
                                 if(!pushed)
                                 {
                                    PRINT_ERROR("Postprocessor closed "
                                                "stdout before reading data");
                                 }
                                 else
                                 {
                                    /* Yes => Terminate result string */
                                    buf[bi] = 0;
                                    finished = 1;
                                 }
                              }
                           }
                        }
                     }
                     /* Terminate postprocessor child process after error */
                     if(error)
                     {
                        rv = api_posix_kill(pid, API_POSIX_SIGTERM);
                        if(rv)
                        {
                           PRINT_ERROR("Termination of child process "
                                       "failed (bug)");
                        }
                     }
                     /* Get exit status of filter */
                     do  { rv2 = api_posix_waitpid(pid, &status, 0); }
                     while((api_posix_pid_t) -1 == rv2
                           && API_POSIX_EINTR == api_posix_errno);
                     if(rv2 != pid)
                     {
                        PRINT_ERROR("Child process not found (bug)");
                     }
                     else
                     {
                        if(error || status)
                        {
                           PRINT_ERROR("Article post processor failed");
                        }
                        else
                        {
                           /* Success */
                           res = buf;
                        }
                     }
                     if(!pushed)  { api_posix_close(fd_out[1]); }
                     api_posix_close(fd_in[0]);
                  }
               }
            }
         }
      }
   }

   /* Check for error */
   if(NULL == res)  { api_posix_free((void*) buf); }

   return(res);
}


/* ========================================================================== */
/*! \brief Start external inews for article injection
 *
 * \param[in] article  Article to inject in canonical form
 *
 * \return
 * - Zero on success
 * - -1 on error
 */

int  ext_inews(const char*  article)
{
   int  res = -1;
   const char*  inews_pathname = config[CONF_INEWS].val.s;
   size_t  len = 0;
   api_posix_struct_stat  state;
   int  rv;
   int  fd_out[2];
   api_posix_pid_t  pid;
   api_posix_pid_t  rv2;
   int  error = 0;
   int  pushed = 0;
   api_posix_ssize_t  rv3;
   size_t  i = 0;
   int  status = -1;

   if(NULL != article)
   {
      len = strlen(article);
      if(len && strlen(inews_pathname))
      {
         if(main_debug)
         {
            printf("%s: %sDelegation to external inews: \x22%s\x22\n",
                   CFG_NAME, MAIN_ERR_PREFIX, inews_pathname);
         }
         rv = fu_check_file(inews_pathname, &state);
         if(rv)
         {
            PRINT_ERROR("External inews not found");
         }
         else
         {
            if(API_POSIX_S_ISDIR(state.st_mode))
            {
               PRINT_ERROR("Directory specified for external inews");
            }
            else
            {
               /* Looks good */
               res = 0;
            }
         }
      }
   }

   if(!res)
   {
      res = -1;
      rv = api_posix_pipe(fd_out);
      if(!rv)
      {
         /* Spawn child process for inews */
         pid = api_posix_fork();
         if(!pid)
         {
            /* Executed by child process */
            api_posix_close(fd_out[1]);
            rv = api_posix_dup2(fd_out[0], API_POSIX_STDIN_FILENO);
            if(-1 != rv)
            {
               rv = sighandler_exec_prepare();
               if(!rv)
               {
                  api_posix_execl(inews_pathname, inews_pathname, (char*) NULL);
               }
            }
            PRINT_ERROR("Executing external inews failed");
            exit(API_POSIX_EXIT_FAILURE);
         }
         else if(-1 != pid)
         {
            api_posix_close(fd_out[0]);
            /* Set pipe to nonblocking mode */
            rv = api_posix_fcntl(fd_out[1], API_POSIX_F_SETFL,
                                 API_POSIX_O_NONBLOCK);
            if(-1 == rv)
            {
               PRINT_ERROR("Configuration of pipe failed");
               error = 1;
            }
            else
            {
               while(!pushed && !error)
               {
                  /* Push article into inews */
                  if(i < len)
                  {
                     rv3 = api_posix_write(fd_out[1], (void*) &article[i],
                                           len - i);
                     if(0 > rv3)
                     {
                        if(API_POSIX_EINTR == api_posix_errno)  { continue; }
                        if(API_POSIX_EAGAIN != api_posix_errno)
                        {
                           error = 1;
                           break;
                        }
                     }
                     else  { i += (size_t) rv3; }
                  }
                  else
                  {
                     /* Transfer to stdin of inews complete */
                     if(!pushed)
                     {
                        api_posix_close(fd_out[1]);
                        pushed = 1;
                     }
                  }
               }
            }
            /* Terminate inews child process after error */
            if(error)
            {
               rv = api_posix_kill(pid, API_POSIX_SIGTERM);
               if(rv)
               {
                  PRINT_ERROR("Termination of child process failed (bug)");
               }
            }
            /* Get exit status of filter */
            do  { rv2 = api_posix_waitpid(pid, &status, 0); }
            while((api_posix_pid_t) -1 == rv2
                   && API_POSIX_EINTR == api_posix_errno);
            if(rv2 != pid)
            {
               PRINT_ERROR("Child process not found (bug)");
            }
            else
            {
               if(error || status)
               {
                  PRINT_ERROR("External inews failed");
               }
               else
               {
                  /* Success */
                  printf("%s: %sExternal inews reported successful injection\n",
                         CFG_NAME, MAIN_ERR_PREFIX);
                  res = 0;
               }
            }
            if(!pushed)  { api_posix_close(fd_out[1]); }
            api_posix_close(fd_out[0]);
         }
      }
   }

   return(res);
}


/* ========================================================================== */
/*! \brief Download file from external source
 *
 * \param[in] lpn  Local pathname where the file should be stored
 * \param[in] uri  URI of file to download
 *
 * \note
 * The caller is responsible that write permission to \e lpn is granted.
 *
 * \return
 * - Zero on success (file is now stored at \e lpn )
 * - -1 on unspecified error
 * - -2 if URI scheme is not supported
 * - -3 if authority of URI is not reachable
 * - -4 if requested file not available via path of URI
 */

int  ext_download_file(const char*  lpn, const char*  uri)
{
   /*
    * Note:
    * Currently the request is always delegated to the simple internal WWW
    * client and all URI schemes that are not "http" are rejected. URIs without
    * an authority field are rejected too.
    *
    * External download tools (like wget) can be hooked in here in the future.
    */

   int  res = 0;
   const char  scheme_http[] = "http";
   size_t  len;
   size_t  i = 0;

   /* Check (case insensitive) URI scheme, separator and double slash */
   len = strlen(scheme_http);
   while(i < len)
   {
      if(!uri[i])  { res = -2;  break; }
      if((int) scheme_http[i] != tolower((int) uri[i]))  { res = -2;  break; }
      ++i;
   }
   if(!res)  { if(':' != uri[i++])  { res = -2; } }
   if(-2 == res)
   {
      PRINT_ERROR("URI scheme for file download not supported");
   }
   if(!res)  { if('/' != uri[i++])  { res = -1; } }
   if(!res)  { if('/' != uri[i++])  { res = -1; } }

   /* Check URI encoding */
   if(!res)
   {
      if(enc_ascii_check_printable(uri))  { res = -1; }
      else
      {
         /*
          * Note: Check for HT and SP is not done yet
          * Check for single quote to allow passing of URI as shell parameter
          * Ensure that the URI has no query and fragment parts
          */
         len = strlen(uri);
         if(strcspn(uri, "\x09\x20'?#") != len)  { res = -1; }
      }
   }
   if(-1 == res)
   {
      PRINT_ERROR("URI for file download has invalid format");
   }

   /* Delegate to internal WWW client */
   if(!res)
   {
      printf("%s: %sFile download delegation to %s for: %s\n",
             CFG_NAME, MAIN_ERR_PREFIX, lpn, uri);
      res = http_download_file(uri, lpn);
   }

   return(res);
}


/* ========================================================================== */
/*! \brief Free an object allocated by external program delegation module
 *
 * Use this function to release dynamic memory that was allocated by the
 * external program delegation module.
 *
 * \param[in] p  Pointer to object
 *
 * Release the memory for the object pointed to by \e p.
 *
 * \note
 * The pointer \e p is allowed to be \c NULL and no operation is performed in
 * this case.
 */

void  ext_free(void*  p)
{
   api_posix_free(p);
}


/*! @} */

/* EOF */
