/*
    Fichier trtligne.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


    Bibliothèque de fonctions permettant de :

    - mémoriser le mode d'encodage et le jeu de caractères du message
    - convertir les accents et caractères spéciaux codés sur 7 bits
      ou/et utilisant un jeu de caractères non ISO-8859 dans le message
    - récupérer un nom de fichier joint
*/


#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include "buflect.h"
#include "encodage.h"
#include "trtentete.h"
#include "trtligne.h"
#include "messages.h"    // pour fonction  conv_iso_utf8 (...)
#include "base64.h"


/* prototypes de fonctions locales à ce source */

void sup_quoted ();
void conv_isomac ();
void conv_utf8_iso ();


/* variable globale au source */

static int charset_texte;      // jeu de caractères accentués du texte



/* retourne la valeur du champ Content-Type */

int recup_ctype ()
{
    int i;   // numéro de caractère dans buf_lect


    // se positionner après le nom de champ
    i = 13;

    // sauter les blancs
    while (buf_lect [i] == ' ')
        i++;

    // détection du type de contenu
    if (tolower (buf_lect [i]) == 't')
    {
        // discrimination entre text/plain et text/html
        if (tolower (buf_lect [i + 5]) == 'p')
            return (TextPlain);
        else if (tolower (buf_lect [i + 5]) == 'h')
            return (TextHtml);
        else
            return (AutreType);
    }
    else if (tolower (buf_lect [i]) == 'm')
    {
        // type message ou type multipart
        if (tolower (buf_lect [i + 1]) == 'e')
            // message/rfc822 (traité comme section texte)
            return (Mesrfc822);

        // discrimination entre les différents mode multipart
        else if (tolower (buf_lect [i + 10]) == 'a')
            // multipart/alternative
            return (MultipAlter);

        else if (tolower (buf_lect [i + 10]) == 'm')
            // multipart/mixed
            return (MultipMixed);

        else if (tolower (buf_lect [i + 10]) == 'r')
        {
            if (tolower (buf_lect [i + 12]) == 'l')
                // multipart/related
                return (MultipRel);
            else if (tolower (buf_lect [i + 12]) == 'p')
                // multipart/report
                return (MultipRep);
        }
        else
            return (Multipart);
    }
    else
        return (AutreType);
}



/* mémorise dans  encodage_texte  la valeur
   du champ Content-Transfer-Encoding */


void mem_encodage ()
{
    int i;   // numéro de caractère dans buf_lect


    // se positionner après le nom de champ
    i = 26;

    // sauter les blancs
    while (buf_lect [i] == ' ')
        i++;

    // détection de l'encodage (simplifiée, on analyse un ou deux caractère)
    if (tolower (buf_lect [i]) == 'q')
        encodage_texte = QuotedPrint;
    // l'encodage peu fréquent "binary" ne doit pas être traité comme "base64"
    else if (tolower (buf_lect [i]) == 'b' && tolower (buf_lect [i+1]) == 'a')
        encodage_texte = Base64;
    else
        encodage_texte = xBits;
}



/* mémorise dans  charset_texte  le jeu de
   caractères utilisé dans le texte du mail
   retourne 1 si champ charset trouvé dans la ligne, 0 sinon */


int lire_charset ()
{
    int i;  // compteur


    // initialisation
    i = 1;

    // chercher le mot  charset  dans la ligne et sortir s'il n'y est pas
    while (tolower (buf_lect [i]) != 'c' || tolower (buf_lect [i+1]) != 'h')
        if (! buf_lect [++i])
            return (0);

    // se positionner après le caractère  =
    while (buf_lect [i++] != '=')
        if (! buf_lect [i])
            return (0);

    // sauter également les blancs s'il y en a
    while (buf_lect [i] == ' ')
        i++;

    // sauter également le  "  s'il y est
    if (buf_lect [i] == '"')
        i++;

    // détection du charset (simplifiée, on analyse un caractère)
    if (tolower (buf_lect [i]) == 'i')
    {
        // jeu de caractère ISO
        charset_texte = Iso8859;

        // vérifier si variante Mac
        while (tolower (buf_lect [i]) != 'x' || buf_lect [i+1] != '-'
                                    || tolower (buf_lect [i+2]) != 'm')
            if (! buf_lect [++i])
                return (1);

        // variante Mac trouvée
        charset_texte = IsoMac;
    }
    else if (tolower (buf_lect [i]) == 'u')
        charset_texte = Utf8;
    else
        charset_texte = xCharset;

    // on a trouvé le charset
    return (1);
}



/* convertit la ligne lue en interprétant les caractères spéciaux */

void majligne ()
{
    int i;  // compteur


    // suppression du . éventuel en première colonne
    if (buf_lect [0] == '.')
    {
        i = 0;

        do
            buf_lect [i] = buf_lect [i + 1];
        while (buf_lect [i++] != '\0');
    }

    // suppression de l'encodage quoted-printable
    if (encodage_texte == QuotedPrint)
        sup_quoted ();

    // et de l'encodage base64
    else if (encodage_texte == Base64)
    {
        i = decode64 (buf_lect);

        // la génération d'un passage à la ligne jusqu'à la version
        // 2.2.4 est supprimée : si elle suppléait à un bug, elle
        // perturbait l'affichage des mails encodés base64.

        // fin de ligne si le texte encodé base64 est terminé
        if (i == 0)
            *buf_lect = '\n';
    }

    // sinon, on rajoute juste un passage à la ligne
    else
    {
        i = strlen (buf_lect);
        buf_lect [i++] = '\n';

        // terminaison de la chaine de caractères
        buf_lect [i] = '\0';
    }

    // remplacement des caractère de controle
    // dont certains déprogramment la visu
    conv_carcontrole ();

    // Dans les anciennes versions, on convertissait ici certaines séquences
    // HTML qui NE DEVRAIT JAMAIS se trouver dans un TEXTE de mail correctement
    // généré (mais on en trouve !!!!)
    // Appel de l'instruction supprimé pour cohérence entre les options -h et -H
    // conv_carhtm ();

    // conversion des jeux de caractères non ISO
    // traitement des jeux de caractères

    // si mail encodé UTF-8
    if (charset_texte == Utf8)
    {
        // et qu'on utilise le jeu de caractères ISO-8859
        if (! util_utf8 ())
        {
            // conversion du jeu de caractères UTF8 en ISO-8859
            conv_utf8_iso ();

            // Normalement, le besoin de conversion des caractères
            // Mac devrait être signalée dans le charset.
            // Toutefois, il est préférable faire la conversion systématiquement
            // Cette fonction remplace aussi le caractère A0 (hexa) par un blanc
            conv_isomac ();
        }
    }
    // sinon le mail est supposé encodé ISO-8859
    else
    {
        // même raison de l'appel de cette fonction que précédemment
        conv_isomac ();

        // si on utilise le jeu de caractères UTF-8
        if (util_utf8 ())
            // conversion du jeu de caractères ISO-8859 en UTF8
            conv_iso_utf8 (buf_lect);   // fonction dans messages.c
    }
}



/* suppression de l'encodage quoted-printable */

void sup_quoted ()
{
    int carcode;  // caractère correspondant à un codage quoted-printable
    int i, j;     // compteurs


    // initialisation
    i = 0;
    j = 0;

    // exploration de toute la ligne
    while (buf_lect [i] != '\0')
    {
        // si caractère quoted printable à convertir
        if (buf_lect [i] == '=' &&
             ((buf_lect [i+1] >= '0' && buf_lect [i+1] <= '9') ||
              (buf_lect [i+1] >= 'A' && buf_lect [i+1] <= 'F') ||
              (buf_lect [i+1] >= 'a' && buf_lect [i+1] <= 'f')))
               // pour les maillers qui mettent les lettres hexa en minuscules !
        {
            // on décode le caractère correspondant
            carcode = hexa (buf_lect + ++i);

            // si ce n'est pas un caractère de controle ou si c'est un LF
            if (carcode > 0x1F || carcode == '\n')
                // le mémoriser
                // Remarque : la conversion d'un LF peut être une source de
                //            problème d'affichage notamment avec vmailfic
                buf_lect [j++] = carcode;

            // sinon, si caractère de controle autre que CR
            else if (carcode != '\r')
                // mémoriser un blanc pour limiter les problèmes d'affichage
                buf_lect [j++] = ' ';

            // se positionner sur le caractère suivant
            i = i + 2;
        }
        // sinon on conserve le caractère courant et on passe au suivant
        else
            buf_lect [j++] = buf_lect [i++];
    }

    // traitement de la fusion éventuelle de 2 lignes
    if (buf_lect [i - 1] == '=')
        j--;
    else
        buf_lect [j++] = '\n';

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



/* conversion des caractères spéciaux sur Mac */

void conv_isomac ()
{
    int i;   // compteur


    for (i = 0; buf_lect [i] != '\0'; i++)
    {
        switch (buf_lect [i])
        {
            case 0x92 :
            case 0xB4 : buf_lect [i] = '\'';
                        break;
            case 0x93 : buf_lect [i] = '«';
                        break;
            case 0x94 : buf_lect [i] = '»';
                        break;
            case 0x9C : buf_lect [i] = '½';
                        break;
            case 0xA0 : buf_lect [i] = ' ';
            default   : break;
        }
    }
}



/* conversion du jeu de caractères UTF8 en ISO-8859 */

void conv_utf8_iso ()
{
    /* indique que le caractère qui suit le à devra être converti
       on utilise une variable statique pour garder l'info en mémoire dans le
       cas où le à et le caractère qui suit seraient sur 2 lignes distinctes */

    static int decalsuiv = 0;

    /* idem si â , suivi du caractère 0x80, suivi d'un 3ème */
    static int codespe = 0;

    // tableau de conversion des caractères de la série E2809x
    char E2809x [16] = {'-',  '-',  '-',  '-',  '-',  '-',  '|',  '_',
                        '\'', '\'',  ',',  '`',  '"',  '"',  '"',  '"'};

    int i, j;   // compteurs


    // initialisation
    i = 0;
    j = 0;

    do
    {
        // si aucun traitement d'encodage sur 3 caractères en cours
        if (!codespe)
        {
            switch (buf_lect [i])
            {
                // si caractère à , on convertira le caractère qui le suit
                case 0xC3 : decalsuiv = 1;
                case 0xC2 : break; // on saute le caractère  ou Ã

                // si caractère Å suivi du caractère 93h
                case 0xC5 : switch (buf_lect [i+1])
                            {
                                            // on convertit le résultat en ¼
                                case 0x92 : buf_lect [j++] = 0xBC;
                                            i++;
                                            break;

                                            // on convertit le résultat en ½
                                case 0x93 : buf_lect [j++] = 0xBD;
                                            i++;
                                            break;

                                // obligatoire pour éviter gros bug d'affichage!
                                            // on convertit le résultat en s
                                case 0x9B : buf_lect [j++] = 's';
                                            i++;
                                            break;

                                default   : buf_lect [j++] = buf_lect [i];
                            }
                            break;


                // si caractère â , mémorisation encodage sur 3 caractères
                case 0xE2 : codespe = 1;
                            break;

                // pour tous les autres caractères, 2 cas possibles
                default   : if (decalsuiv)
                            {
                                // conversion du caractère qui suit le Ã
                                buf_lect [j++] = buf_lect [i] | 0x40;
                                decalsuiv = 0;
                            }
                            else
                                // ou simple copie du caractère courant
                                buf_lect [j++] = buf_lect [i];
            }
        }
        // sinon, si on est sur le 2ème caractère d'un encodage sur 3 car
        else if (codespe == 1)
        {
            // cas général (le 3ème caractère déterminera le résultat)
            if (buf_lect [i] == 0x80)
                codespe ++;

            // cas particulier du symbole euro
            else if (buf_lect [i] == 0x82 && buf_lect [i+1] == 0xAC)
            {
                buf_lect [j++] = 0xA4;
                i++;
                codespe = 0;
            }

            // encodage non reconnu
            else
            {
                buf_lect [j++] = 0xE2;
                buf_lect [j++] = buf_lect [i];
                codespe = 0;
            }
        }
        // traitement du dernier caractère d'un encodage sur 3 car
        else if (codespe == 2)
        {
            if (0x90 <= buf_lect [i] && buf_lect [i] <= 0x9F)
                 buf_lect [j++] = E2809x [buf_lect [i] & 0x0F];

            // pointillés
            else if (buf_lect [i] == 0xA6)
            {
                buf_lect [j++] = '.';

               // on ne les met que si c'est possible sans
               // écraser les caractères non encore traités
               if (i > j)
                   buf_lect [j++] = '.';

               if (i > j)
                   buf_lect [j++] = '.';
            }
            // sinon, cas d'une séquence encodée non répertoriée
            else
            {
                if (i > j + 1)
                {
                    // même précaution que pour pointillés
                    buf_lect [j++] = 0xE2;
                    buf_lect [j++] = 0x80;
                }

                buf_lect [j++] = buf_lect [i];
            }

            codespe = 0;
        }

        // si fin de ligne, on ne remet pas à jour decalsuiv et codespe
        if (buf_lect [++i] == 0)
            buf_lect [j++] = '\0';
    }
    while (buf_lect [i] != 0);
}



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

void supavantbody ()
{
    static char *balise = "<body";
    static int  posbal  = 0;
    int poslig = 0;
    int i = 0;


    // tant que non fin de ligne et <body non trouvé
    while (buf_lect [poslig] && avantbody)
    {
        // tant que le caractère courant en minuscules
        // correspond à celui de la balise
        while ((buf_lect [poslig] | 0x20) == balise [posbal])
        {
            // passer au caractère suivant des deux cotés
            poslig ++;
            posbal ++;
        }

        // si balise non trouvée
        if (balise [posbal])
        {
            // si on est dans une autre balise
            if (posbal)
                // revenir au début de la balise à chercher
                posbal = 0;
            // sinon
            else
                // avancer d'un caractère dans la ligne
                poslig ++;
        }
        // sinon
        else
            // balise trouvée
            avantbody = 0;
    }

    // cas particulier : <body juste en fin de ligne
    if (avantbody && (balise [posbal] == 0))
        avantbody = 0;

    // si balise non trouvée
    if (avantbody)
        // effacer la ligne
        *buf_lect = '\0';
    // sinon
    else
    {
        // si balise entièrement dans la ligne
        if (poslig > posbal)
        {
            // effacer dans la ligne tout ce qui précède cette balise
            poslig = poslig - posbal;
            i = 0;

            do
                buf_lect [i++] = buf_lect [poslig];
            while (buf_lect [poslig++]);
        }
        // sinon si balise à cheval sur 2 lignes du code source
        else if (poslig < posbal)
        {
            // chercher le décalage
            i = posbal - poslig;

            // aller en fin de ligne
            while (buf_lect [poslig])
                poslig ++;

            // décaler la ligne de i caractères en remontant depuis la fin
            i = i + poslig;

            while (poslig)
                buf_lect [i --] = buf_lect [poslig --];

            // recopier le début de la balise
            do
                buf_lect [i] = balise [i];
            while (i --);
        }

        // réinitialiser posbal pour nouvelle recherche dans vmailfic
        posbal = 0;
    }
}



/* supprimer les balises HTML et leur contenu */

void sup_balhtm ()
{
    static int dansbal = 0;
    int i, j;


    // initialisation
    i = 0;
    j = 0;

    // pour chaque caractère de la ligne
    while (buf_lect [i])
    {
        // si caractère <
        if (buf_lect [i] == '<')
        {
            // on rentre dans une balise HTML
            dansbal = 1;

            // si la balise '<' est précédée de blancs
            if (j > 0 && buf_lect [j - 1] == ' ')
            {
                // les supprimer
                do
                    j--;
                while (buf_lect [j] == ' ' && j > 0);

                // sauf un s'il n'est pas en début de ligne
                if (buf_lect [j] != ' ')
                    j++;
            }
        }

        // si on n'est pas dans un balise HTML
        if (! dansbal)
            // on affichera le caractère courant
            buf_lect [j++] = buf_lect [i];

        // si caractère >
        if (buf_lect [i] == '>')
            // on sort d'une balise HTML
            dansbal = 0;

        // passer au caractère suivant
        i++;
    }

    // terminer la ligne si nécessaire
    buf_lect [j] = '\0';
}



/* réduit à un les espaces multiples dans un texte htlm */

void sup_multiblancs ()
{
    int i, j;


    // initialisation
    i = 0;
    j = 0;

    // pour chaque caractère de la ligne
    while (buf_lect [i])
    {
        // conserver ce caractère
        buf_lect [j++] = buf_lect [i];

        // si c'est un espace
        if (buf_lect [i] == ' ')
        {
            // sauter les espaces qui le suivent
            do
                i++;
            while (buf_lect [i] == ' ');
        }
        // sinon passer au caractère suivant
        else
            i++;
    }

    // supprimer les blancs en fin de ligne
    while (j && buf_lect [j] == ' ')
        j--;

    // terminer la ligne si nécessaire
    buf_lect [j] = '\0';
}



/* convertir les caractères HTML, par exemple les &#8217; en apostrophes */

void conv_carhtm ()
{
    // caractères de la forme &#nombre; (avec un nombre > à 255)
    int  valeurcar [] = {339, 8211, 8216, 8217, 0};  // valeur du caractère
    char carequiv []  = "½-''";  // caractère équivalent

    // caractères symbolisés par une chaine commençant par & et finissant par ;
    char *carnomme [] = {" nbsp", "<lt", ">gt", "&amp", "'quot", "'rsquo",
                         "'apos", "¤euro", "àagrave", "âacirc", "çccedil",
                         "éeacute", "èegrave", "êecirc", "îicirc", "ôocirc",
                         "ùugrave", "ûucirc", "°deg", NULL};

    char cartrouve; // caractère trouvé à partir de carnomme

    int trouve;  // indique si on a trouvé un caractère nommé
    int choix;   // numéro du caractère nommé en cours de test
    int val;     // valeur numérique après &#
    int depl;    // numéro du caractère nommé testé
    int i, j;    // position dans buf_lect en lecture et en écriture


    // initialisation
    i = 0;
    j = 0;

    do
    {
        // si on trouve les caractères & et on traite tous les caractère nommés
        // ou si on trouve les caractères &# à la suite
        if (buf_lect [i] == '&')
        {
            // si ce qui suit risque d'être le codage d'un nombre
            if (buf_lect [i+1] == '#')
            {
                depl = 2;
                val = 0;

                // extraire ce nombre
                while (buf_lect [i + depl] >= '0' && buf_lect [i + depl] <= '9')
                    val = (val * 10) + (buf_lect [i + depl++] & 0x0F);

                // si on est bien tombé sur un nombre
                if (buf_lect [i + depl] == ';')
                {
                    // et que ce nombre tient sur un octet
                    if (val <= 255)
                    {
                        // prendre en compte le caractère équivalent
                        buf_lect [j++] = val;

                        // et sauter la taille de la chaine analysée
                        i = i + depl;
                    }
                    // sinon
                    else
                    {
                        choix = 0;

                        // chercher si le nombre estmémorise dans valeurcar
                        while (valeurcar [choix] && val != valeurcar [choix])
                            choix ++;

                        // s'il l'est
                        if (valeurcar [choix])
                        {
                            // récupérer le caractère équivalent
                            buf_lect [j++] = carequiv [choix];

                            // et sauter la taille de la chaine analysée
                            i = i + depl;
                        }
                        // sinon
                        else
                            // simple copie du caractère courant
                            buf_lect [j++] = '&';
                    }
                }
                // sinon
                else
                    // simple copie du caractère courant
                    buf_lect [j++] = '&';
            }
            // sinon, caractère nommé
            else
            {
                // initialisation
                trouve = 0;
                choix = 0;

                // chercher un éventuel caractère nommé
                while (!trouve && carnomme [choix])
                {
                    // on vérifie caractère par caractère
                    depl = 1;

                    while ((buf_lect [i+depl] | 0x20) == carnomme [choix][depl])
                        depl ++;

                    // si caractère nommé trouvé
                    if (buf_lect [i + depl] == ';' && !carnomme [choix][depl])
                        // mémoriser cette information
                        trouve = 1;
                    else
                        // sinon, essayer avec le caractère nommé suivant
                        choix ++;
                }

                // si caractère nommé trouvé
                if (trouve)
                {
                    // sélectionner le caractère trouvé
                    cartrouve = carnomme [choix][0];

                    // si on trouvé un caractère ASCII ou on
                    // travaille avec un jeu de caractères ISO
                    if ((cartrouve & 0x80) == 0 || ! util_utf8 ())
                    {
                        // remplacer le code ½par le caractère adéquat
                        buf_lect [j++] = cartrouve;
                    }
                    // sinon (on est en UTF-8) si caractère euro
                    else if (cartrouve == 0xA4)
                    {
                        // générer la séquence UTF-8 adéquate
                        buf_lect [j++] = 0xE2;
                        buf_lect [j++] = 0x82;
                        buf_lect [j++] = 0xAC;
                    }
                    // sinon (autre caractère UTF-8 plus facile à générer)
                    else
                    {
                        // générer la séquence UTF-8 adéquate
                        buf_lect [j++] = 0xC3;
                        buf_lect [j++] = cartrouve - 0x40;
                    }

                    // et sauter la taille du caractère nommé
                    i = i + depl;
                }
                // sinon
                else
                    // simple copie du caractère courant
                    buf_lect [j++] = '&';
            }
        }
        // sinon
        else
            // simple copie du caractère courant
            buf_lect [j++] = buf_lect [i];
    }
    // on fait ce traitement jusqu'à la fin de la ligne
    while (buf_lect [i++] != 0);
}



/* convertir les caractères de controle de la ligne */

void conv_carcontrole ()
{
    int i, j;  // numéros de caractère dans buf_lect


    // initialisation
    i = 0;
    j = 0;

    // calculer la taille de la ligne après traitement
    while (buf_lect [i])
    {
        // si caractère de controle autre que \n ou \r suivi de \n
        if (buf_lect [i] < ' ' && buf_lect [i] != '\n')
            if (buf_lect [i] != '\r' || buf_lect [i+1] != '\n')
                // on le convertira
                j++;

        // prise en compte du caractère traité et passage au suivant
        i++;
        j++;
    }

    // pour éviter un débordement de tableau si ligne très longue
    if (j >= sz_buflect)
        j = sz_buflect - 1;

    // si des caractères de controle ont été trouvés
    if (i != j)
    {
        // recopie du caractère de fin de chaine
        buf_lect [j--] = '\0';
        i--;

        // conversion de la ligne de la fin au début
        // pour éviter des écrasements de caractères
        while (i < j)
        {
            // si caractère de controle à convertir
            if (buf_lect [i] < ' ' && buf_lect [i] != '\n')
            {
                if (buf_lect [i] != '\r' || buf_lect [i+1] != '\n')
                {
                    // on en met deux à la place
                    buf_lect [j--] = buf_lect [i--] | 0x40;
                    buf_lect [j--] = '^';
                }
                else
                    // sinon, simple déplacement du caractère
                    buf_lect [j--] = buf_lect [i--];
            }
            else
                // sinon, simple déplacement du caractère
                buf_lect [j--] = buf_lect [i--];
        }
    }
}



/* retourne la position d'un nom de fichier joint dans la ligne courante */

int posnomfic ()
{
    char chaineref [] = "name="; // chaine à chercher pour trouver fichier joint
    int i, j;                    // compteurs


    if (strlen (buf_lect) < 7)
        // la ligne est trop courte pour contenir un nom de fichier joint
        return (0);

    // le mot clé name ou filename ne commence pas en première colonne
    i = 1;

    // recherche de la chaine name =
    do
    {
        // test sur le premier caractère
        if (tolower (buf_lect [i]) == *chaineref)
        {
            // et les suivants
            j = 1;

            while (tolower (buf_lect [i+j]) == chaineref [j])
                j++;

            // trouvé ?
            if (chaineref [j] == '\0')
            {
                // sauter le " éventuel
                if (buf_lect [i+j] == '"')
                    i++;

                // et retourner la position du nom
                return (i + j);
            }
        }
    }
    // continuer la recherche
    while (buf_lect [++i] != '\0');

    // fichier joint non trouvé
    return (0);
}