/*
    Fichier trtligne.c
    Auteur Bernard Chardonneau

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

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


    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 <stdio.h>
#include <string.h>
#include "buflect.h"
#include "encodage.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 mem_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 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);
        buf_lect [i++] = '\n';
        buf_lect [i] = '\0';
    }

    // 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';
    }

    // convertir les caractères HTML, par exemple les &#8217; en apostrophes
    // ce genre de séquence NE DEVRAIT JAMAIS se trouver dans un TEXTE de
    // mail correctement généré (mais on en trouve !!!!)
    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 ();
    }
    // sinon le mail est supposé encodé ISO-8859
    else
    {
        // 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
        else
            // 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
            conv_isomac ();
    }
}



/* 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')))
        {
            // 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] = '½';
            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;

    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 : if (buf_lect [i+1] == 0x93)
                            {
                                // on convertit le résultat en  ½
                                buf_lect [j++] = 0xBD;
                                i++;
                            }
                            else
                                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)
        {
            switch (buf_lect [i])
            {
                case 0x99: buf_lect [j++] = '\'';
                           break;

                case 0x93:
                case 0x9C:
                case 0x9D:
                case 0x9E: buf_lect [j++] = '"';
                           break;

                           // pointillés
                case 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++] = '.';

                           break;

                // cas d'une séquence encodée non répertoriée
                default  : 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);
}



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

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 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++;
    }
    while (buf_lect [i++]);

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



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

void conv_carhtm (char limitation)
{
    // caractères auxquels est associé un nombre précédé d'un # ou un nom
    char *carnomme [] = {"½#339", "-#8211", "'#8216", "'#8217", " nbsp",
                         "<lt", ">gt", "&amp", "'quot", "¤euro", "àagrave",
                         "éeacute", NULL};

    int trouve;  // indique si on a trouvé un caractère nommé
    int choix;   // numéro du caractère nommé en cours de test
    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] == '&' &&
                (buf_lect [i+1] == limitation || !limitation))
        {
            // initialisation
            trouve = 0;
            choix = 0;

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

                while (tolower (buf_lect [i + depl]) == 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)
            {
                // le remplacer par le caractère adéquat
                buf_lect [j++] = carnomme [choix][0];

                // 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);
}



/* 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);
}