Commençons par le commencement : Comment c'est fait un mail ?

A la base, c'était simple : On copie pas mal la poste, on a un expéditeur, un destinataire, un sujet et un contenu. Histoire d'ajouter plein de bidules qui font jolis et qui servent pas tellement, on a ajouté des choses comme la priorité, les copies, les copies cachées, etc ...

Et puis un jour, ils se sont dit "ah, j'aimerai bien t'envoyer un fichier avec mon mail". Et voilà comment on est passé d'un système tout simple, à des serveurs de messagerie saturés par des envoi de vidéos rigolotes à tous ses potes.

Pour pouvoir mettre plein de choses dans un même mail, ils ont décidé de pondre une norme qui permet de segmenter ce bon vieux mail en plusieurs morceaux, chacun pouvant contenir des données totalement différentes. Ils ont appelé ça MIME : Multipurpose Internet Mail Extensions (article bien expliqué d'ailleurs).

Bon, je vais pas détailler tout le fonctionnement de cette norme, mais juste m'attacher au strict minimum. Pour illustrer la réflexion, on va faire un truc simple : Je m'envoi un mail avec un fichier texte joint, comme ça je pourrais le passer à la loupe et voir comment il est fichu.

From: Moi
To: Moi
Subject: Exemple mail avec fichier joint
MIME-version: 1.0
Content-Type: multipart/mixed; boundary="separateur"

This is a multi-part message in MIME format.
--separateur
Content-Type: text/plain; charset=ISO-8859-1
Content-Transfer-Encoding: 7bit

Youpi on va disséquer un mail
--separateur
Content-Type: text/plain; name="exemple.txt"
Content-transfer-encoding: base64
Content-Disposition: attachment; filename="exemple.txt"

DKzs01452OP00B
--separateur--

On voit bien que c'est un mail classique, avec quelques froufrous en plus :

  1. Le "MIME-version: 1.0" dans l'entete du mail, qui indique que c'est pas un mail texte tout simple
  2. Content-Type qu'on retrouve pour le mail en lui même et pour toutes les parties du mail. Là aussi c'est parlant, ça indique ce que contient la partie en question
  3. un "boundary" qui va permettre d'indiquer le séparateur pour que le logiciel de messagerie puisse découper le mail

Passons au PHP. Par rapport à un mail texte, nous avons deux choses à modifier : L'entete et le corps. Pour faire au plus simple, on va reprendre directement les infos dans l'exemple qu'on s'est fait. Pas mal de choses vont donc être "en dur" dans le script, il nous restera à calculer le séparateur et à traduire le fichier à joindre.

Traduire le fichier à joindre ? Ben oui, a l'époque où on a fait les premiers mails, on était loin de se douter qu'un jour on doive y mettre des gros fichiers dedans, donc il y a certaines limites (nombre de caractères par lignes par exemple) qui ont été posées, il va donc falloir composer avec. Mais n'ayez pas peur, il y a des fonctions PHP toute prêtes à nous aider pour ça.

function mailfichier($from, $to, $subject, $body, $nomfichier) {
  // generation du separateur, une chaine aleatoire fait l'affaire
  $bound = '------------'.md5(uniqid('toto'))

  $entete = 'From: '.$from."\n"
    .'MIME-Version: 1.0'."\n"
    .'Content-Type: multipart/mixed;'."\n"
    .' boundary="'.$bound.'"';

  $body = 'This is a multi-part message in MIME format.'."\n"
    .'--'.$bound."\n"
    .'Content-Type: text/plain; charset=ISO-8859-1'."\n"
    .'Content-Transfer-Encoding: 7bit'."\n\n"
    .$body
    ."\n\n"
    .'--'.$bound."\n"
    .'Content-Type: text/plain;'."\n".' name="'.basename($nomfichier).'"'."\n"
    .'Content-Transfer-Encoding: base64'."\n"
    .'Content-Disposition: attachment;'."\n".' filename="'.basename($nomfichier).'"'."\n\n"
    .chunk_split(base64_encode(file_get_contents($nomfichier)))
    .'--'.$bound.'--'."\n";

  return @mail($to, $subject, $body, $entete);
}

Voyons les fonctions PHP utilisées :

  • md5() et uniqid() qui permettent de générer une chaine de caractères aléatoires à partir de la chaine qu'on y passe. Le séparateur ne doit pas apparaitre dans le reste du mail, à part comme séparateur bien sûr, donc il faut quelque chose d'à peu près unique.
  • basename() qui permet d'extraire juste le nom du fichier dans une chaine de caractères qui contient le chemin complet du fichier.
  • file_get_contents() qui permet de récuperer le contenu d'un fichier à partir de son nom
  • base64_encore() qui va traduire le contenu du fichier pour qu'on puisse le joindre au mail
  • chunk_split() qui va découper ce contenu pour être certain qu'il tient dans ce qui a été prévu pour les mails

Ce qu'il faudrait développer maintenant :

  1. Calculer, ou passer en paramètre le type de fichier, dans cet exemple c'est toujours "text/plain", donc si on veut envoyer un autre type de fichier, ça ne fonctionnera pas comme il faut
  2. Sécuriser les paramètres. On peut faire faire plein de bêtises à votre fonction si on ajoute pas quelques controles, notamment sur le from, puisqu'il est largué dans les entêtes
  3. prévoir le cas où on veut joindre plusieurs fichiers. Il suffit pour cela de répéter le séparateur + entete + contenu de chaque fichier et laisser le "--" + séparateur + "--" apparaitre une seule fois en fin de mail.