/*
 * libEtPan! -- a mail library
 *
 * Copyright (C) 2001, 2005 - DINH Viet Hoa
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the libEtPan! project nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/*
 * $Id: mailprivacy_tools.c,v 1.18 2011/01/06 00:09:52 hoa Exp $
 */

#ifdef HAVE_CONFIG_H
#	include <config.h>
#endif

#if __APPLE__
#include <TargetConditionals.h>
#endif

#include "mailprivacy_tools.h"
#include "mailprivacy_tools_private.h"

#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#ifdef WIN32
#	include "win_etpan.h"
#else
#	include <libgen.h>
#	include <sys/mman.h>
#	include <sys/wait.h>
#endif
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <libetpan/mailmessage.h>
#include <ctype.h>
#include "mailprivacy.h"
#include <libetpan/libetpan-config.h>
#include <libetpan/data_message_driver.h>

void mailprivacy_mime_clear(struct mailmime * mime)
{
  struct mailmime_data * data;
  clistiter * cur;
  
  switch (mime->mm_type) {
  case MAILMIME_SINGLE:
    data = mime->mm_data.mm_single;
    if (data != NULL) {
      if (data->dt_type == MAILMIME_DATA_FILE)
        unlink(data->dt_data.dt_filename);
    }
    break;
    
  case MAILMIME_MULTIPLE:
    data = mime->mm_data.mm_multipart.mm_preamble;
    if (data != NULL) {
      if (data->dt_type == MAILMIME_DATA_FILE)
        unlink(data->dt_data.dt_filename);
    }
    data = mime->mm_data.mm_multipart.mm_epilogue;
    if (data != NULL) {
      if (data->dt_type == MAILMIME_DATA_FILE)
        unlink(data->dt_data.dt_filename);
    }
    
    for(cur = clist_begin(mime->mm_data.mm_multipart.mm_mp_list) ;
        cur != NULL ; cur = clist_next(cur)) {
      struct mailmime * submime;
      
      submime = clist_content(cur);
      
      mailprivacy_mime_clear(submime);
    }
    break;
    
  case MAILMIME_MESSAGE:
    if (mime->mm_data.mm_message.mm_msg_mime != NULL) {
      mailprivacy_mime_clear(mime->mm_data.mm_message.mm_msg_mime);
    }
    break;
  }
}


static FILE * get_tmp_file(char * filename)
{
  int fd;
  mode_t old_mask;
  FILE * f;
  
  old_mask = umask(0077);
  fd = mkstemp(filename);
  umask(old_mask);
  if (fd == -1)
    return NULL;
  
  f = fdopen(fd, "r+");
  if (f == NULL) {
    close(fd);
    unlink(filename);
  }
  
  return f;
}

FILE * mailprivacy_get_tmp_file(struct mailprivacy * privacy,
    char * filename, size_t size)
{
  snprintf(filename, size, "%s/libetpan-privacy-XXXXXX", privacy->tmp_dir);
  return get_tmp_file(filename);
}

int mailprivacy_get_tmp_filename(struct mailprivacy * privacy,
    char * filename, size_t size)
{
  FILE * f;
  
  f = mailprivacy_get_tmp_file(privacy, filename, size);
  if (f == NULL)
    return MAIL_ERROR_FILE;
  
  fclose(f);
  
  return MAIL_NO_ERROR;
}

static char * dup_file(struct mailprivacy * privacy,
    char * source_filename)
{
  char filename[PATH_MAX];
  FILE * dest_f;
  int r;
  struct stat stat_info;
  char * dest_filename;
  char * mapping;
  size_t written;
  int fd;
  
  dest_f = mailprivacy_get_tmp_file(privacy, filename, sizeof(filename));
  if (dest_f == NULL)
    goto err;
  
  dest_filename = strdup(filename);
  if (dest_filename == NULL)
    goto close_dest;
  
  fd = open(source_filename, O_RDONLY);
  if (fd < 0)
    goto free_dest;
  
  r = fstat(fd, &stat_info);
  if (r < 0)
    goto close_src;
  
  mapping = mmap(NULL, stat_info.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
  if (mapping == (char *)MAP_FAILED)
    goto close_src;
  
  written = fwrite(mapping, 1, stat_info.st_size, dest_f);
  if (written != (size_t) stat_info.st_size)
    goto unmap;
  
  munmap(mapping, stat_info.st_size);
  close(fd);
  fclose(dest_f);
  
  return dest_filename;
  
 unmap:
  munmap(mapping, stat_info.st_size);
 close_src:
  close(fd);
 free_dest:
  free(dest_filename);
 close_dest:
  fclose(dest_f);
 err:
  return NULL;
}


/*
  mime_data_replace()
  
  write a mime part to a file and change the reference of mailmime_data
  to the file.
*/

static int mime_data_replace(struct mailprivacy * privacy,
    int encoding_type,
    struct mailmime_data * data,
    int reencode)
{
  char filename[PATH_MAX];
  FILE * f;
  size_t written;
  char * dup_filename;
  int res;
  int r;
  int decoded;

  if (data->dt_type != MAILMIME_DATA_TEXT) {
    res = MAIL_NO_ERROR;
    goto err;
  }
  
  f = mailprivacy_get_tmp_file(privacy, filename, sizeof(filename));
  if (f == NULL) {
    res = MAIL_ERROR_FILE;
    goto err;
  }
  
  decoded = 0;
  if (reencode) {
    if (encoding_type != -1) {
      char * content;
      size_t content_len;
      size_t cur_token;
    
      cur_token = 0;
      r = mailmime_part_parse(data->dt_data.dt_text.dt_data,
          data->dt_data.dt_text.dt_length,
          &cur_token, encoding_type, &content, &content_len);
    
      if (r == MAILIMF_NO_ERROR) {
        /* write decoded */
        written = fwrite(content, 1, content_len, f);
        if (written != content_len) {
          fclose(f);
          unlink(filename);
          res = MAIL_ERROR_FILE;
          goto err;
        }
        mmap_string_unref(content);
      
        decoded = 1;
        data->dt_encoded = 0;
      }
    }
  }
  
  if (!decoded) {
    written = fwrite(data->dt_data.dt_text.dt_data, 1,
        data->dt_data.dt_text.dt_length, f);
    if (written != data->dt_data.dt_text.dt_length) {
      fclose(f);
      unlink(filename);
      res = MAIL_ERROR_FILE;
      goto err;
    }
  }
  
  fclose(f);
  
  dup_filename = strdup(filename);
  if (dup_filename == NULL) {
    unlink(filename);
    res = MAIL_ERROR_MEMORY;
    goto err;
  }
  
  data->dt_type = MAILMIME_DATA_FILE;
  data->dt_data.dt_filename = dup_filename;
  
  return MAIL_NO_ERROR;
  
 err:
  return res;
}


/*
  recursive_replace_single_parts()
  
  write all parts of the given mime part to file.
*/

static int recursive_replace_single_parts(struct mailprivacy * privacy,
    struct mailmime * mime, int reencode)
{
  int r;
  int res;
  clistiter * cur;
  
  mime->mm_mime_start = NULL;
  
  switch(mime->mm_type) {
  case MAILMIME_SINGLE:
    if (mime->mm_data.mm_single != NULL) {
      int encoding_type;
      struct mailmime_single_fields single_fields;
      
      mailmime_single_fields_init(&single_fields, mime->mm_mime_fields,
          mime->mm_content_type);
      
      if (single_fields.fld_encoding != NULL)
        encoding_type = single_fields.fld_encoding->enc_type;
      else
        encoding_type = -1;
      
      r = mime_data_replace(privacy, encoding_type,
          mime->mm_data.mm_single, reencode);
      if (r != MAIL_NO_ERROR) {
        res = r;
        goto err;
      }
    }
    break;
    
  case MAILMIME_MULTIPLE:
    if (mime->mm_data.mm_multipart.mm_preamble != NULL) {
      r = mime_data_replace(privacy, -1,
          mime->mm_data.mm_multipart.mm_preamble, reencode);
      if (r != MAIL_NO_ERROR) {
        res = r;
        goto err;
      }
    }
    
    if (mime->mm_data.mm_multipart.mm_epilogue != NULL) {
      r = mime_data_replace(privacy, -1,
          mime->mm_data.mm_multipart.mm_epilogue, reencode);
      if (r != MAIL_NO_ERROR) {
        res = r;
        goto err;
      }
    }
    
    for(cur = clist_begin(mime->mm_data.mm_multipart.mm_mp_list) ;
        cur != NULL ; cur = clist_next(cur)) {
      struct mailmime * child;
      
      child = clist_content(cur);
      
      r = recursive_replace_single_parts(privacy, child, reencode);
      if (r != MAIL_NO_ERROR) {
        res = r;
        goto err;
      }
    }
    
    break;
    
  case MAILMIME_MESSAGE:
    if (mime->mm_data.mm_message.mm_msg_mime != NULL) {
      r = recursive_replace_single_parts(privacy,
          mime->mm_data.mm_message.mm_msg_mime, reencode);
      if (r != MAIL_NO_ERROR) {
        res = r;
        goto err;
      }
    }
    break;
  }
  
  return MAIL_NO_ERROR;
  
 err:
  return res;
}

/*
  mailprivacy_get_mime()
  
  parse the message in MIME structure,
  all single MIME parts are stored in files.
  
  privacy can be set to NULL to disable privacy check.
*/

int mailprivacy_get_mime(struct mailprivacy * privacy,
    int check_privacy, int reencode,
    char * content, size_t content_len,
    struct mailmime ** result_mime)
{
  struct mailmime * mime;
  mailmessage * msg;
  int r;
  int res;

#if 0
  int check_privacy;
  
  check_privacy = (privacy != NULL);
#endif
  
  /*
    use message data driver, get bodystructure and
    convert all the data part in MAILMIME_SINGLE to files.
  */
  
  msg = data_message_init(content, content_len);
  if (msg == NULL) {
    res = MAIL_ERROR_MEMORY;
    goto err;
  }
  
#if 0
  if (msg->mime == NULL) {
    if (check_privacy) {
      r = mailprivacy_msg_get_bodystructure(privacy, msg, &mime);
    }
    else {
      /*
        don't use etpan_msg_get_bodystructure because it is not useful
        and to avoid loops due to security part
      */
      r = mailmessage_get_bodystructure(msg, &mime);
    }
  }
  else {
    mime = msg->mime;
  }
#endif

  if (check_privacy)
    r = mailprivacy_msg_get_bodystructure(privacy, msg, &mime);
  else
    r = mailmessage_get_bodystructure(msg, &mime);
  if (r != MAIL_NO_ERROR) {
    res = r;
    goto free_msg;
  }

  /*
    should be done so that the MIME structure need not to be unregistered.
  */
  mailprivacy_recursive_unregister_mime(privacy, mime);

  r = recursive_replace_single_parts(privacy, mime, reencode);
  if (r != MAIL_NO_ERROR) {
    res = r;
    goto clear_mime;
  }
  
  data_message_detach_mime(msg);
#if 0
  if (check_privacy)
    mailprivacy_msg_flush(privacy, msg);
  else
    mailmessage_flush(msg);
#endif
  mailprivacy_msg_flush(privacy, msg);
  mailmessage_free(msg);
  
  * result_mime = mime;
  
  return MAIL_NO_ERROR;
  
 clear_mime:
  mailprivacy_mime_clear(mime);
  mailprivacy_msg_flush(privacy, msg);
 free_msg:
  mailmessage_free(msg);
 err:
  return res;
}

#ifndef LIBETPAN_SYSTEM_BASENAME
static char * libetpan_basename(char * filename)
{
  char * next;
  char * p;
  
  p = filename;
  next = strchr(p, '/');
  
  while (next != NULL) {
    p = next;
    next = strchr(p + 1, '/');
  }
  
  if (p == filename)
    return filename;
  else
    return p + 1;
}
#else
#define libetpan_basename(a) basename(a)
#endif

struct mailmime *
mailprivacy_new_file_part(struct mailprivacy * privacy,
    char * filename,
    char * default_content_type, int default_encoding)
{
  char basename_buf[PATH_MAX];
  struct mailmime_mechanism * encoding;
  struct mailmime_content * content;
  struct mailmime * mime;
  int r;
  char * dup_filename;
  struct mailmime_fields * mime_fields;
  int encoding_type;
  char * content_type_str;
  int do_encoding;
  
  if (filename != NULL) {
    strncpy(basename_buf, filename, PATH_MAX);
    libetpan_basename(basename_buf);
  }
  
  encoding = NULL;
  
  /* default content-type */
  if (default_content_type == NULL)
    content_type_str = "application/octet-stream";
  else
    content_type_str = default_content_type;
    
  content = mailmime_content_new_with_str(content_type_str);
  if (content == NULL) {
    goto free_content;
  }
  
  do_encoding = 1;
  if (content->ct_type->tp_type == MAILMIME_TYPE_COMPOSITE_TYPE) {
    struct mailmime_composite_type * composite;
    
    composite = content->ct_type->tp_data.tp_composite_type;
    
    switch (composite->ct_type) {
    case MAILMIME_COMPOSITE_TYPE_MESSAGE:
      if (strcasecmp(content->ct_subtype, "rfc822") == 0)
        do_encoding = 0;
      break;

    case MAILMIME_COMPOSITE_TYPE_MULTIPART:
      do_encoding = 0;
      break;
    }
  }
  
  if (do_encoding) {
    if (default_encoding == -1)
      encoding_type = MAILMIME_MECHANISM_BASE64;
    else
      encoding_type = default_encoding;
    
    /* default Content-Transfer-Encoding */
    encoding = mailmime_mechanism_new(encoding_type, NULL);
    if (encoding == NULL) {
      goto free_content;
    }
  }
  
  mime_fields = mailmime_fields_new_with_data(encoding,
      NULL, NULL, NULL, NULL);
  if (mime_fields == NULL) {
    goto free_content;
  }
  
  mime = mailmime_new_empty(content, mime_fields);
  if (mime == NULL) {
    goto free_mime_fields;
  }
  
  if ((filename != NULL) && (mime->mm_type == MAILMIME_SINGLE)) {
    /*
      duplicates the file so that the file can be deleted when
      the MIME part is done
    */
    dup_filename = dup_file(privacy, filename);
    if (dup_filename == NULL) {
      goto free_mime;
    }
  
    r = mailmime_set_body_file(mime, dup_filename);
    if (r != MAILIMF_NO_ERROR) {
      free(dup_filename);
      goto free_mime;
    }
  }
  
  return mime;
  
 free_mime:
  mailmime_free(mime);
  goto err;
 free_mime_fields:
  mailmime_fields_free(mime_fields);
  mailmime_content_free(content);
  goto err;
 free_content:
  if (encoding != NULL)
    mailmime_mechanism_free(encoding);
  if (content != NULL)
    mailmime_content_free(content);
 err:
  return NULL;
}


int mailmime_substitute(struct mailmime * old_mime,
    struct mailmime * new_mime)
{
  struct mailmime * parent;
  
  parent = old_mime->mm_parent;
  if (parent == NULL)
    return MAIL_ERROR_INVAL;
  
  if (old_mime->mm_parent_type == MAILMIME_MESSAGE)
    parent->mm_data.mm_message.mm_msg_mime = new_mime;
  else /* MAILMIME_MULTIPLE */
    old_mime->mm_multipart_pos->data = new_mime;
  new_mime->mm_parent = parent;
  new_mime->mm_parent_type = old_mime->mm_parent_type;
  
  /* detach old_mime */
  old_mime->mm_parent = NULL;
  old_mime->mm_parent_type = MAILMIME_NONE;
  
  return MAIL_NO_ERROR;
}



/* write mime headers and body to a file, CR LF fixed */

int mailprivacy_fetch_mime_body_to_file(struct mailprivacy * privacy,
    char * filename, size_t size,
    mailmessage * msg, struct mailmime * mime)
{
  int r;
  int res;
  FILE * f;
  char * content;
  size_t content_len;
  int col;
  
  if (mime->mm_parent_type == MAILMIME_NONE) {
    res = MAIL_ERROR_INVAL;
    goto err;
  }
  
  f = mailprivacy_get_tmp_file(privacy, filename, size);
  if (f == NULL) {
    res = MAIL_ERROR_FETCH;
    goto err;
  }
  
  r = mailprivacy_msg_fetch_section_mime(privacy, msg, mime,
      &content, &content_len);
  if (r != MAIL_NO_ERROR) {
    res = MAIL_ERROR_FETCH;
    goto close;
  }
  
  col = 0;
  r = mailimf_string_write(f, &col, content, content_len);
  mailprivacy_msg_fetch_result_free(privacy, msg, content);
  if (r != MAILIMF_NO_ERROR) {
    res = r;
    goto close;
  }
  
  r = mailprivacy_msg_fetch_section(privacy, msg, mime,
      &content, &content_len);
  if (r != MAIL_NO_ERROR) {
    res = MAIL_ERROR_FETCH;
    goto close;
  }
  
  r = mailimf_string_write(f, &col, content, content_len);
  mailprivacy_msg_fetch_result_free(privacy, msg, content);
  if (r != MAILIMF_NO_ERROR) {
    res = r;
    goto close;
  }
  
  fclose(f);
  
  return MAIL_NO_ERROR;
  
 close:
  fclose(f);
  unlink(filename);
 err:
  return res;
}


int mailprivacy_get_part_from_file(struct mailprivacy * privacy,
    int check_security, int reencode, char * filename,
    struct mailmime ** result_mime)
{
  int fd;
  struct mailmime * mime;
  int r;
  struct stat stat_info;
  int res;
  char * mapping;

  fd = open(filename, O_RDONLY);
  if (fd < 0) {
    res = MAIL_ERROR_FILE;
    goto err;
  }

  r = fstat(fd, &stat_info);
  if (r < 0) {
    res = MAIL_ERROR_FILE;
    goto close;
  }

  mapping = mmap(NULL, stat_info.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
  if (mapping == (char *)MAP_FAILED) {
    res = MAIL_ERROR_FILE;
    goto close;
  }
  
  mime = NULL;
  /* check recursive parts if privacy is set */
  r = mailprivacy_get_mime(privacy, check_security, reencode,
      mapping, stat_info.st_size, &mime);
  if (r != MAIL_NO_ERROR) {
    res =  r;
    goto unmap;
  }

  if (mime->mm_type == MAILMIME_MESSAGE) {
    struct mailmime * submime;
    
    submime = mime->mm_data.mm_message.mm_msg_mime;
    if (mime->mm_data.mm_message.mm_msg_mime != NULL) {
      mailmime_remove_part(submime);
      mailmime_free(mime);
      
      mime = submime;
    }
  }

  munmap(mapping, stat_info.st_size);
  
  close(fd);

  * result_mime = mime;

  return MAIL_NO_ERROR;
  
 unmap:
  munmap(mapping, stat_info.st_size);
 close:
  close(fd);
 err:
  return res;
}

int mail_quote_filename(char * result, size_t size, char * path)
{
  char * p;
  char * result_p;
  size_t remaining;

  result_p = result;
  remaining = size;

  for(p = path ; * p != '\0' ; p ++) {
    int quote_not_needed;
    
#if 0
    quote_not_needed = (isalpha(* p) || isdigit(* p) || (* p == '/'));
#else
    quote_not_needed = (* p != '\\') && (* p != '\'') && (* p != '\"');
#endif
    if (quote_not_needed) {
      if (remaining > 0) {
        * result_p = * p;
        result_p ++; 
        remaining --;
      }
      else {
        result[size - 1] = '\0';
        return -1;
      }
    }
    else { 
      if (remaining >= 2) {
        * result_p = '\\';
        result_p ++; 
        * result_p = * p;
        result_p ++; 
        remaining -= 2;
      }
      else {
        result[size - 1] = '\0';
        return -1;
      }
    }
  }
  if (remaining > 0) {
    * result_p = '\0';
  }
  else {
    result[size - 1] = '\0';
    return -1;
  }
  
  return 0;
}


static void prepare_mime_single(struct mailmime * mime)
{
  struct mailmime_single_fields single_fields;
  int encoding;
  int r;
  
  if (mime->mm_mime_fields != NULL) {
    mailmime_single_fields_init(&single_fields, mime->mm_mime_fields,
        mime->mm_content_type);
    if (single_fields.fld_encoding != NULL) {
      encoding = single_fields.fld_encoding->enc_type;
      switch (encoding) {
      case MAILMIME_MECHANISM_8BIT:
      case MAILMIME_MECHANISM_7BIT:
      case MAILMIME_MECHANISM_BINARY:
        single_fields.fld_encoding->enc_type =
          MAILMIME_MECHANISM_QUOTED_PRINTABLE;
        break;
      }
    }
    else {
      struct mailmime_mechanism * mechanism;
      struct mailmime_field * field;
      
      mechanism =
        mailmime_mechanism_new(MAILMIME_MECHANISM_QUOTED_PRINTABLE, NULL);
      if (mechanism == NULL)
        return;
      
      field = mailmime_field_new(MAILMIME_FIELD_TRANSFER_ENCODING,
          NULL, mechanism, NULL, NULL, 0, NULL, NULL, NULL);
      if (field == NULL) {
        mailmime_mechanism_free(mechanism);
        return;
      }
      
      r = clist_append(mime->mm_mime_fields->fld_list, field);
      if (r < 0) {
        mailmime_field_free(field);
        return;
      }
    }
  }
  
  if (mime->mm_type == MAILMIME_SINGLE) {
    switch (mime->mm_data.mm_single->dt_encoding) {
    case MAILMIME_MECHANISM_8BIT:
    case MAILMIME_MECHANISM_7BIT:
    case MAILMIME_MECHANISM_BINARY:
      mime->mm_data.mm_single->dt_encoding =
        MAILMIME_MECHANISM_QUOTED_PRINTABLE;
      mime->mm_data.mm_single->dt_encoded = 0;
      break;
    }
  }
}

/*
  mailprivacy_prepare_mime()
  
  we assume we built ourself the message.
*/

void mailprivacy_prepare_mime(struct mailmime * mime)
{
  clistiter * cur;
  
  switch (mime->mm_type) {
  case MAILMIME_SINGLE:
    if (mime->mm_data.mm_single != NULL) {
      prepare_mime_single(mime);
    }
    break;
    
  case MAILMIME_MULTIPLE:
    for(cur = clist_begin(mime->mm_data.mm_multipart.mm_mp_list) ;
        cur != NULL ; cur = clist_next(cur)) {
      struct mailmime * child;
      
      child = clist_content(cur);
      
      mailprivacy_prepare_mime(child);
    }
    break;
    
  case MAILMIME_MESSAGE:
    if (mime->mm_data.mm_message.mm_msg_mime) {
      mailprivacy_prepare_mime(mime->mm_data.mm_message.mm_msg_mime);
    }
    break;
  }
}


char * mailprivacy_dup_imf_file(struct mailprivacy * privacy,
    char * source_filename)
{
  char filename[PATH_MAX];
  FILE * dest_f;
  int r;
  struct stat stat_info;
  char * dest_filename;
  char * mapping;
  int fd;
  int col;
  
  dest_f = mailprivacy_get_tmp_file(privacy,
      filename, sizeof(filename));
  if (dest_f == NULL)
    goto err;
  
  dest_filename = strdup(filename);
  if (dest_filename == NULL)
    goto close_dest;
  
  fd = open(source_filename, O_RDONLY);
  if (fd < 0)
    goto free_dest;
  
  r = fstat(fd, &stat_info);
  if (r < 0)
    goto close_src;
  
  mapping = mmap(NULL, stat_info.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
  if (mapping == (char *)MAP_FAILED)
    goto close_src;
  
  col = 0;
  r = mailimf_string_write(dest_f, &col, mapping, stat_info.st_size);
  if (r != MAILIMF_NO_ERROR)
    goto unmap;
  
  munmap(mapping, stat_info.st_size);
  close(fd);
  fclose(dest_f);
  
  return dest_filename;
  
 unmap:
  munmap(mapping, stat_info.st_size);
 close_src:
  close(fd);
 free_dest:
  free(dest_filename);
 close_dest:
  fclose(dest_f);
 err:
  return NULL;
}

/* TODO : better function to duplicate mime fields, currenly such inelegant */

struct mailmime_fields *
mailprivacy_mime_fields_dup(struct mailprivacy * privacy,
    struct mailmime_fields * mime_fields)
{
  FILE * f;
  char tmp_file[PATH_MAX];
  int col;
  int r;
  struct mailmime_fields * dup_mime_fields;
  int fd;
  char * mapping;
  struct stat stat_info;
  struct mailimf_fields * fields;
  size_t cur_token;
  
  f = mailprivacy_get_tmp_file(privacy, tmp_file, sizeof(tmp_file));
  if (f == NULL)
    goto err;
  
  col = 0;
  r = mailmime_fields_write(f, &col, mime_fields);
  if (r != MAILIMF_NO_ERROR)
    goto unlink;
  
  fflush(f);
  
  fd = fileno(f);
  if (fd == -1)
    goto unlink;
  
  r = fstat(fd, &stat_info);
  if (r < 0)
    goto unlink;
  
  mapping = mmap(NULL, stat_info.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
  if (mapping == (char *)MAP_FAILED)
    goto unlink;
  
  cur_token = 0;
  r = mailimf_optional_fields_parse(mapping, stat_info.st_size,
      &cur_token, &fields);
  if (r != MAILIMF_NO_ERROR)
    goto unmap;
  
  r = mailmime_fields_parse(fields, &dup_mime_fields);
  mailimf_fields_free(fields);
  if (r != MAILIMF_NO_ERROR)
    goto unmap;
  
  munmap(mapping, stat_info.st_size);
  fclose(f);
  unlink(tmp_file);

  return dup_mime_fields;

 unmap:
  munmap(mapping, stat_info.st_size);
 unlink:
  fclose(f);
  unlink(tmp_file);
 err:
  return NULL;
}



struct mailmime_parameter *
mailmime_parameter_dup(struct mailmime_parameter * param)
{
  char * name;
  char * value;
  struct mailmime_parameter * dup_param;

  name = strdup(param->pa_name);
  if (name == NULL)
    goto err;
  
  value = strdup(param->pa_value);
  if (value == NULL)
    goto free_name;
  
  dup_param = mailmime_parameter_new(name, value);
  if (dup_param == NULL)
    goto free_value;
  
  return dup_param;
  
 free_value:
  free(value);
 free_name:
  free(name);
 err:
  return NULL;
}

struct mailmime_composite_type *
mailmime_composite_type_dup(struct mailmime_composite_type * composite_type)
{
  struct mailmime_composite_type * dup_composite;
  char * token;
  
  token = NULL;
  if (composite_type->ct_token != NULL) {
    token = strdup(composite_type->ct_token);
    if (token == NULL)
      goto err;
  }
  
  dup_composite = mailmime_composite_type_new(composite_type->ct_type, token);
  if (dup_composite == NULL)
    goto free_token;
  
  return dup_composite;
  
 free_token:
  if (token != NULL)
    free(token);
 err:
  return NULL;
}

struct mailmime_discrete_type *
mailmime_discrete_type_dup(struct mailmime_discrete_type * discrete_type)
{
  struct mailmime_discrete_type * dup_discrete;
  char * extension;
  
  extension = NULL;
  if (discrete_type->dt_extension != NULL) {
    extension = strdup(discrete_type->dt_extension);
    if (extension == NULL)
      goto err;
  }
  
  dup_discrete = mailmime_discrete_type_new(discrete_type->dt_type, extension);
  if (dup_discrete == NULL)
    goto free_extension;
  
  return dup_discrete;
  
 free_extension:
  if (extension != NULL)
    free(extension);
 err:
  return NULL;
}

struct mailmime_type * mailmime_type_dup(struct mailmime_type * type)
{
  struct mailmime_type * dup_type;
  struct mailmime_discrete_type * discrete_type;
  struct mailmime_composite_type * composite_type;
  
  discrete_type = NULL;
  composite_type = NULL;
  switch (type->tp_type) {
  case MAILMIME_TYPE_DISCRETE_TYPE:
    discrete_type =
      mailmime_discrete_type_dup(type->tp_data.tp_discrete_type);
    if (discrete_type == NULL)
      goto err;
    break;
    
    composite_type =
      mailmime_composite_type_dup(type->tp_data.tp_composite_type);
    if (composite_type == NULL)
      goto free_discrete;
  }
  
  dup_type = mailmime_type_new(type->tp_type, discrete_type, composite_type);
  if (dup_type == NULL)
    goto free_composite;
  
  return dup_type;
  
 free_composite:
  if (composite_type != NULL)
    mailmime_composite_type_free(composite_type);
 free_discrete:
  if (discrete_type != NULL)
    mailmime_discrete_type_free(discrete_type);
 err:
  return NULL;
}

struct mailmime_content *
mailmime_content_dup(struct mailmime_content * content)
{
  clist * list;
  struct mailmime_type * type;
  int r;
  struct mailmime_content * dup_content;
  char * subtype;

  type = mailmime_type_dup(content->ct_type);
  if (type == NULL)
    goto err;
  
  subtype = strdup(content->ct_subtype);
  if (subtype == NULL)
    goto free_type;
  
  list = clist_new();
  if (list == NULL)
    goto free_subtype;
  
  if (content->ct_parameters != NULL) {
    clistiter * cur;
    
    for(cur = clist_begin(content->ct_parameters) ;
        cur != NULL ; cur = clist_next(cur)) {
      struct mailmime_parameter * param;
      
      param = mailmime_parameter_dup(clist_content(cur));
      if (param == NULL)
        goto free_list;
      
      r = clist_append(list, param);
      if (r < 0) {
        mailmime_parameter_free(param);
        goto free_list;
      }
    }
  }
  
  dup_content = mailmime_content_new(type, subtype, list);
  if (dup_content == NULL)
    goto free_list;

  return dup_content;
  
 free_list:
  clist_foreach(list, (clist_func) mailmime_parameter_free, NULL);
 free_subtype:
  free(subtype);
 free_type:
  mailmime_type_free(type);
 err:
  return NULL;
}


int mailprivacy_fetch_decoded_to_file(struct mailprivacy * privacy,
    char * filename, size_t size,
    mailmessage * msg, struct mailmime * mime)
{
  int r;
  int res;
  FILE * f;
  char * content;
  size_t content_len;
  size_t written;
  struct mailmime_single_fields single_fields;
  int encoding;
  size_t cur_token;
  char * parsed_content;
  size_t parsed_content_len;
  
  mailmime_single_fields_init(&single_fields, mime->mm_mime_fields,
      mime->mm_content_type);
  if (single_fields.fld_encoding != NULL)
    encoding = single_fields.fld_encoding->enc_type;
  else
    encoding = MAILMIME_MECHANISM_8BIT;
  
  r = mailprivacy_msg_fetch_section(privacy, msg, mime,
      &content, &content_len);
  if (r != MAIL_NO_ERROR) {
    res = MAIL_ERROR_FETCH;
    goto err;
  }
  
  cur_token = 0;
  r = mailmime_part_parse(content, content_len, &cur_token,
      encoding, &parsed_content, &parsed_content_len);
  mailprivacy_msg_fetch_result_free(privacy, msg, content);
  if (r != MAILIMF_NO_ERROR) {
    res = MAIL_ERROR_PARSE;
    goto err;
  }
  
  f = mailprivacy_get_tmp_file(privacy, filename, size);
  if (f == NULL) {
    res = MAIL_ERROR_FETCH;
    goto free_fetch;
  }
  written = fwrite(parsed_content, 1, parsed_content_len, f);
  if (written != parsed_content_len) {
    res = MAIL_ERROR_FILE;
    goto close;
  }
  fclose(f);
  
  mmap_string_unref(parsed_content);
  
  return MAIL_NO_ERROR;

 close:
  fclose(f);
  unlink(filename);
 free_fetch:
  mmap_string_unref(parsed_content);
 err:
  return res;
}



#ifdef HAVE_MINGW32_SYSTEM
/* Define to 1 do enable debugging.  */
#define DEBUG_W32_SPAWN 0

/* Create pipe where the read end is inheritable.  */
static int
create_inheritable_pipe (HANDLE filedes[2])
{
  HANDLE r, w, h;
  SECURITY_ATTRIBUTES sec_attr;

  memset (&sec_attr, 0, sizeof sec_attr);
  sec_attr.nLength = sizeof sec_attr;
  sec_attr.bInheritHandle = FALSE;
    
  if (!CreatePipe (&r, &w, &sec_attr, 0))
    return -1;

  if (!DuplicateHandle (GetCurrentProcess(), r,
                        GetCurrentProcess(), &h, 0,
                        TRUE, DUPLICATE_SAME_ACCESS ))
    {
      CloseHandle (r);
      CloseHandle (w);
      return -1;
    }
  CloseHandle (w);
  w = h;

  filedes[0] = r;
  filedes[1] = w;
  return 0;
}
#endif /*HAVE_MINGW32_SYSTEM*/


int mailprivacy_spawn_and_wait(char * command, char * passphrase,
    char * stdoutfile, char * stderrfile,
    int * bad_passphrase)
{
#if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR
  //https://github.com/dinhviethoa/libetpan/issues/275
  //mailprivacy_spawn_and_wait is not needed on iOS
  return MAIL_ERROR_COMMAND;
#endif

#ifdef WIN32
  int res;
  SECURITY_ATTRIBUTES sec_attr;
  PROCESS_INFORMATION pi = 
    {
      NULL,      /* Returns process handle.  */
      0,         /* Returns primary thread handle.  */
      0,         /* Returns pid.  */
      0          /* Returns tid.  */
    };
  STARTUPINFO si;
  int cr_flags;
  HANDLE wp_passphrase[2];
  HANDLE fd_out;
  HANDLE fd_err;
  int code;

  /* Prepare security attributes.  */
  memset (&sec_attr, 0, sizeof sec_attr);
  sec_attr.nLength = sizeof sec_attr;
  sec_attr.bInheritHandle = TRUE;

  fd_out = CreateFile (stdoutfile,
		       GENERIC_READ|GENERIC_WRITE,
		       FILE_SHARE_READ|FILE_SHARE_WRITE,
		       &sec_attr,
		       CREATE_ALWAYS,
		       FILE_ATTRIBUTE_NORMAL,
		       NULL);
  if (fd_out == INVALID_HANDLE_VALUE)
    {
      res = ERROR_PASSPHRASE_FILE;
      goto err;
    }

  fd_err = CreateFile (stderrfile,
		       GENERIC_READ|GENERIC_WRITE,
		       FILE_SHARE_READ|FILE_SHARE_WRITE,
		       &sec_attr,
		       CREATE_ALWAYS,
		       FILE_ATTRIBUTE_NORMAL,
		       NULL);
  if (fd_err == INVALID_HANDLE_VALUE)
    {
      res = ERROR_PASSPHRASE_FILE;
      goto close_out;
    }

  /* Create a pipe for the passphrase.  */
  if (create_inheritable_pipe (wp_passphrase))
    {
      res = ERROR_PASSPHRASE_FILE;
      goto close_err;
    }

  /* Prepare security attributes.  */
  memset (&sec_attr, 0, sizeof sec_attr);
  sec_attr.nLength = sizeof sec_attr;
  sec_attr.bInheritHandle = FALSE;
  
  /* Start the process.  */
  memset (&si, 0, sizeof si);
  si.cb = sizeof (si);
  si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
  si.wShowWindow = DEBUG_W32_SPAWN ? SW_SHOW : SW_MINIMIZE;
  si.hStdInput  = wp_passphrase[0];
  si.hStdOutput = fd_out;
  si.hStdError  = fd_err;

  cr_flags = (CREATE_DEFAULT_ERROR_MODE
              | GetPriorityClass (GetCurrentProcess ())
              | CREATE_SUSPENDED); 
  if (!CreateProcess (NULL,          /* Program to start.  */
                      command,       /* Command line arguments.  */
                      &sec_attr,     /* Process security attributes.  */
                      &sec_attr,     /* Thread security attributes.  */
                      TRUE,          /* Inherit handles.  */
                      cr_flags,      /* Creation flags.  */
                      NULL,          /* Environment.  */
                      NULL,          /* Use current drive/directory.  */
                      &si,           /* Startup information. */
                      &pi            /* Returns process information.  */
                      ))
    {
      CloseHandle (wp_passphrase[0]);
      CloseHandle (wp_passphrase[1]);

      res = ERROR_PASSPHRASE_COMMAND;
      goto close_err;
    }

  /* Close the other end of the pipe.  */
  CloseHandle (wp_passphrase[0]);
  CloseHandle (fd_out);
  CloseHandle (fd_err);
  /* Process has been created suspended; resume it now. */
  ResumeThread (pi.hThread);
  CloseHandle (pi.hThread); 

  if ((passphrase != NULL) && (strlen(passphrase) > 0)) {
    DWORD written;
    WriteFile (wp_passphrase[1], passphrase, strlen(passphrase),
	       &written, NULL);
  }
  else {
    DWORD written;
    /* dummy password */
    WriteFile(wp_passphrase[1], "*dummy*", 7, &written, NULL);
  }
  CloseHandle(wp_passphrase[1]);

  code = WaitForSingleObject (pi.hProcess, INFINITE);
  if (code == WAIT_OBJECT_0)
    {
      DWORD exc;
      if (GetExitCodeProcess (pi.hProcess, &exc))
	{
	  if (exc)
	    *bad_passphrase = 1;
	}
      else
	res = ERROR_PASSPHRASE_COMMAND;
    }
  else
    res = ERROR_PASSPHRASE_COMMAND;

  CloseHandle (pi.hProcess);
  return 0;

 close_err:
  CloseHandle (fd_err);
 close_out:
  CloseHandle (fd_out);
 err:
  return res;
#else
  int res;
  int fd_out;
  int fd_err;
  pid_t pid;
  int passphrase_input[2];
  int r;

  fd_out = open(stdoutfile, O_RDWR | O_CREAT | O_TRUNC, 0600);
  if (fd_out < 0) {
    res = ERROR_PASSPHRASE_FILE;
    goto err;
  }
  
  fd_err = open(stderrfile, O_RDWR | O_CREAT | O_TRUNC, 0600);
  if (fd_err < 0) {
    res = ERROR_PASSPHRASE_FILE;
    goto close_out;
  }
  
  r = pipe(passphrase_input);
  if (r < 0) {
    res = ERROR_PASSPHRASE_FILE;
    goto close_err;
  }
  
  pid = fork();
  switch (pid) {
  case -1:
    {
      close (passphrase_input[0]);
      close (passphrase_input[1]);
      res = ERROR_PASSPHRASE_COMMAND;
      goto close_err;
    }
    
  case 0:
    /* child */
    {
      int status;
      
      /* close unneeded fd */
      close(passphrase_input[1]);
      
      dup2(passphrase_input[0], 0);
      close(passphrase_input[0]);
      dup2(fd_out, 1);
      close(fd_out);
      dup2(fd_err, 2);
      close(fd_err);
      
#if !TARGET_OS_IPHONE && !TARGET_IPHONE_SIMULATOR
      //https://github.com/dinhviethoa/libetpan/issues/275
      //system() is not supported on iOS 11.
      status = system(command);
#endif
  
      exit(WEXITSTATUS(status));
    }
    break;
    
  default:
    /* parent */
    {
      int status;
      
      /* close unneeded fd */
      close(fd_err);
      close(fd_out);
      close(passphrase_input[0]);
      
      if ((passphrase != NULL) && (strlen(passphrase) > 0)) {
        r = (int) write(passphrase_input[1], passphrase, strlen(passphrase));
        if (r != (int) strlen(passphrase)) {
          close(passphrase_input[1]);
          return ERROR_PASSPHRASE_FILE;
        }
      }
      else {
        /* dummy password */
        r = (int) write(passphrase_input[1], "*dummy*", 7);
        if (r != 7) {
          close(passphrase_input[1]);
          return ERROR_PASSPHRASE_FILE;
        }
      }
      close(passphrase_input[1]);
      
      waitpid(pid, &status, 0);
      
      if (WEXITSTATUS(status) != 0)
        *bad_passphrase = 1;

      return MAIL_NO_ERROR;
    }
    break;
  }
  
 close_err:
  close(fd_err);
 close_out:
  close(fd_out);
 err:
  return res;
#endif
}


