Daniel Richter: E-Mail: Muss sich der PDF-Anhang im Header befinden?

Hallo,

ich habe ein paar PHP-Scripte für einen Bestellvorgang erstellt. Am Ende sollen die gesammelten Informationen in Form einer E-Mail versendet werden (Plaintext + PDF-Anhang).

Da ich mich damit nicht wirklich auskenne, habe ich mir ein paar Beispielscripte zusammengesucht. Alle gefundenen Scripte schreiben die PDF in den Kopfbereich (4. Parameter von mail()). Allerdings haben diese immer dafür gesorgt, dass sich PHP bei mail() aufhängt (60 Sekunden werden überschritten), die PDF wird aber seltsamer Weise trotzdem versendet. Nun habe ich einfach mal probiert, was passiert, wenn ich die PDF in den Nachrichtenteil (Parameter 3 bei mail()) packe. Tatsächlich funktioniert es so ohne Probleme.

Die getesteten Mail-Programme (Pegasus Mail, Outlook Express, GMX-Webinterface), kamen damit zurecht. Entspricht das aber auch den Standards, oder handelt es sich hierbei nur um eine Fehlertoleranz bei den getesteten Programmen?

Hier der Code meiner Funktion:

  
function verschickePdfMail($empfaenger, $betreff, $text, $pdfPfad, $pdf_dateinameMail){  
 $id = md5(uniqid(time()));  
 $dateiinhalt = file_get_contents($pdfPfad);  
  
  // Absender Name und E-Mail Adresse  
 //$kopf = "From: Manfred Mustermann <meine@mailadresse.xy>\n";  
 $kopf = "";  
 $body = "";  
 $kopf .= "MIME-Version: 1.0\n";  
 $kopf .= "Content-Type: multipart/mixed; boundary=$id\n\n";  
 $kopf .= "This is a multi-part message in MIME format\n";  
 $body .= "--$id\n";  
 $body .= "Content-Type: text/plain; charset=ISO-8859-15\n";  
 $body .= "Content-Transfer-Encoding: 8bit\n\n";  
 $body .= $text; // Inhalt der E-Mail (Body)  
 // Content-Type: image/gif, image/jpeg, image/png » MIME-Typen - selfHtml.org  
 $body .= "\n--$id";  
 $body .= "\nContent-Type: application/pdf; name=$pdf_dateinameMail\n";  
 $body .= "Content-Transfer-Encoding: base64\n";  
 $body .= "Content-Disposition: attachment; filename=$pdf_dateinameMail\n\n";  
 $body .= chunk_split(base64_encode($dateiinhalt));  
 $body .= "\n--$id--";  
 return mail($empfaenger, $betreff, $body, $kopf); // E-Mail versenden  
}  

mfg. Daniel

  1. Hello,

    function verschickePdfMail($empfaenger, $betreff, $text, $pdfPfad, $pdf_dateinameMail){
    $id = md5(uniqid(time()));
    $dateiinhalt = file_get_contents($pdfPfad);
      // Absender Name und E-Mail Adresse
    //$kopf = "From: Manfred Mustermann meine@mailadresse.xy\n";
    $kopf = "";
    $body = "";
    $kopf .= "MIME-Version: 1.0\n";
    $kopf .= "Content-Type: multipart/mixed; boundary=$id\n\n";
    $kopf .= "This is a multi-part message in MIME format\n";
    $body .= "--$id\n";
    $body .= "Content-Type: text/plain; charset=ISO-8859-15\n";
    $body .= "Content-Transfer-Encoding: 8bit\n\n";
    $body .= $text; // Inhalt der E-Mail (Body)
    // Content-Type: image/gif, image/jpeg, image/png » MIME-Typen - selfHtml.org
    $body .= "\n--$id";
    $body .= "\nContent-Type: application/pdf; name=$pdf_dateinameMail\n";
    $body .= "Content-Transfer-Encoding: base64\n";
    $body .= "Content-Disposition: attachment; filename=$pdf_dateinameMail\n\n";

    Schau mal nach, welches Zeilenende-Zeichen chunk_split() verwendet.
    Wenn Du mittels sendmail-Script auf Linux versendest, solltest Du nur "\n" verwenden. MWn hat chunk_split aber "\r\n" als voreinstellung

    $body .= chunk_split(base64_encode($dateiinhalt));

    $body .= "\n--$id--";
    return mail($empfaenger, $betreff, $body, $kopf); // E-Mail versenden
    }
    [/code]

    mfg. Daniel

    Ein harzliches Glückauf

    Tom vom Berg

    --
    Nur selber lernen macht schlau
    http://bergpost.annerschbarrich.de
    1. Hallo,

      function verschickePdfMail($empfaenger, $betreff, $text, $pdfPfad, $pdf_dateinameMail){
      […]
      $body .= "Content-Disposition: attachment; filename=$pdf_dateinameMail\n\n";

      Schau mal nach, welches Zeilenende-Zeichen chunk_split() verwendet.
      Wenn Du mittels sendmail-Script auf Linux versendest, solltest Du nur "\n" verwenden. MWn hat chunk_split aber "\r\n" als voreinstellung

      OK, danke für den Tipp. Ich hab den E-Mail-Teil derzeit nur unter Windows getestet…

      Habe die Zeile jetzt durch folgendes ersetzt:

        
       $body .= chunk_split(base64_encode($dateiinhalt), 76, "\n");  
      
      

      mfg. Daniel

      1. Hello,

        OK, danke für den Tipp. Ich hab den E-Mail-Teil derzeit nur unter Windows getestet…

        Dann ist aber vermutlich alles verkorkst.
        Unter Windows wird vermutlich ein MTA direkt über Port 25 mit der Maildatei gefüttert.
        Das musst Du unbedingt vorher feststellen.

        Wenn es so ist, dann musst Du ALLE Zeilenumbrüche, die für die Steuerung des MTA relevant sind, mit "\r\n" vornehmen, also zur Trennung der header, zur Trennung des Bodys vom Header, zur Trennung der Body-Elemente voneinander.

        Sonst kann es sein, dass die Mail deshalb nicht durchgeht.

        Nochmal zum Verständnis: "\r\n" ist als Zeilenschaltung für den Mailtransfer vorgeschrieben. Wenn Du aber ein klassisches sendmail-Script kit dem Versenden beauftragst (das steht dann in der php.ini eingetregen), dann will dieses aus allen "\n" ein "\r\n" machen und vermutlich auch aus den "\r". auf jeden Fall entstehen dadurch dann entweder "\r\n\r\n" oder "\r\r\n"

        Beide Möglichkeiten entsprechen aber nicht mehr der RFC 2822 oder 2045.

        Somit wird Dein Übertragungsversuch in 98% der Fälle irgendwo im Netz an einem Relais scheitern.

        Ein harzliches Glückauf

        Tom vom Berg

        --
        Nur selber lernen macht schlau
        http://bergpost.annerschbarrich.de
        1. Hallo,

          Wenn es so ist, dann musst Du ALLE Zeilenumbrüche, die für die Steuerung des MTA relevant sind, mit "\r\n" vornehmen, also zur Trennung der header, zur Trennung des Bodys vom Header, zur Trennung der Body-Elemente voneinander.

          Sonst kann es sein, dass die Mail deshalb nicht durchgeht.

          Nö, unter Windows wird die Mail ohne Probleme versendet (Jedenfalls wenn ich die PDF nicht über den header-Parameter sende). Sowohl über den lokalen Mailserver (Mercury), als auch über einen Mailserver im Internet.

          Nochmal zum Verständnis: "\r\n" ist als Zeilenschaltung für den Mailtransfer vorgeschrieben. Wenn Du aber ein klassisches sendmail-Script kit dem Versenden beauftragst (das steht dann in der php.ini eingetregen), dann will dieses aus allen "\n" ein "\r\n" machen und vermutlich auch aus den "\r". auf jeden Fall entstehen dadurch dann entweder "\r\n\r\n" oder "\r\r\n"

          Beide Möglichkeiten entsprechen aber nicht mehr der RFC 2822 oder 2045.

          Letztendlich soll das Ganze auf einem Linux-Server laufen. Da ich derzeit noch überall "\n" verwende, kann also nur "\n" oder "\r\n" versendet werden.

          Ich habe jetzt mal mehrere Kombinationen ausprobiert:

          Linux, "\r\n": OK
          Linux, "\n": OK
          Windows, "\r\n": OK
          Windows, "\n": OK

          mfg. Daniel

          1. Hello,

            Ich habe jetzt mal mehrere Kombinationen ausprobiert:

            Linux, "\r\n": OK
            Linux, "\n": OK
            Windows, "\r\n": OK
            Windows, "\n": OK

            Was ist da OK?

            Lies bitte erst die Threads, die Du zu diesem Thema im Archiv finden kannst. Ich kann das jetzt nicht alles nochmal durchkauen.

            Dein test ist jedenfalls unvollständig, wenn Du nicht auch anschaust, was nachher tatsächlich beim Empfänger ankommt und feststellst, wo die eventuell feststellbaren automatischen Änderungen vorgenommen worden sind.

            Ein harzliches Glückauf

            Tom vom Berg

            --
            Nur selber lernen macht schlau
            http://bergpost.annerschbarrich.de
            1. Hallo,

              Ich habe jetzt mal mehrere Kombinationen ausprobiert:

              Linux, "\r\n": OK
              Linux, "\n": OK
              Windows, "\r\n": OK
              Windows, "\n": OK

              Was ist da OK?

              Die Mail kommt an

              Dein test ist jedenfalls unvollständig, wenn Du nicht auch anschaust, was nachher tatsächlich beim Empfänger ankommt und feststellst, wo die eventuell feststellbaren automatischen Änderungen vorgenommen worden sind.

              Kennst du zufällig einen Mailclient der mir die Zeilenumbruchsymbole anzeigt? Wenn ich den Text einfach nur kopiere, nimmt er immer diejenigen, die im Editor eingestellt sind.

              mfg. Daniel

              1. Hello,

                Kennst du zufällig einen Mailclient der mir die Zeilenumbruchsymbole anzeigt? Wenn ich den Text einfach nur kopiere, nimmt er immer diejenigen, die im Editor eingestellt sind.

                Das ist ein echtes Problem, dass man irgenwann nicht mehr nachvollziehen kann, an welcher Stelle welche Änderungen vorgenommen wurden.

                Eine gute Möglichkeit ist es da, die Mails auf dem SMTP-Server direkt als Datei aus der Mailbox zu holen. Dazu muss man natürlich einen haben, auf den SSH-Zugriff besteht.

                Ein harzliches Glückauf

                Tom vom Berg

                --
                Nur selber lernen macht schlau
                http://bergpost.annerschbarrich.de
  2. Moin!

    Da ich mich damit nicht wirklich auskenne, habe ich mir ein paar Beispielscripte zusammengesucht. Alle gefundenen Scripte schreiben die PDF in den Kopfbereich (4. Parameter von mail()). Allerdings haben diese immer dafür gesorgt, dass sich PHP bei mail() aufhängt (60 Sekunden werden überschritten), die PDF wird aber seltsamer Weise trotzdem versendet. Nun habe ich einfach mal probiert, was passiert, wenn ich die PDF in den Nachrichtenteil (Parameter 3 bei mail()) packe. Tatsächlich funktioniert es so ohne Probleme.

    Ich kann nicht ganz nachvollziehen, warum es in deinem ersten Beispiel nicht funktioniert, aber ich kann erklären, warum es überhaupt funktioniert - aber schlecht ist. :)

    Mails sind nichts anderes als fortlaufender Text - ein Zeichen nach dem anderen. Damit man in solch einen Datenstrom etwas Struktur für wichtige Informationen hineinbringen kann, gibt es sozusagen "Formatierungskonventionen". Die oberste und wichtigste ist: Eine Mail beginnt erstmal mit dem Header, und sobald eine komplette Leerzeile kommt, endet der Header, und es beginnt der Body.

    Daraus ergeben sich zwei wichtige Konsequenzen:
    1. Innerhalb des Headers darf es keinerlei formatierende Leerzeilen geben, denn die würden den Body starten.
    2. Wenn man sich aber der Konsequenzen bewußt ist, kann man eine Leerzeile auch absichtlich ausnutzen, um im "Header" den Body zu starten.

    Der Befehl mail() hängt einfach nur Header-Parameter und Body-Parameter, getrennt durch eine Leerzeile, hintereinander und schickt das Ergebnis zum Mailserver.

    Es ist allerdings keine gute Idee, aufgrund dieser Logik den Body-Parameter einfach zu ignorieren und sämtlichen Mailinhalt künstlich über den Header-Parameter zu versenden. Beispielsweise verhindert die PHP-Sicherheitsextension Suhosin diese Vorgehensweise, denn genauso, wie man absichtlich Body-Text in den Header-Parameter geben kann, so kann es einem auch unabsichtlich bzw. durch Spammer passieren, wenn man die Headerzeilen nicht komplett statisch im Skript ablegt, sondern sie mit Variablen anreichert. Die Variablen könnten ebenfalls eine Leerzeile (und nachfolgend Spam-Werbebotschaften) enthalten und so das Skript mißbrauchen, ohne dass es gewollt ist.

    Dein Reparaturversuch ist allerdings nicht korrekt:

    Hier der Code meiner Funktion:

    function verschickePdfMail($empfaenger, $betreff, $text, $pdfPfad, $pdf_dateinameMail){
    $id = md5(uniqid(time()));
    $dateiinhalt = file_get_contents($pdfPfad);

    // Absender Name und E-Mail Adresse
    //$kopf = "From: Manfred Mustermann meine@mailadresse.xy\n";
    $kopf = "";
    $body = "";
    $kopf .= "MIME-Version: 1.0\n";
    $kopf .= "Content-Type: multipart/mixed; boundary=$id\n\n";

    // Am Ende dieser Zeile kommt die Leerzeile, die den Header beendet. Alles nachfolgende ist schon Body!

    $kopf .= "This is a multi-part message in MIME format\n";
    $body .= "--$id\n";
    $body .= "Content-Type: text/plain; charset=ISO-8859-15\n";
    $body .= "Content-Transfer-Encoding: 8bit\n\n";
    $body .= $text; // Inhalt der E-Mail (Body)
    // Content-Type: image/gif, image/jpeg, image/png » MIME-Typen - selfHtml.org
    $body .= "\n--$id";
    $body .= "\nContent-Type: application/pdf; name=$pdf_dateinameMail\n";
    $body .= "Content-Transfer-Encoding: base64\n";
    $body .= "Content-Disposition: attachment; filename=$pdf_dateinameMail\n\n";
    $body .= chunk_split(base64_encode($dateiinhalt));
    $body .= "\n--$id--";
    return mail($empfaenger, $betreff, $body, $kopf); // E-Mail versenden
    }

      
     - Sven Rautenberg
    
    -- 
    "Love your nation - respect the others."
    
    1. Hallo,

      Der Befehl mail() hängt einfach nur Header-Parameter und Body-Parameter, getrennt durch eine Leerzeile, hintereinander und schickt das Ergebnis zum Mailserver.

      Genau das dachte ich mir auch. Daher fand ich es umso seltsamer, dass er sich dabei aufhängt. Aber wer weiß, was die XAMPP-Hersteller an Sicherheitsfeatures eingebaut haben…

      Dein Reparaturversuch ist allerdings nicht korrekt:

      Hier der Code meiner Funktion:

      function verschickePdfMail($empfaenger, $betreff, $text, $pdfPfad, $pdf_dateinameMail){
      $id = md5(uniqid(time()));
      $dateiinhalt = file_get_contents($pdfPfad);

      // Absender Name und E-Mail Adresse
      //$kopf = "From: Manfred Mustermann meine@mailadresse.xy\n";
      $kopf = "";
      $body = "";
      $kopf .= "MIME-Version: 1.0\n";
      $kopf .= "Content-Type: multipart/mixed; boundary=$id\n\n";

      // Am Ende dieser Zeile kommt die Leerzeile, die den Header beendet. Alles nachfolgende ist schon Body!

      $kopf .= "This is a multi-part message in MIME format\n";
      $body .= "--$id\n";

      OK, vielen Dank. Habe jetzt den 2. Zeilenumbruch entfernt und die nachfolgende Zeile an den body gehängt.

      [code lang=php]
       […]
       $kopf .= "Content-Type: multipart/mixed; boundary=$id\n";
       $body .= "This is a multi-part message in MIME format\n";
       $body .= "--$id\n";
       […]

        
      Vielen Dank für Deine Hilfe.  
        
      mfg. Daniel
      
      -- 
      [Selfcode](http://forum.de.selfhtml.org/cgi-bin/selfcode.pl): [ie:{ fl:( br:> va:) ls:& fo:) rl:( n4:# ss:) de:> js:) mo:} zu:}](http://www.peter.in-berlin.de/projekte/selfcode/?code=ie%3A%7B+fl%3A%28+br%3A%3E+va%3A%29+ls%3A%26+fo%3A%29+rl%3A%28+n4%3A%23+ss%3A%29+de%3A%3E+js%3A%29+mo%3A%7D+zu%3A%7D)