/*
    Fichier voirmail.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 affiche un mail en attente de téléchargement
    (ou de destruction).
    Un fichier de configuration est utilisé pour se connecter à la
    boite aux lettres.
    Le mail à afficher est repéré par son numéro d'ordre dans la
    liste des mails sur le serveur.
*/


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

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "messages.h"
#include "buflect.h"
#include "ficonf.h"
#include "pop.h"
#include "encodage.h"
#include "trtentete.h"
#include "trtligne.h"
#include "trtbordure.h"
#include "trtsection.h"


/* prototypes */
void affiche_entete (int numes);
void affiche_corpsmail ();
void aff_mail ();
void aff_texte ();
int  fin_mail ();


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


int ctypeorig;   // type principal du mail
int ctype;       // type de la section courante

// options d'affichage
int opthb;       // de la section text/html au lieu de text/plain
int optT;        // affichage partiel de l'entête pour les traductions

int lig_nonvide; // présence de caractères significatifs dans la ligne



/* programme principal */

void main (int nbarg, char *varg[])
{
    FILE *fconf;        // descripteur du fichier de configuration
    int  numes;         // numéro du mail courant


    // récupération du nom de l'exécutable
    memcom (*varg);

    // si commande lancée avec une option
    if (--nbarg > 1 && varg [1][0] == '-')
    {
        // prendre en compte les options -h, -H, -b et -B
        switch (varg [1][1])
        {
            case 'h' : opthb = 1;
                       break;

            case 'H' : opthb = 3;
                       break;

            case 'b' : opthb = 5;
                       break;

            case 'B' : opthb = 7;
                       break;

            // cas d'une option incorrecte
            default  : psyntaxe ("SYNT_VOIRMAIL");
                       // on n'ira pas plus loin
                       return;
        }

        // un argument traité
        nbarg --;
        varg ++;
    }
    else
        // sinon pas d'option
        opthb = 0;

    // controle du nombre d'arguments
    if (nbarg == 2)
    {
        // ouvrir le fichier de configuration
        fconf = ouvre_ficonf (varg [2]);

        if (fconf)
        {
            // connexion sur le compte mail du serveur pop
            if (connect_pop (fconf))
            {
                // récupérer le numéro de mail
                numes = atoi (varg [1]);

                // afficher ce mail si possible
                if (numes > 0 && numes <= nbmails ())
                {
                    // entête
                    affiche_entete (numes);

                    // et contenu
                    affiche_corpsmail ();
                }
                else if (numes > 0)
                    // "Numéro de mail trop grand"
                    affiche_err ("NUMAIL_TROP_GRAND");
                else
                    // "Numéro de mail %s incorrect"
                    aff_err_arg ("NUMAIL_FAUX", varg [1]);

                // se déconnecter proprement du serveur pop
                deconnect_pop ();
            }

            // on n'a plus besoin du fichier de configuration
            fclose (fconf);
        }
    }
    // sinon
    else
        // "Syntaxe : %s [-(h|H|b|B)] numéro_message fichier_configuration"
        psyntaxe ("SYNT_VOIRMAIL");

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



/* lit l'entête du message choisi et l'affiche */

void affiche_entete (int numes)
{
    char bufw [120];     // buffer d'envoi d'une requête
    // buffers pour affichage ordonné des caractéristiques du message
    char bufFrom [120], bufXorig [120], bufTo [120], bufCc [120],
         bufReply [120], bufSubject [120];
    int  analsuiv;      // indicateur : il faudra traiter la ligne suivante
    char *varenv_xorig; // variable d'environnement libremail_xorig
    char xorig;         // information utile de libremail_xorig


    // initialisation
    bufFrom [0]    = '\0';
    bufXorig [0]   = '\0';
    bufTo [0]      = '\0';
    bufCc [0]      = '\0';
    bufReply [0]   = '\0';
    bufSubject [0] = '\0';
    analsuiv       =   0;
    nbordures      =   0;
    ctypeorig = TextPlain;  // valeur par défaut

    // récupérer la partie significative de la variable libremail_xorig
    varenv_xorig = getenv ("libremail_xorig");

    if (varenv_xorig)
        xorig = tolower (*varenv_xorig);
    else
        xorig = '\0';

    // demande de lecture du message
    sprintf (bufw, "RETR %d", numes);
    env_pop (bufw);

    // "Message %d\n"
    printf (message ("AFF_NUMAIL"), numes);

    // lire la première ligne de l'entête du message
    lire_pop ();

    // terminé pour ce mail si erreur d'envoi coté serveur
    if (memcmp (buf_lect, "-ERR ", 5) == 0)
    {
        puts (buf_lect);
        return;
    }

    // lecture et mémorisation des caractéristiques du message
    do
    {
        // récupération si nécessaire du charset ou de la
        // bordure dans la 2ème ligne du champ Content-Type
        if (analsuiv)
        {
            // si mode multipart
            if (ctypeorig & Multipart)
            {
                // si on trouve une bordure, la mémoriser
                if (mem_boundary ())
                    // dans ce cas, l'analyse ne se poursuivra
                    // pas sur les lignes suivantes
                    analsuiv = 0;
            }
            // sinon
            else
            {
                // mémoriser le jeu de caractères
                lire_charset ();

                // l'analyse ne se poursuivra pas sur les lignes suivantes
                analsuiv = 0;
            }
        }

        // mémorisation des caractéristiques importantes
        if (start ("Date"))
            puts (buf_lect);   // la date est affichée directement
        else if (start ("From"))
            memconvbuf (bufFrom);
        else if (start ("X-Original-From") && xorig != 'n')
            memconvbuf (bufXorig);
        else if (start ("To"))
            memconvbuf (bufTo);
        else if (start ("Cc"))
            memconvbuf (bufCc);
        else if (start ("Reply-To"))
            memconvbuf (bufReply);
        else if (start ("Subject"))
            memconvbuf (bufSubject);
        else if (start ("Content-Type"))
        {
            // récupérer la valeur du champ Content-Type
            ctypeorig = recup_ctype ();

            // si ce type correspond au texte du mail
            if (ctypeorig == TextPlain || ctypeorig == TextHtml)
            {
                // mémoriser le jeu de caractères
                if (! lire_charset ())
                    // à partir de cette ligne ou de la suivante
                    analsuiv = 1;
            }
            // sinon si mode multipart
            else if (ctypeorig & Multipart)
            {
                // mémoriser la bordure
                if (! mem_boundary ())
                    // à partir de cette ligne ou de la suivante
                    analsuiv = 1;
            }
        }
        else if (start ("Content-Transfer-Encoding"))
            mem_encodage ();

        // lire la ligne suivante de l'entête du message
        lire_pop ();
    }
    while (buf_lect [0] != '\0');  // lecture entête terminée si ligne vide

    // affichage de certaines caractéristiques mémorisées
    if (bufFrom [0] && (! bufXorig [0] || xorig == '2'))
        puts (bufFrom);
    if (bufXorig [0])
        puts (bufXorig);
    if (bufTo [0])
        puts (bufTo);
    if (bufCc [0])
        puts (bufCc);
    if (bufReply [0])
        puts (bufReply);
    if (bufSubject [0])
        puts (bufSubject);
}



/* lit le contenu du message choisi et l'affiche */

void affiche_corpsmail ()
{
    // sauter la ligne blanche qui précède le message
    lire_pop ();

    // affichage du contenu du mail
    if (ctypeorig & Multipart)
        // il faudra analyser les sections
        aff_mail ();
    else
        // un affichage simple suffit
        aff_texte ();
}



/* suppression des balises html et affichage du reste */

void filtre_balhtm ()
{
    // supprimer les balises html
    sup_balhtm ();

    // réduit à un les blancs multiples et les supprime en fin de ligne
    sup_multiblancs ();

    // et convertir les caractère sous la forme &...;
    conv_carhtm ();

    // si la dernière ligne lue n'est pas vide
    if (*buf_lect && *buf_lect != '\n')
    {
        // l'afficher
        printf ("%s", buf_lect);

        // et mémoriser le fait qu'elle n'est pas vide
        lig_nonvide = 1;
    }
    // sinon si la précédente n'était pas vide ou pas d'option -H
    else if (lig_nonvide)
    {
        // l'afficher
        putchar ('\n');

        // et mémoriser le fait qu'elle est vide
        lig_nonvide = 0;
    }
}



/* affichage du contenu du mail */

void aff_mail ()
{
    // mémorisation des modes multipart trouvés
    int multipmixed = 0, multipalter = 0, multiprel = 0, multiprep = 0;


    // mémoriser tous les modes multipart imbriqués
    do
    {
        switch (ctype)
        {
            case MultipMixed : multipmixed = 1;
                               break;

            case MultipAlter : multipalter = 1;
                               break;

            case MultipRep   : multiprep   = 1;
                               break;

            case MultipRel   : multiprel   = 1;
        }

        // passer à la section suivante
        prochaine_section ();

        // récupérer ses caractéristiques
        recup_infos_section ();
    }
    while ((ctype & Multipart) && ! fin_mail ());

    // indiquer si le mail peut contenir des pièces jointes
    if (multipmixed)
        // "Pièce(s) jointes(s) probable(s)"
        affiche_msg ("PJ_PROBABLE");

    // si une section multipart/alternative a été trouvée, se positionner
    // (si l'on n'y est pas) sur la section text/plain ou text/html du mail
    if (multipalter)
    {
        if (opthb)
            posit_texthtm ();
        else
            posit_texte ();
    }

    // générer une ligne de séparation avec l'entête
    putchar ('\n');

    // si structure du mail non conforme, message d'erreur
    if (fin_mail ())
    {
        if (opthb)
            // "Pas de zone texte html dans ce mail !!!"
            affiche_msg ("MANQUE_ZONE_HTML");
        else
            // "Pas de zone texte dans ce mail !!!"
            affiche_msg ("MANQUE_ZONE_TEXTE");
    }

    // si on doit supprimer la partie html avant <body
    if (opthb & 4)
        // le mémoriser
        avantbody = 1;

    // lecture et affichage du corps du message
    // on s'arrête en fin de mail, ou sur la prochaine bordure
    while (! fin_mail () && (nbordures == 0 || ! surbordure ()))
    {
        // mise en forme de la dernière ligne lue
        majligne ();

        // si on est dans l'entête html qu'on doit supprimer
        if (avantbody)
            // le faire
            supavantbody ();

        // si option de suppression des balises html
        if (opthb & 2)
            // les supprimer
            filtre_balhtm ();

        // sinon afficher simplement la ligne
        else
            printf ("%s", buf_lect);

        // et lecture de la suivante
        lire_pop ();

        // si mode multipart/report, on saute les entêtes de section
        if (multiprep && nbordures && surbordure ())
        {
            do
                lire_pop ();
            while (*buf_lect && ! fin_mail ());
        }
    }

    // si mode multipart mixed on va lister les pièces jointes
    if (ctypeorig == MultipMixed)
        liste_pj ();
}



/* lecture et affichage d'un mail de type text/plain (ou text/html) */

void aff_texte ()
{
    // si le mail est au format text/plain
    if (ctypeorig == TextPlain)
        // on ne traitera pas les balises html
        opthb = 0;

    // si texte encodé base 64
    if (encodage_texte == Base64)
        // générer une ligne de séparation avec l'entête
        putchar ('\n');

    // si on doit supprimer la partie html avant <body
    if (opthb & 4)
        // le mémoriser
        avantbody = 1;

    // répéter
    do
    {
        // mise en forme de la dernière ligne lue
        majligne ();

        // si on est dans l'entête html qu'on doit supprimer
        if (avantbody)
            // le faire
            supavantbody ();

        // si option de suppression des balises html
        if (opthb & 2)
            // les supprimer
            filtre_balhtm ();

        // sinon afficher simplement la ligne
        else
            printf ("%s", buf_lect);

        // et lecture de la suivante
        lire_pop ();
    }
    // on s'arrête en fin de mail
    while (! fin_mail ());
}



// indique si le mail a été lu entièrement

int fin_mail ()
{
    return (buf_lect [0] == '.' && buf_lect [1] == '\0');
}



/* Ce qui suit est une adaptation des fonctions de trtsection
   en remplaçant la fonction lire_fmail par lire_pop */


/* avancer jusqu'à la section suivante si
   on n'est pas sur un marqueur de section */


void prochaine_section ()
{
    // tant qu'on n'est pas sur une bordure
    while (! surbordure ())
    {
        // avancer d'une ligne
        lire_pop ();

        // si problème de structure du mail (fin de mail rencontrée)
        if (fin_mail ())
            return;   // sortir
    }

    // si bordure trouvée, lire la ligne suivante
    if (buf_lect [0] == '-')
        lire_pop ();
}



/* mémorise le type, le jeu de caractères et le mode d'encodage de la section */

void recup_infos_section ()
{
    // initialisation : type de section non trouvé
    ctype = 0;

    // tant qu'on est dans l'entête de la section, on va chercher
    // des informations dans toutes les lignes sachant qu'elles
    // peuvent apparaitre dans un ordre quelconque
    while (buf_lect [0])
    {
        // type de la section
        if (start ("Content-Type"))
            ctype = recup_ctype ();

        // mode d'encodage des caractères
        else if (start ("Content-Transfer-Encoding"))
            mem_encodage ();

        // si section multipart
        if (ctype & Multipart)
            // mémoriser la bordure quand on la trouve
            mem_boundary ();
        // sinon
        else
            // récupérer le jeu de caractères utilisé quand on le trouve
            lire_charset ();

        // passer à la ligne suivante
        lire_pop ();
    }
}



// se positionner sur la section texte du mail (si l'on n'y est pas)

void posit_texte ()
{
    posit_section (TextPlain);
}



// se positionner sur la section text/html du mail (si l'on n'y est pas)

void posit_texthtm ()
{
    posit_section (TextHtml);
}



// se positionner (si l'on n'y est pas) sur la section text/plain
// ou text/html du mail, en fonction du paramètre  typetexte

void posit_section (int typetexte)
{
    // tant qu'on n'a pas trouvé la section contenant
    // le texte du mail et non fin de mail
    while (ctype != typetexte && buf_lect [0] != '\0')
    {
        // si une bordure a été mémorisée
        if (nbordures)
            // se positionner sur la section suivante
            prochaine_section ();

        // récupérer les informations sur la section
        recup_infos_section ();
    }

    // message d'erreur si on est arrivé en fin de mail
    if (fin_mail ())
    {
        // Problème de structure d'un mail multi section : fin de mail atteinte
        sprintf (buf_lect, " %s\n", message ("PB_STRUCT_MAIL"));
    }
}



// lister les pièces jointes

void liste_pj ()
{
    int  posjoint;       // position du nom du fichier joint
    char nomjoint [120]; // nom du fichier joint
    int  nbext;          // nombre d'extentions du fichier
    int  derctype;       // pour mémoriser champ content-type de la section
    int  i;              // compteur


    // on ne conserve que la bordure de premier niveau
    // (permet de sauter le texte HTML en mode multipart/alternative)
    nbordures = 1;

    // tant que non fin de mail et pas sorti de la section de premier niveau
    while (nbordures && ! fin_mail ())
    {
        // se positionner sur la prochaine section si on n'y est pas déjà
        prochaine_section ();

        // terminé s'il n'y en a plus
        if (fin_mail ())
           return;

        // réinitialisation type de section
        derctype = 0;

        // chercher et mémoriser le nom du fichier joint s'il y en a un
        do
        {
            // mémoriser les sections message/rfc822 et text/html
            if (start ("Content-Type"))
                derctype = recup_ctype ();

            // chercher si la ligne contient un nom de fichier joint
            majligne ();
            posjoint = posnomfic ();

            // si trouvé hors d'une section text/html
            if (posjoint && derctype != TextHtml)
            {
                // récupérer son nom
                // "-> Fichier joint : "
                strcpy (nomjoint, message ("FICJOINT"));
                i = strlen (nomjoint);
                nbext = 0;

                // convertir les données encodées comme dans une entête
                majlignentete ();

                // recopie du nom de fichier sans déborder du tableau
                while (buf_lect [posjoint] != '"' && buf_lect [posjoint])
                {
                    // sans les (horribles) blancs qu'il pourrait contenir !!!
                    // et sans déborder du tableau
                    if (buf_lect [posjoint] != ' ' && i < sizeof (nomjoint))
                        nomjoint [i++] = buf_lect [posjoint];

                    // passer au caractère suivant
                    posjoint++;

                    // on compte les . (nombre d'extentions) du fichier
                    // à partir du 2ème caractère du nom
                    if (buf_lect [posjoint] == '.')
                        nbext ++;
                }

                // terminer la chaine générée
                if (i + 1 >= sizeof (nomjoint))
                    i = sizeof(nomjoint) - 1;

                nomjoint [i] = '\0';
            }

            // passer à la ligne suivante
            lire_pop ();
        }
        while (! posjoint && buf_lect [0] != '\0' && ! fin_mail ());

        // si fichier joint trouvé hors d'une section text/html
        if (posjoint && derctype != TextHtml)
        {
            // actuellement tous les fichiers joints avec extention peuvent
            // contenir des virus, on accepte quand même les .exe
            if (nbext > 1 || (nbext == 1 && strcmp (nomjoint + i - 4, ".exe\n")
                             != 0 && strcmp (nomjoint + i - 4, ".EXE\n") != 0))
            {
                // se positionner sur la première ligne du contenu du fichier
                while (buf_lect [0] != '\0' && ! fin_mail ())
                    lire_pop ();

                if (! fin_mail ())
                    lire_pop ();

                // si le fichier commence par les caractères MZ
                // (on teste l'encodage base64 correspondant)
                if (buf_lect [0] == 'T' && buf_lect [1] == 'V'
                 && buf_lect [2] >= 'o' && buf_lect [2] <= 'r')
                    // il peut s'agir d'un virus MS-DOS / Windows
                    // "     *** VIRUS ? ***\n"
                    strcpy (nomjoint + i, message ("VIRUS_POSSIBLE"));
            }

            // afficher le nom du fichier joint trouvé
            puts (nomjoint);
        }

        // sinon, si section message/rfc822 trouvée
        else if (derctype == Mesrfc822)
            // la récupérer et la traiter
            recup_rfc822 ();
    }
}



/* récupère le contenu d'une section rfc822 */

void recup_rfc822 ()
{
    char fichtmp [18];  // fichier pour mémoriser le contenu de la section
    FILE *ftmp;         // descripteur associé à ce fichier
    char commande [28]; // commande à exécuter pour afficher cette section


    // fabriquer un nom de fichier de travail
    sprintf (fichtmp, "/tmp/mail-%d", getpid ());

    // l'ouvrir en écriture
    ftmp = fopen (fichtmp, "w");

    // terminé si problème d'accès au fichier
    if (! ftmp)
    {
        // "Impossible de récupérer un mail en pièce jointe"
        affiche_err ("IMPOS_RECUP-MAILJOINT");
        return;
    }

    // afficher une ligne de séparation
    putchar ('\n');

    // lecture de la première ligne non vide de la section
    lire_pop ();

    // tant que non fin de section
    while (! surbordure () && ! fin_mail ())
    {
        // mémoriser la ligne
        fputs (buf_lect, ftmp);
        fputc ('\n', ftmp);

        // et lire la suivante
        lire_pop ();
    }

    // la section a été entièrement récupérée
    fclose (ftmp);

    // afficher cette section
    sprintf (commande, "voirfmail %s", fichtmp);
    system (commande);

    // on peut détruire le fichier de travail
    unlink (fichtmp);
}