/*
    Fichier joindre.c
    Auteur Bernard Chardonneau

    Logiciel libre, droits d'utilisation précisés en français
    dans le fichier : licence-fr.txt

    Traductions des droits d'utilisation dans les fichiers :
    licence-de.txt , licence-en.txt , licence-es.txt ,
    licence-it.txt , licence-nl.txt , licence-pt.txt ,
    licence-eo.txt , licence-eo-utf.txt

    Droits d'utilisation également sur la page web :
    http://libremail.tuxfamily.org/voir.php?page=droits


    Ce programme rajoute des fichiers joints à un mail à envoyer.
    Le nom du fichier mail concerné est passé en paramètre.
    Le chemin d'accès au fichier peut être absolu ou relatif
*/


#define appli   // pour la déclaration de variables globales à l'application

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include "messages.h"
#include "buflect.h"
#include "fmail.h"
#include "trtentete.h"
#include "trtligne.h"
#include "trtbordure.h"
#include "base64.h"


#define  octet     unsigned char


/* prototypes */
void prepar_mail ();
void ajout_fich (int narg, char **varg);
void joinfich (char *chemfich);
void ajout_quoted ();
void ajout_base64 ();


/* variables globales au source
   (pour éviter des tonnes de passages de paramètres) */


FILE *descfic;       // descripteur du fichier à joindre
int  ajouts = 0;     // pour éviter défauts si aucun fichier n'est rajouté



/* programme principal */

int main (int nbarg, char *varg[])
{
    // récupération du nom de l'exécutable
    memcom (*varg);

    // controle du nombre d'arguments
    if (--nbarg >= 1)
    {
        // si le fichier mail existe
        if (access (varg [1], 0) == 0)
        {
            // passer le mail en mode multipart/mixed si nécessaire
            prepar_mail (varg [1]);

            // ajouter les fichiers à joindre
            ajout_fich (--nbarg, varg + 1);
        }
        else
            // "Fichier %s non trouvé"
            aff_err_arg ("FICH_ABSENT", varg [1]);
    }
    else
        // "Syntaxe : %s nom_fichier_mail [fichiers_à_joindre]"
        psyntaxe ("SYNT_JOINDRE");

    // pour faire plaisir à gcc qui veut une fonction main de type int
    return (0);
}



/* passe le mail en mode multipart/mixed s'il n'y est pas déjà */

void prepar_mail (char *nomfmail)
{
    long posfic;          // position dans le fichier en début de ligne
    long posContent;      // position au début du champ Content-Type
    char bufEncode [120]; // contenu du champ Content-Transfer-Encoding
    char fictmp [120];    // fichier tampon pour passer en mode multipart/mixed
    FILE *desctmp;        // descripteur du fichier fictmp
    int  typederlig;      // mémorise si la dernière ligne du message est blanche


    // initialisation
    posfic     =  0;
    posContent = -1;
    *bufEncode = '\0';

    // ouvrir le fichier mail en lecture/écriture
    fmail = fopen (nomfmail, "r+");

    // erreur fatale si ouverture refusée
    if (! fmail)
    {
        // "Impossible de mettre à jour le fichier %s"
        errfatale ("IMPOS_MAJ_FICH", nomfmail);
    }

    // lecture du fichier mail jusqu'au champ Content-Type
    while (lire_fmail () && ! start ("Content-Type"))
        ;

    // si le fichier n'est pas encore mode multipart/mixed
    if (recup_ctype () != MultipMixed)
    {
        // fabriquer un nom de fichier tampon
        strcpy (fictmp, nomfmail);
        strcat (fictmp, ".t");

        // et l'ouvrir en écriture
        desctmp = fopen (fictmp, "w");

        // erreur fatale si ouverture refusée
        if (! desctmp)
        {
            // "Impossible de mettre à jour le fichier %s"
            errfatale ("IMPOS_MAJ_FICH", nomfmail);
        }

        // revenir au debut du fichier mail
        rewind (fmail);
        lire_fmail ();

        // recopier son entête sans les champs Content-T...
        while (lire_fmail () && *buf_lect)
        {
            // si champ Content-Type
            if (start ("Content-Type"))
            {
                // on mémorise la position de la ligne
                posContent = posfic;

                // et on lit jusqu'à la dernière ligne du champ
                do
                    lire_fmail ();
                while (*buf_lect == ' ' || *buf_lect == '\t');
            }

            // si champ Content-Transfer-Encoding
            if (start ("Content-Transfer-Encoding"))
                // on mémorise la ligne
                membuf (bufEncode);
            // sinon
            else
            {
                // on recopie le ligne dans le fichier tampon
                fputs (buf_lect, desctmp);
                fputc ('\n', desctmp);
            }

            // récupérer la position de début de la ligne suivante
            posfic = ftell (fmail);
        }

        // générer une bordure avec une partie aléatoire
        sprintf (bordure [0], "-LIBREMAIL-BORDURE-id=%08lX%03X-", time (0),
                                                                 getpid ());

        // générer le nouveau champ Content-Type
        fprintf (desctmp, "Content-Type: multipart/mixed; boundary=\"%s\"\n",
                                                                 bordure [0]);

        // première section : générer la bordure
        fprintf (desctmp, "\n--%s\n", bordure [0]);

        // le champ Content-Type
        if (posContent >= 0)
        {
            // on se positionne dessus pour le recopier
            fseek (fmail, posContent, SEEK_SET);
            lire_fmail ();

            // répéter
            do
            {
                // recopier la ligne
                fprintf (desctmp, "%s\n", buf_lect);

                // et lire la suivante
                lire_fmail ();
            }
            // jusqu'à la fin de ce champ
            while (*buf_lect == ' ' || *buf_lect == '\t');
        }
        else
        {
            // jeu de caractères du message
            fputs ("Content-Type: text/plain; charset=", desctmp);

            if (util_utf8 ())
                fputs ("UTF-8;\n", desctmp);
            else
                fputs ("ISO-8859-15;\n", desctmp);
        }

        // le champ Content-Transfer-Encoding
        if (*bufEncode)
            fputs (bufEncode, desctmp);
        else
            fputs ("Content-Transfer-Encoding: 8bit", desctmp);

        // + passage à la ligne
        fputc ('\n', desctmp);

        // revenir sur le texte du mail
        fseek (fmail, posfic, SEEK_SET);

        // le recopier jusqu'en fin de fichier
        while (lire_fmail ())
        {
            // recopier la ligne
            fprintf (desctmp, "%s\n", buf_lect);

            // mémoriser si longueur nulle
            typederlig = *buf_lect;
        }

        // rajouter une ligne blanche si nécessaire
        if (typederlig)
            fputc ('\n', desctmp);

        // générer la bordure finale
        fprintf (desctmp, "--%s--\n", bordure [0]);

        // fermer les fichiers
        fclose (desctmp);
        fclose (fmail);

        // et remplacer l'ancien fichier par le nouveau
        unlink (nomfmail);
        rename (fictmp, nomfmail);
    }
    // sinon (fichier déjà en mode multipart/mixed)
    else
    {
        // mémoriser la bordure
        while (*buf_lect && ! mem_boundary ())
            lire_fmail ();

        // et fermer le fichier (on le réouvrira dans ajout_fich)
        fclose (fmail);
    }
}



/* rajoute les fichiers joints au mail */

void ajout_fich (int narg, char **varg)
{
    char chemfich [120];   // vom de fichier à joindre saisi au clavier
    int  i;                // compteur


    // ouvrir le fichier mail en lecture/écriture
    fmail = fopen (*varg, "r+");

    // traitement d'une erreur qui ne devrait jamais arriver
    if (!fmail)
    {
        // "Erreur 2ème ouverture fichier mail !!!"
        affiche_err ("ERR_OUVERT_2");
        return;
    }

    // si des noms de fichiers à joindre ont été passés en paramètre
    if (narg > 0)
    {
        // les rajouter en pièce jointe un par un
        for (i = 1; i <= narg; i++)
            joinfich (varg [i]);
    }
    // sinon, on va saisir leur nom au clavier
    else
    {
        // retour à la configuration standard du clavier si nécessaire
        system ("stty icanon echo");

        do
        {
         // "Nom de fichier à joindre ? (touche entrée quand il n'y en a plus)"
            affiche_msg ("NOUV_FICJOINT");

            // on lit avec fgets pour éviter le warning de compilation de gets
            // puis on enlève le \n en trop  (que gets ne met pas)
            fgets (chemfich, sizeof (chemfich), stdin);
            chemfich [strlen (chemfich) - 1] = '\0';

            if (*chemfich)
                joinfich (chemfich);
        }
        while (*chemfich);   // liste terminée lorsque ligne vide
    }

    // si on a rajouté des fichiers
    if (ajouts)
    {
        // corriger la dernière bordure
        fseek (fmail, -1, SEEK_END);
        fputs ("--\n", fmail);
    }

    // le fichier mail est prêt
    fclose (fmail);
}



/* rajoute un fichier joint au mail */

void joinfich (char *chemfich)
{
    int  car;              // caractère du fichier à joindre
    int  normal, special;  // compteurs de caractères
    char nomfich [30];     // nom du fichier joint (sans le chemin)
    int  i, j;             // compteurs


    // ouvrir en lecture le fichier à joindre
    descfic = fopen (chemfich, "r");

    // si ouvert
    if (descfic)
    {
        // initialisation
        normal  = 0;
        special = 0;

        // on va déterminer le mode d'encodage le moins encombrant
        car = getc (descfic);

        // tant que non fin de fichier
        while (car != EOF)
        {
            // comptabiliser ce caractère dans la bonne catégorie
            if ((car < 0x20 && car != '\t') || car == '=' || car > 0X7E)
                special++;
            else
                normal++;

            // continuer avec le caractère suivant
            car = getc (descfic);
        }

        // si pas encore de fichier rajouté
        if (!ajouts)
        {
            // supprimer 2 - à la dernière bordure
            fseek (fmail, -3, SEEK_END);
            fputc ('\n', fmail);

            // cette correction n'est faite qu'une fois
            ajouts = 1;
        }

        // fabriquer le nom du fichier joint à partir du chemin
        j = 0;

        for (i = 0; chemfich [i]; i++)
        {
            // on supprime le nom de répertoire du fichier à joindre
            if (chemfich [i] == '/' || chemfich [i] == '\\')
                j = 0;
            // ainsi que les blancs dans le nom de fichier
            else if (chemfich [i] != ' ')
                nomfich [j++] = chemfich [i];

        }

        nomfich [j] = 0;

        // générer l'entête de la pièce jointe
        fputs   ("Content-Type: application/octet-stream;\n", fmail);
        fprintf (fmail, "        name=\"%s\"\n", nomfich);
        fputs   ("Content-Disposition: attachment;\n", fmail);
        fprintf (fmail, "        filename=\"%s\"\n", nomfich);

        // revenir en début de fichier
        rewind (descfic);

        // choisir le mode d'encodage pour joindre la pièce
        if (special * 5 <= normal)
            ajout_quoted ();
        else
            ajout_base64 ();

        // générer la bordure terminale
        fprintf (fmail, "--%s\n", bordure [0]);

        // terminé avec ce fichier
        fclose (descfic);
    }
    // sinon, message d'erreur
    else if (access (chemfich, 0) < 0)
        // "Fichier %s inexistant"
        aff_err_arg ("FICH_INEXISTANT", chemfich);
    else
        // "Fichier %s protégé en lecture"
        aff_err_arg ("ACCES_FICH_LECT", chemfich);
}



/* encode le fichier à joindre en quoted-printable */

void ajout_quoted ()
{
    int car;       // caractères du fichier à joindre
    int posligne;  // position dans la ligne courante du fichier mail


    // terminaison de l'entête de la pièce jointe
    fputs ("Content-Transfer-Encoding: quoted-printable\n\n", fmail);

    // initialisation
    posligne = 1;
    car = getc (descfic);

    // tant que non fin de fichier
    while (car != EOF)
    {
        // traitement particulier du caractère CR
        if (car == '\r')
        {
            // lire le caractère suivant
            car = getc (descfic);

            // si c'est un LF
            if (car == '\n')
            {
                // passage à la ligne sans le \r qui
                // sera rajouté à l'expédition du mail
                fputc ('\n', fmail);
                posligne = 1;
            }
            // sinon
            else
            {
                // encoder le CR comme les autres caractères spéciaux
                fputs ("=0D", fmail);
                posligne = posligne + 3;

                // on traitera le caractère qui suit plus tard
                ungetc (car, descfic);
            }

            // si CR LF un passage à la ligne sera généré plus loin
        }

        // autres caractères spéciaux
        else if ((car < 0x20 && car != '\t') || car == '=' || car > 0X7E)
        {
            // passage à la ligne si la précédente était trop longue
            if (posligne > 72)
            {
                fputs ("=\n", fmail);
                posligne = 1;
            }

            // ces caractères sont encodés
            fprintf (fmail, "=%02X", car);

            if (car == '\n')
            {
                fputs ("=\n", fmail);   // pour la lisibilité
                posligne = 1;
            }
            else
                posligne = posligne + 3;
        }
        // caractères normaux
        else
        {
            // passage à la ligne si la précédente était trop longue
            if (posligne > 74)
            {
                fputs ("=\n", fmail);
                posligne = 1;
            }

            // ces caractères sont juste recopiés
            putc (car, fmail);
            posligne ++;
        }

        // passer au caractère suivant
        car = getc (descfic);
    }
}



/* encode le fichier à joindre en base64 */

void ajout_base64 ()
{
    char bufin  [58]; // tableau contenant des données du fichier à joindre
    char bufout [77]; // données de bufin converties base64
    int  nbcar;       // nombre de caractères récupérés dans bufin


    // terminaison de l'entête de la pièce jointe
    fputs ("Content-Transfer-Encoding: base64\n\n", fmail);

    // lire de quoi générer une ligne encodée base64
    nbcar = fread (bufin, 1, 57, descfic);

    // si le fichier joint n'est pas vide
    if (nbcar)
    {
        // répéter
        do
        {
            // encoder les données base64 et les copier dans le fichier mail
            encode64 (bufin, bufout, nbcar);
            fputs (bufout, fmail);
            fputc ('\n', fmail);

            // lire de quoi générer une ligne de plus
            nbcar = fread (bufin, 1, 57, descfic);
        }
        // jusqu'à fin de fichier
        while (nbcar);
    }
}