/*
  This file is part of TALER
  Copyright (C) 2025 Taler Systems SA

  TALER is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
  Foundation; either version 3, or (at your option) any later version.

  TALER 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 General Public License for more details.

  You should have received a copy of the GNU General Public License along with
  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
*/
/**
 * @file mhd_typst.c
 * @brief MHD utility functions for PDF generation
 * @author Christian Grothoff
 *
 *
 */
#include "platform.h"  /* UNNECESSARY? */
#include "taler/taler_util.h"
#include "taler/taler_mhd_lib.h"
#include <microhttpd.h>


/**
 * Information about a specific typst invocation.
 */
struct TypstStage
{
  /**
   * Name of the FIFO for the typst output.
   */
  char *filename;

  /**
   * Typst context we are part of.
   */
  struct TALER_MHD_TypstContext *tc;

  /**
   * Handle to the typst process.
   */
  struct GNUNET_Process *proc;

  /**
   * Handle to be notified about stage completion.
   */
  struct GNUNET_ChildWaitHandle *cwh;

};


struct TALER_MHD_TypstContext
{

  /**
   * Project data to use for Typst.
   */
  const struct GNUNET_OS_ProjectData *pd;

  /**
   * Directory where we create temporary files (or FIFOs) for the IPC.
   */
  char *tmpdir;

  /**
   * Array of stages producing PDFs to be combined.
   */
  struct TypstStage *stages;

  /**
   * Handle for pdftk combining the various PDFs.
   */
  struct GNUNET_Process *proc;

  /**
   * Handle to wait for @e proc to complete.
   */
  struct GNUNET_ChildWaitHandle *cwh;

  /**
   * Callback to call on the final result.
   */
  TALER_MHD_TypstResultCallback cb;

  /**
   * Closure for @e cb
   */
  void *cb_cls;

  /**
   * Task for async work.
   */
  struct GNUNET_SCHEDULER_Task *t;

  /**
   * Name of the final file created by pdftk.
   */
  char *output_file;

  /**
   * Context for fail_async_cb().
   */
  char *async_hint;

  /**
   * Context for fail_async_cb().
   */
  enum TALER_ErrorCode async_ec;

  /**
   * Length of the @e stages array.
   */
  unsigned int num_stages;

  /**
   * Number of still active stages.
   */
  unsigned int active_stages;

  /**
   * Should the directory be removed when done?
   */
  bool remove_on_exit;
};


void
TALER_MHD_typst_cancel (struct TALER_MHD_TypstContext *tc)
{
  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Cleaning up TypstContext\n");
  if (NULL != tc->t)
  {
    GNUNET_SCHEDULER_cancel (tc->t);
    tc->t = NULL;
  }
  for (unsigned int i = 0; i<tc->num_stages; i++)
  {
    struct TypstStage *stage = &tc->stages[i];

    if (NULL != stage->cwh)
    {
      GNUNET_wait_child_cancel (stage->cwh);
      stage->cwh = NULL;
    }
    if (NULL != stage->proc)
    {
      GNUNET_break (GNUNET_OK ==
                    GNUNET_process_kill (stage->proc,
                                         SIGKILL));
      GNUNET_process_destroy (stage->proc);
      stage->proc = NULL;
    }
    GNUNET_free (stage->filename);
  }
  GNUNET_free (tc->stages);
  if (NULL != tc->cwh)
  {
    GNUNET_wait_child_cancel (tc->cwh);
    tc->cwh = NULL;
  }
  if (NULL != tc->proc)
  {
    GNUNET_break (GNUNET_OK ==
                  GNUNET_process_kill (tc->proc,
                                       SIGKILL));
    GNUNET_process_destroy (tc->proc);
  }
  GNUNET_free (tc->output_file);
  if (NULL != tc->tmpdir)
  {
    if (tc->remove_on_exit)
      GNUNET_DISK_directory_remove (tc->tmpdir);
    GNUNET_free (tc->tmpdir);
  }
  GNUNET_free (tc);
}


/**
 * Create file in @a tmpdir with one of the PDF inputs.
 *
 * @param[out] stage initialized stage data
 * @param tmpdir where to place temporary files
 * @param data input JSON with PDF data
 * @return true on success
 */
static bool
inline_pdf_stage (struct TypstStage *stage,
                  const char *tmpdir,
                  const json_t *data)
{
  const char *str = json_string_value (data);
  char *fn;
  size_t n;
  void *b;
  int fd;

  if (NULL == str)
  {
    GNUNET_break (0);
    return false;
  }
  b = NULL;
  n = GNUNET_STRINGS_base64_decode (str,
                                    strlen (str),
                                    &b);
  if (NULL == b)
  {
    GNUNET_break (0);
    return false;
  }
  GNUNET_asprintf (&fn,
                   "%s/external-",
                   tmpdir);
  stage->filename = GNUNET_DISK_mktemp (fn);
  if (NULL == stage->filename)
  {
    GNUNET_break (0);
    GNUNET_free (b);
    GNUNET_free (fn);
    return false;
  }
  GNUNET_free (fn);
  fd = open (stage->filename,
             O_WRONLY | O_TRUNC,
             S_IRUSR | S_IWUSR);
  if (-1 == fd)
  {
    GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
                              "open",
                              stage->filename);
    if (0 != unlink (stage->filename))
      GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
                                "unlink",
                                stage->filename);
    GNUNET_free (b);
    GNUNET_free (stage->filename);
    return false;
  }

  {
    size_t off = 0;

    while (off < n)
    {
      ssize_t r;

      r = write (fd,
                 b + off,
                 n - off);
      if (-1 == r)
      {
        GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
                                  "write",
                                  stage->filename);
        GNUNET_break (0 == close (fd));
        GNUNET_free (b);
        GNUNET_free (stage->filename);
        return false;
      }
      off += r;
    }
  }
  GNUNET_break (0 == close (fd));
  GNUNET_free (b);
  return true;
}


/**
 * Generate a response for @a tc indicating an error of type @a ec.
 *
 * @param[in,out] tc context to fail
 * @param ec error code to return
 * @param hint hint text to return
 */
static void
typst_context_fail (struct TALER_MHD_TypstContext *tc,
                    enum TALER_ErrorCode ec,
                    const char *hint)
{
  struct TALER_MHD_TypstResponse resp = {
    .ec = ec,
    .details.hint = hint
  };

  if (NULL != tc->cb)
  {
    tc->cb (tc->cb_cls,
            &resp);
    tc->cb = NULL;
  }
}


/**
 * Helper task for typst_context_fail_async().
 *
 * @param cls a `struct TALER_MHD_TypstContext`
 */
static void
fail_async_cb (void *cls)
{
  struct TALER_MHD_TypstContext *tc = cls;

  tc->t = NULL;
  typst_context_fail (tc,
                      tc->async_ec,
                      tc->async_hint);
  GNUNET_free (tc->async_hint);
  TALER_MHD_typst_cancel (tc);
}


/**
 * Generate a response for @a tc indicating an error of type @a ec.
 *
 * @param[in,out] tc context to fail
 * @param ec error code to return
 * @param hint hint text to return
 */
static void
typst_context_fail_async (struct TALER_MHD_TypstContext *tc,
                          enum TALER_ErrorCode ec,
                          const char *hint)
{
  tc->async_ec = ec;
  tc->async_hint = (NULL == hint) ? NULL : GNUNET_strdup (hint);
  tc->t = GNUNET_SCHEDULER_add_now (&fail_async_cb,
                                    tc);
}


/**
 * Called when the pdftk helper exited.
 *
 * @param cls our `struct TALER_MHD_TypstContext *`
 * @param type type of the process
 * @param exit_code status code of the process
 */
static void
pdftk_done_cb (void *cls,
               enum GNUNET_OS_ProcessStatusType type,
               long unsigned int exit_code)
{
  struct TALER_MHD_TypstContext *tc = cls;

  tc->cwh = NULL;
  GNUNET_process_destroy (tc->proc);
  tc->proc = NULL;
  switch (type)
  {
  case GNUNET_OS_PROCESS_UNKNOWN:
    GNUNET_assert (0);
    return;
  case GNUNET_OS_PROCESS_RUNNING:
    /* we should not get this notification */
    GNUNET_break (0);
    return;
  case GNUNET_OS_PROCESS_STOPPED:
    /* Someone is SIGSTOPing our helper!? */
    GNUNET_break (0);
    return;
  case GNUNET_OS_PROCESS_EXITED:
    if (0 != exit_code)
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "pdftk exited with status %d\n",
                  (int) exit_code);
      typst_context_fail (tc,
                          TALER_EC_EXCHANGE_GENERIC_PDFTK_FAILURE,
                          "pdftk failed");
    }
    else
    {
      struct TALER_MHD_TypstResponse resp = {
        .ec = TALER_EC_NONE,
        .details.filename = tc->output_file,
      };

      GNUNET_assert (NULL != tc->cb);
      tc->cb (tc->cb_cls,
              &resp);
      tc->cb = NULL;
    }
    break;
  case GNUNET_OS_PROCESS_SIGNALED:
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "pdftk died with signal %d\n",
                (int) exit_code);
    typst_context_fail (tc,
                        TALER_EC_EXCHANGE_GENERIC_PDFTK_CRASH,
                        "pdftk killed by signal");
    break;
  }
  TALER_MHD_typst_cancel (tc);
}


/**
 * Function called once all of the individual stages are done.
 * Triggers the pdftk run for @a tc.
 *
 * @param[in,out] cls a `struct TALER_MHD_TypstContext *` context to run pdftk for
 */
static void
complete_response (void *cls)
{
  struct TALER_MHD_TypstContext *tc = cls;
  const char *argv[tc->num_stages + 5];

  tc->t = NULL;
  argv[0] = "pdftk";
  for (unsigned int i = 0; i<tc->num_stages; i++)
    argv[i + 1] = tc->stages[i].filename;
  argv[tc->num_stages + 1] = "cat";
  argv[tc->num_stages + 2] = "output";
  argv[tc->num_stages + 3] = tc->output_file;
  argv[tc->num_stages + 4] = NULL;
  tc->proc = GNUNET_process_create (GNUNET_OS_INHERIT_STD_ERR);
  if (GNUNET_OK !=
      GNUNET_process_run_command_argv (tc->proc,
                                       argv[0],
                                       argv))
  {
    GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
                         "fork");
    GNUNET_process_destroy (tc->proc);
    tc->proc = NULL;
    TALER_MHD_typst_cancel (tc);
    return;
  }
  tc->cwh = GNUNET_wait_child (tc->proc,
                               &pdftk_done_cb,
                               tc);
  GNUNET_assert (NULL != tc->cwh);
}


/**
 * Cancel typst. Wrapper task to do so asynchronously.
 *
 * @param[in] cls a `struct TALER_MHD_TypstContext`
 */
static void
cancel_async (void *cls)
{
  struct TALER_MHD_TypstContext *tc = cls;

  tc->t = NULL;
  TALER_MHD_typst_cancel (tc);
}


/**
 * Called when a typst helper exited.
 *
 * @param cls our `struct TypstStage *`
 * @param type type of the process
 * @param exit_code status code of the process
 */
static void
typst_done_cb (void *cls,
               enum GNUNET_OS_ProcessStatusType type,
               long unsigned int exit_code)
{
  struct TypstStage *stage = cls;
  struct TALER_MHD_TypstContext *tc = stage->tc;

  stage->cwh = NULL;
  GNUNET_process_destroy (stage->proc);
  stage->proc = NULL;
  switch (type)
  {
  case GNUNET_OS_PROCESS_UNKNOWN:
    GNUNET_assert (0);
    return;
  case GNUNET_OS_PROCESS_RUNNING:
    /* we should not get this notification */
    GNUNET_break (0);
    return;
  case GNUNET_OS_PROCESS_STOPPED:
    /* Someone is SIGSTOPing our helper!? */
    GNUNET_break (0);
    return;
  case GNUNET_OS_PROCESS_EXITED:
    if (0 != exit_code)
    {
      char err[128];

      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "typst exited with status %d\n",
                  (int) exit_code);
      GNUNET_snprintf (err,
                       sizeof (err),
                       "Typst exited with status %d",
                       (int) exit_code);
      typst_context_fail (tc,
                          TALER_EC_EXCHANGE_GENERIC_TYPST_TEMPLATE_FAILURE,
                          err);
      GNUNET_assert (NULL == tc->t);
      tc->t = GNUNET_SCHEDULER_add_now (&cancel_async,
                                        tc);
      return;
    }
    break;
  case GNUNET_OS_PROCESS_SIGNALED:
    {
      char err[128];

      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "typst died with signal %d\n",
                  (int) exit_code);
      GNUNET_snprintf (err,
                       sizeof (err),
                       "Typst died with signal %d",
                       (int) exit_code);
      typst_context_fail (tc,
                          TALER_EC_EXCHANGE_GENERIC_TYPST_CRASH,
                          err);
      GNUNET_assert (NULL == tc->t);
      tc->t = GNUNET_SCHEDULER_add_now (&cancel_async,
                                        tc);
      return;
    }
    break;
  }
  tc->active_stages--;
  if (NULL != stage->proc)
  {
    GNUNET_process_destroy (stage->proc);
    stage->proc = NULL;
  }
  if (0 != tc->active_stages)
    return;
  GNUNET_assert (NULL == tc->t);
  tc->t = GNUNET_SCHEDULER_add_now (&complete_response,
                                    tc);
}


/**
 * Check that @a id only consists of characters that are safe to
 * interpolate into a Typst `#import` statement (alphanumerics and
 * `.`, `-`, `_`).  This prevents code/path injection via the form
 * name or version.
 *
 * @param id identifier to validate
 * @return true if @a id is non-empty and only uses the safe charset
 */
static bool
is_valid_form_identifier (const char *id)
{
  if ( (NULL == id) ||
       ('\0' == id[0]) )
    return false;
  for (const char *p = id; '\0' != *p; p++)
  {
    char c = *p;

    if (! ( ( ('a' <= c) && ('z' >= c)) ||
            ( ('A' <= c) && ('Z' >= c)) ||
            ( ('0' <= c) && ('9' >= c)) ||
            ('.' == c) ||
            ('-' == c) ||
            ('_' == c) ) )
      return false;
  }
  return true;
}


/**
 * Setup typst stage to produce one of the PDF inputs.
 *
 * @param[out] stage initialized stage data
 * @param i index of the stage
 * @param tmpdir where to place temporary files
 * @param template_path where to find templates
 * @param doc input document specification
 * @return true on success
 */
static bool
setup_stage (struct TypstStage *stage,
             unsigned int i,
             const char *tmpdir,
             const char *template_path,
             const struct TALER_MHD_TypstDocument *doc)
{
  struct TALER_MHD_TypstContext *tc = stage->tc;
  char *input;
  bool is_dot_typ;

  if (NULL == doc->form_name)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Stage %u: Dumping inlined PDF attachment\n",
                i);
    return inline_pdf_stage (stage,
                             tmpdir,
                             doc->data);
  }

  if (! is_valid_form_identifier (doc->form_name))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Stage %u: Invalid form name `%s'\n",
                i,
                doc->form_name);
    return false;
  }
  if ( (NULL != doc->form_version) &&
       (! is_valid_form_identifier (doc->form_version)) )
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Stage %u: Invalid form version `%s'\n",
                i,
                doc->form_version);
    return false;
  }

  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Stage %u: Handling form %s\n",
              i,
              doc->form_name);

  /* Setup inputs */
  {
    char *dirname;

    GNUNET_asprintf (&dirname,
                     "%s/%u/",
                     tmpdir,
                     i);
    if (GNUNET_OK !=
        GNUNET_DISK_directory_create (dirname))
    {
      GNUNET_free (dirname);
      return false;
    }
    GNUNET_free (dirname);
  }

  /* Setup data input */
  {
    char *jfn;

    GNUNET_asprintf (&jfn,
                     "%s/%u/input.json",
                     tmpdir,
                     i);
    if (0 !=
        json_dump_file (doc->data,
                        jfn,
                        JSON_INDENT (2)))
    {
      GNUNET_break (0);
      GNUNET_free (jfn);
      return false;
    }
    GNUNET_free (jfn);
  }

  /* setup output file name */
  GNUNET_asprintf (&stage->filename,
                   "%s/%u/input.pdf",
                   tmpdir,
                   i);

  /* setup main input Typst file */
  {
    char *intyp;
    size_t slen = strlen (doc->form_name);

    is_dot_typ = ( (slen > 4) &&
                   (0 == memcmp (&doc->form_name[slen - 4],
                                 ".typ",
                                 4)) );
    /* We do not append the ":$VERSION" if a filename ending with ".typ"
       is given. Otherwise we append the version, or ":0.0.0" if no
       explicit version is given. */
    GNUNET_asprintf (&intyp,
                     "#import \"%s/%s%s%s\": form\n"
                     "#form(json(\"input.json\"))\n",
                     template_path,
                     doc->form_name,
                     is_dot_typ ? "" : ":",
                     is_dot_typ
                     ? ""
                     : ( (NULL == doc->form_version)
                         ? "0.0.0"
                         : doc->form_version));
    GNUNET_asprintf (&input,
                     "%s/%u/input.typ",
                     tmpdir,
                     i);
    if (GNUNET_OK !=
        GNUNET_DISK_fn_write (input,
                              intyp,
                              strlen (intyp),
                              GNUNET_DISK_PERM_USER_READ))
    {
      GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
                                "write",
                                input);
      GNUNET_free (input);
      GNUNET_free (intyp);
      return false;
    }
    GNUNET_free (intyp);
  }

  /* now setup typst invocation */
  {
    const char *argv[6];

    if (is_dot_typ)
    {
      /* This deliberately breaks the typst sandbox. Why? Because Typst sucks
         and does not support multiple roots, but here we have dynamic data in
         /tmp and a style file outside of /tmp (and copying is also not
         practical as we don't know what all to copy). Typst should really
         support multiple roots. Anyway, in production this path should not
         happen, because there we use Typst packages. */
      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                  "Bypassing Typst sandbox. You should use Typst packages instead of `%s'.\n",
                  doc->form_name);
      argv[0] = "typst";
      argv[1] = "compile";
      argv[2] = "--root";
      argv[3] = "/";
      argv[4] = input;
      argv[5] = NULL;
    }
    else
    {
      argv[0] = "typst";
      argv[1] = "compile";
      argv[2] = input;
      argv[3] = NULL;
    }
    stage->proc = GNUNET_process_create (GNUNET_OS_INHERIT_STD_ERR);
    {
      char *datadir;

      datadir = GNUNET_OS_installation_get_path (
        tc->pd,
        GNUNET_OS_IPK_DATADIR);
      GNUNET_assert (GNUNET_OK ==
                     GNUNET_process_set_options (
                       stage->proc,
                       GNUNET_process_option_set_environment ("XDG_DATA_HOME",
                                                              datadir)));
      GNUNET_free (datadir);
    }
    if (GNUNET_OK !=
        GNUNET_process_run_command_argv (stage->proc,
                                         "typst",
                                         argv))
    {
      GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
                           "fork");
      GNUNET_free (input);
      GNUNET_process_destroy (stage->proc);
      stage->proc = NULL;
      return false;
    }
    GNUNET_free (input);
    tc->active_stages++;
    stage->cwh = GNUNET_wait_child (stage->proc,
                                    &typst_done_cb,
                                    stage);
    GNUNET_assert (NULL != stage->cwh);
  }
  return true;
}


struct TALER_MHD_TypstContext *
TALER_MHD_typst (
  const struct GNUNET_OS_ProjectData *pd,
  const struct GNUNET_CONFIGURATION_Handle *cfg,
  bool remove_on_exit,
  const char *cfg_section_name,
  unsigned int num_documents,
  const struct TALER_MHD_TypstDocument docs[static num_documents],
  TALER_MHD_TypstResultCallback cb,
  void *cb_cls)
{
  static enum GNUNET_GenericReturnValue once = GNUNET_NO;
  struct TALER_MHD_TypstContext *tc;

  switch (once)
  {
  case GNUNET_OK:
    break;
  case GNUNET_NO:
    if (GNUNET_SYSERR ==
        GNUNET_OS_check_helper_binary ("typst",
                                       false,
                                       NULL))
    {
      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                  "`typst' command not found\n");
      once = GNUNET_SYSERR;
      return NULL;
    }
    if (GNUNET_SYSERR ==
        GNUNET_OS_check_helper_binary ("pdftk",
                                       false,
                                       NULL))
    {
      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                  "`pdftk' command not found\n");
      once = GNUNET_SYSERR;
      return NULL;
    }
    once = GNUNET_OK;
    break;
  case GNUNET_SYSERR:
    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                "PDF generation initialization failed before, not even trying again\n");
    return NULL;
  }
  tc = GNUNET_new (struct TALER_MHD_TypstContext);
  tc->pd = pd;
  tc->tmpdir = GNUNET_strdup ("/tmp/taler-typst-XXXXXX");
  tc->remove_on_exit = remove_on_exit;
  tc->cb = cb;
  tc->cb_cls = cb_cls;
  if (NULL == mkdtemp (tc->tmpdir))
  {
    GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
                              "mkdtemp",
                              tc->tmpdir);
    GNUNET_free (tc->tmpdir);
    TALER_MHD_typst_cancel (tc);
    return NULL;
  }
  GNUNET_asprintf (&tc->output_file,
                   "%s/final.pdf",
                   tc->tmpdir);

  /* setup typst stages */
  {
    char *template_path;

    if (GNUNET_OK !=
        GNUNET_CONFIGURATION_get_value_string (cfg,
                                               cfg_section_name,
                                               "TYPST_TEMPLATES",
                                               &template_path))
    {
      GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
                                 cfg_section_name,
                                 "TYPST_TEMPLATES");
      TALER_MHD_typst_cancel (tc);
      return NULL;
    }
    if ('@' != template_path[0])
      template_path = GNUNET_CONFIGURATION_expand_dollar (cfg,
                                                          template_path);
    tc->stages = GNUNET_new_array (num_documents,
                                   struct TypstStage);
    tc->num_stages = num_documents;
    for (unsigned int i = 0; i<num_documents; i++)
    {
      tc->stages[i].tc = tc;
      if (! setup_stage (&tc->stages[i],
                         i,
                         tc->tmpdir,
                         template_path,
                         &docs[i]))
      {
        char err[128];

        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                    "Typst setup failed on stage %u\n",
                    i);
        GNUNET_snprintf (err,
                         sizeof (err),
                         "Typst setup failed on stage %u",
                         i);
        typst_context_fail_async (tc,
                                  TALER_EC_EXCHANGE_GENERIC_TYPST_TEMPLATE_FAILURE,
                                  err);
        GNUNET_free (template_path);
        return tc;
      }
    }
    GNUNET_free (template_path);
  }
  if (0 == tc->active_stages)
  {
    tc->t = GNUNET_SCHEDULER_add_now (&complete_response,
                                      tc);
  }
  return tc;
}


struct MHD_Response *
TALER_MHD_response_from_pdf_file (const char *filename)
{
  struct MHD_Response *resp;
  struct stat s;
  int fd;

  fd = open (filename,
             O_RDONLY);
  if (-1 == fd)
  {
    GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
                              "open",
                              filename);
    return NULL;
  }
  if (0 !=
      fstat (fd,
             &s))
  {
    GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
                              "fstat",
                              filename);
    GNUNET_assert (0 == close (fd));
    return NULL;
  }
  resp = MHD_create_response_from_fd (s.st_size,
                                      fd);
  if (NULL == resp)
  {
    GNUNET_break (0);
    GNUNET_assert (0 == close (fd));
    return NULL;
  }
  TALER_MHD_add_global_headers (resp,
                                false);
  GNUNET_break (MHD_YES ==
                MHD_add_response_header (resp,
                                         MHD_HTTP_HEADER_CONTENT_TYPE,
                                         "application/pdf"));
  return resp;
}
