/*
    Fichier sjmails.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 les caractéristique de chaque mail
    en attente de téléchargement (ou de destruction).

    Il y a 3 formats d'affichage.

    Le format détaillé (option -d) donne plusieurs lignes d'information :
    - date et heure d'envoi
    - expéditeur et destinataire(s) + éventuelle adresse de réponse
    - sujet du mail
    - format du message (texte, html ...)
    - éventuellement premières lignes du message

    Le format simplifié (option -s) affiche une ligne par message :
      nom expéditeur     sujet     date et heure d'envoi

    La variante du format simplifié (option -e) affiche une ligne par message :
      adresse expéditeur     sujet     date et heure d'envoi

    Un fichier de configuration est utilisé pour se connecter à la
    boite aux lettres.
*/


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

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


// options d'affichage
#define opt_s           0
#define opt_e           1
#define opt_d           2


/* prototypes */
void aff_detail (int numes);
void aff_resume (int numes, int lg_num);


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


int nblig_mes;    // nombre de lignes à afficher pour chaque message
int opt_aff;      // option affichage (simple = -s et -e, détaillé = -d)
int opt_inv = 0;  // option inversion de l'ordre d'affichage des mails
int test_xorig;   // traitement ou non du champ X-Original-From

// largeur des champs affichés en mode simplifié (options -s et -e)
int sz_nom, sz_adr, sz_avantdate;


/* programme principal */

int main (int nbarg, char *varg[])
{
    FILE *fconf;           // descripteur du fichier de configuration
    int  numes, nbmes;     // numéro du mail courant et nombre de mails
    int  lg_num;           // nombre caractères pour afficher tous numéros mails
    int  sup_nbmes;        // variable pour calculer lg_num
    char format_numes [6]; // format affichage numéros mails
    int  largeur;          // largeur de l'affichage si option -s ou -e
    char *varenv_xorig;    // variable d'environnement libremail_xorig


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

    // se positionner sur le premier argument de sjmails
    varg ++;
    nbarg --;

    // initialisation options de fonctionnement
    opt_aff = opt_s;
    nblig_mes = 0;
    largeur = 0;   // la valeur sera fixée plus tard

    // récupération des options de fonctionnement éventuelles
    while (nbarg > 0 && **varg == '-' && varg [0][2] == '\0')
    {
        switch (varg [0][1])
        {
            // option affichage détaillé
            case 'd' : opt_aff = opt_d;

                       // recupération du nombre de lignes s'il existe
                       nblig_mes = atoi (varg [1]);

                       // dans ce cas, décompter l'argument traité
                       if (nblig_mes || *varg [1] == '0')
                       {
                           varg ++;
                           nbarg --;
                       }

                       break;

            // option affichage simplifié
            case 's' : opt_aff = opt_s;
                       break;

            // option affichage simplifié avec adresse expéditeur
            case 'e' : opt_aff = opt_e;
                       break;

            // option largeur de l'affichage simplifié
            case 'w' : // passer à l'argument suivant
                       varg ++;
                       nbarg --;

                       // récupération de la largeur de l'affichage
                       largeur = atoi (*varg);
                       break;

            // option inversion de l'ordre d'affichage
            case 'i' :
            case 'r' : opt_inv = 1;
                       break;

            default  : // "Option %s incorrecte"
                       aff_err_arg ("ERR_OPTION", *varg);

                       // rappeller la syntaxe de la commande
                       psyntaxe ("SYNT_SJFMAILS");
        }

        // décompter l'argument traité
        varg ++;
        nbarg --;
    }

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

        if (fconf)
        {
            // connexion sur le compte mail du serveur pop
            if (connect_pop (fconf))
            {
                // récupération du nombre de mails
                nbmes = nbmails ();

                // déterminer si on va traiter les champs X-Original-From
                varenv_xorig = getenv ("libremail_xorig");

                if (varenv_xorig)
                    test_xorig = (tolower (*varenv_xorig) != 'n');
                else
                    test_xorig = 1;

                // affichage détaillé ou résumé de l'entête des mails
                if (opt_aff == opt_d)
                {
                    // l'ordre d'affichage dépend de l'option -i éventuelle
                    if (opt_inv)
                    {
                        // option -i : on affiche les mails
                        // du plus récent au plus ancien
                        for (numes = nbmes; numes >= 1; numes--)
                            aff_detail (numes);
                    }
                    else
                    {
                        // pas d'option : on affiche les mails
                        // dans leur ordre d'arrivée sur le serveur
                        for (numes = 1; numes <= nbmes; numes++)
                            aff_detail (numes);
                    }
                }
                else
                {
                    // auparavant calcul nbre min de car pour n° messages
                    lg_num = 1;
                    sup_nbmes = 10;

                    while (nbmes >= sup_nbmes)
                    {
                        lg_num ++;
                        sup_nbmes *= 10;
                    }

                    // et fabrication de la chaine de format d'affichage
                    sprintf (format_numes, "%%%dd ", lg_num++);

                    // valeur par défaut de la largeur
                    // d'affichage si elle n'a pas été fixée
                    if (! largeur)
                        largeur = 80;

                    // calcul de la taille des différentes zones d'affichage
                    sz_nom = (largeur / 4) + lg_num;
                    sz_adr = (largeur / 3) + lg_num - 2;
                    sz_avantdate = largeur - 13;

                    // affichage résumé des entêtes de mails
                    // l'ordre d'affichage dépend de l'option -i éventuelle
                    if (opt_inv)
                    {
                        // option -i : on affiche les mails
                        // du plus récent au plus ancien
                        for (numes = nbmes; numes >= 1; numes--)
                        {
                            printf (format_numes, numes);
                            aff_resume (numes, lg_num);
                        }
                    }
                    else
                    {
                        // pas d'option : on affiche les mails
                        // dans leur ordre d'arrivée sur le serveur
                        for (numes = 1; numes <= nbmes; numes++)
                        {
                            printf (format_numes, numes);
                            aff_resume (numes, lg_num);
                        }
                    }
                }

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

            // on n'a plus besoin du fichier de configuration
            fclose (fconf);
        }
    }
    else
        // "Syntaxe : %s [-(s|e|d [nb_lignes_messages])]
        //               [-w largeur] [-i] fichier_configuration"
        psyntaxe ("SYNT_SJMAILS");

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



/* lit l'entête d'un message et affiche certaines lignes */

void aff_detail (int numes)
{
    char bufw [120]; // buffer d'envoi d'une requête
    // buffers pour affichage ordonné des caractéristiques du message
    char bufFrom [120], bufTo [120], bufCc [120], bufReply [120],
         bufSubject [120], bufContent [120], bufXorig [120];
    int i, j;       // simples compteurs


    // initialisation
    bufFrom [0]    = '\0';
    bufTo [0]      = '\0';
    bufCc [0]      = '\0';
    bufReply [0]   = '\0';
    bufSubject [0] = '\0';
    bufContent [0] = '\0';
    bufXorig [0]   = '\0';

    // demande de lecture de l'entête du message
    if (nblig_mes)
        sprintf (bufw, "TOP %d %d", numes, nblig_mes);
    else
        sprintf (bufw, "TOP %d 1", numes);

    env_pop (bufw);

    // "Message %d\n"
    putchar ('\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
    {
        // si c'est le champ sujet
        if (start ("Subject"))
        {
            // mémoriser la ligne
            memconvbuf (bufSubject);

            // on va tester la ligne qui suit
            lire_pop ();
            majlignentete ();

            // si c'est encore une ligne du sujet
            if (*buf_lect == ' ' || *buf_lect == '\t')
            {
                // initialisation compteurs
                i = 1;
                j = strlen (bufSubject);

                // compléter le sujet du mail
                while (buf_lect [i] && j < sizeof (bufSubject) - 1)
                    bufSubject [j++] = buf_lect [i++];

                // terminer la chaine de caractères
                bufSubject [j] = '\0';
            }
        }

        // mémorisation des autres 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") && test_xorig)
            memconvbuf (bufXorig);
        else if (start ("To"))
            memconvbuf (bufTo);
        else if (start ("Cc"))
            memconvbuf (bufCc);
        else if (start ("Reply-To"))
            memconvbuf (bufReply);
        else if (start ("Content-Type"))
        {
            membuf (bufContent);

            if (recup_ctype () == TextPlain)
                lire_charset ();
        }
        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 des caractéristiques mémorisées
    if (bufFrom [0])
        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);
    if (bufContent [0])
        puts (bufContent);

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

    // lecture de la fin du message jusqu'a une ligne limitée a un .
    while (buf_lect [0] != '.' || buf_lect [1] != '\0')
    {
        // affichage éventuel des dernières lignes lues
        if (nblig_mes)
        {
            // on prend en compte les nouveaux champs Content éventuels
            if (start ("Content-Type") && recup_ctype () == TextPlain)
                lire_charset ();
            else if (start ("Content-Transfer-Encoding"))
                mem_encodage ();

            // convertir la ligne lue et l'afficher
            majligne ();
            printf ("%s", buf_lect);
        }

        // et lecture de la suivante
        lire_pop ();
    }
}



/* lit l'entête d'un message et affiche un résumé sur une ligne */

void aff_resume (int numes, int lg_num)
{
    char bufw [120]; // buffer d'envoi d'une requête
    // buffers pour affichage ordonné des caractéristiques du message
    char bufFrom [120], bufSubject [120], bufDate [120];
    // 1ère et éventuellement 3ème lettre des noms de mois
    static char *mois [] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
                             "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
    int sz_champ1;  // nombre max de caractères du champ expéditeur
    int i, j;       // simples compteurs


    // initialisation
    bufFrom [0]    = '\0';
    bufSubject [0] = '\0';
    bufDate [0]    = '\0';

    // demande de lecture de l'entête du message
    if (nblig_mes)
        sprintf (bufw, "TOP %d %d", numes, nblig_mes);
    else
        sprintf (bufw, "TOP %d 1", numes);

    env_pop (bufw);

    // 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
    {
        // si c'est le champ sujet
        if (start ("Subject"))
        {
            // mémoriser la ligne
            memconvbuf (bufSubject);

            // on va tester la ligne qui suit
            lire_pop ();
            majlignentete ();

            // si c'est encore une ligne du sujet
            if (*buf_lect == ' ' || *buf_lect == '\t')
            {
                // initialisation compteurs
                i = 1;
                j = strlen (bufSubject);

                // compléter le sujet du mail
                while (buf_lect [i] && j < sizeof (bufSubject) - 1)
                    bufSubject [j++] = buf_lect [i++];

                // terminer la chaine de caractères
                bufSubject [j] = '\0';
            }
        }

        // mémorisation des autres caractéristiques importantes
        if (start ("Date"))
            membuf (bufDate);
        else if (start ("From") && ! *bufFrom)
            memconvbuf (bufFrom);
        else if (start ("X-Original-From") && test_xorig)
            memconvbuf (bufFrom);

        // lire la ligne suivante de l'entête du message
        lire_pop ();
    }
    while (buf_lect [0] != '.' || buf_lect [1] != '\0');

    // initialisation : on est en début de ligne
    i = lg_num;

    // affichage du nom ou de l'adresse de l'expéditeur du message
    if (*bufFrom)
    {
        if (tolower (*bufFrom) == 'x')
            j = 16;
        else
            j = 6;

        // option -s : affichage du nom de l'expéditeur
        if (opt_aff == opt_s)
        {
            while (bufFrom [j] == '"' || bufFrom [j] == ' ')
                j++;

            putchar (bufFrom [j++]);

            // si affichage avec un jeu de caractères ISO-8859
            if (! util_utf8 ())
            {
                // on affichera sz_nom caractères maximum
                while (++i < sz_nom && bufFrom [j] && bufFrom [j] != '<'
                             && bufFrom [j] != '"' && bufFrom [j] != '=')
                    putchar (bufFrom [j++]);
            }
            // sinon (affichage avec le jeu de caractères UTF-8)
            else
            {
                // on affichera toujours sz_nom caractères maximum
                while (++i < sz_nom && bufFrom [j] && bufFrom [j] != '<'
                             && bufFrom [j] != '"' && bufFrom [j] != '=')
                {
                    // mais certains caractères ne seront pas comptés

                    /* il serait plus logique de ne pas compter les caractères
                       entre 0x80 et 0xBF, mais certains caractères UTF8 codés
                       sur 3 (ou 4 ?) octets s'affichent en largeur double, ce
                       qui décale les autres colonnes et génère un saut de ligne
                       intempestif après la date */

                    if ((bufFrom [j] & 0xC0) == 0xC0)
                        i--;

                    putchar (bufFrom [j++]);
                }
            }
        }
        // option -e : affichage de l'adresse de l'expéditeur
        else
        {
            // recherche de la partie adresse de l'expéditeur
            while (bufFrom [j] != '@' && bufFrom [j])
                j++;

            // positionnement au début de cette adresse
            while (bufFrom [j] != '<' && bufFrom [j] != ' ')
                j--;

            j++;

            // copie de l'adresse
            putchar (tolower (bufFrom [j++]));

            // pour ne pas tronquer l'adresse, on
            // accepte de déborder sur le champ sujet
            while (++i < sz_avantdate && bufFrom [j]
                                      && bufFrom [j] != '>')
                putchar (tolower (bufFrom [j++]));
        }
    }

    // détermination nombre de caractères réservé au champ expéditeur
    if (opt_aff == opt_s)
        sz_champ1 = sz_nom;
    else
        sz_champ1 = sz_adr;

    // compléter ce champ par des blancs
    while (i++ <= sz_champ1)
        putchar (' ');

    // affichage du sujet du message
    if (*bufSubject)
    {
        j = 9;

        // on saute les blancs inutiles
        while (bufSubject [j] == ' ')
            j++;

        // si affichage avec un jeu de caractères ISO-8859
        if (! util_utf8 ())
        {
            // on affichera jusqu'à sz_avantdate caractères maximum
            while (++i < sz_avantdate && bufSubject [j])
            {
                // si présence d'une tabulation
                if (bufSubject [j] == '\t')
                {
                    // afficher un blanc pour une meilleure mise en page
                    putchar (' ');
                    j++;
                }
                else
                    // sinon affichage du caractère
                    putchar (bufSubject [j++]);
            }
        }
        // sinon (affichage avec le jeu de caractères UTF-8)
        else
        {
            // on affichera jusqu'à sz_avantdate caractères maximum
            while (++i < sz_avantdate && bufSubject [j])
            {
                // si présence d'une tabulation
                if (bufSubject [j] == '\t')
                {
                    // afficher un blanc pour une meilleure mise en page
                    putchar (' ');
                    j++;
                }
                // sinon affichage du caractère
                else
                {
                    // mais certains caractères ne seront pas comptés
                    // même remarque que pour bufFrom
                    if ((bufSubject [j] & 0xC0) == 0xC0)
                        i--;

                    putchar (bufSubject [j++]);
                }
            }
        }
    }
    else
        i++;    // pour corriger l'alignement

    // complété par des blancs
    while (i++ <= sz_avantdate)
        putchar (' ');

    // affichage de la date et de l'heure du message
    if (*bufDate)
    {
        j = 6;

        // jour du mois
        while (bufDate [j] < '0' || bufDate [j] > '9')
            j++;

        printf ("%02d", atoi (bufDate + j));

        while (bufDate [j] > ' ')
            j++;

        j++;

        // mois
        i = 0;

        while (i < 12 && (toupper (bufDate [j]) != mois [i][0]
                     || tolower (bufDate [j+1]) != mois [i][1]
                     || tolower (bufDate [j+2]) != mois [i][2]))
            i++;

        printf ("/%02d/", i + 1);

        // année sur 2 chiffres
        while (bufDate [j] < '0' || bufDate [j] > '9')
            j++;

        printf ("%02d", atoi (bufDate + j) % 100);

        while (bufDate [j] > ' ')
            j++;

        // heure, sans les secondes ni le fuseau horaire
        for (i = 0; i < 6; i++)
            putchar (bufDate [j++]);
    }

    // passage à la ligne
    putchar ('\n');
}