Rainer: Mailversand mit Anhang

Hallo,

habe folgensdes Script zum testen geschrieben:

<?php
$file = "pdfdatei.pdf";
$file_name = "wie_soll_anhang_heissen.pdf";
$from = "test@example.org";
$to = "meine@example.de";
$message = "Hier steht dann die Nachricht der Mail";
$boundary = strtoupper(md5(uniqid(time())));
$mail_header  = "From:Test <$from>";
$mail_header .= "\nMIME-Version: 1.0";
$mail_header .= "\nContent-Type: multipart/mixed; boundary=$boundary";
$mail_header .= "\n--$boundary";
$mail_header .= "\nContent-Type: text/plain";
$mail_header .= "\nContent-Transfer-Encoding: 8bit";
$mail_header .= "\n$message";

$file_content = fread(fopen($file,"r"),filesize($file));
$file_content = chunk_split(base64_encode($file_content));

$mail_header .= "\n--$boundary";
$mail_header .= "\nContent-Type: application/octetstream; name="$file_name"";
$mail_header .= "\nContent-Transfer-Encoding: base64";
$mail_header .= "\nContent-Disposition: attachment; filename="$file_name"\n";

$mail_header .= "$file_content\n";
$mail_header .= "--$boundary--\n";
mail("$to","Betreff","$message","$mail_header");
?>

$to ist selbstverständlich im Test eine korrekte Mailadresse von mir.
Die Email kommt an. Der Anhang ist 0kb groß. Warum? Die Datei ist auch i.O.

Ich finde einfach den Fehler nicht.

Gruß Rainer

  1. Hi,

    ich habe es nicht ausprobiert, aber möglicherweise verwendet Dein E-Mailprogramm Daten aus dem Content-Length Header-Eintrag.

    In jedem Fall solltest du nicht nur Content-Disposition und Content-Type angeben sondern auch Content-Length.

    Infos dazu:
    http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.13

    Content-Type gibt (unverbindlich und eigentlich optional) die Größe der Datei ein Bytes an.

    Lg
    Revo

  2. Hi,

    <?php
    $file = "pdfdatei.pdf";
    $file_name = "wie_soll_anhang_heissen.pdf";
    $from = "test@example.org";
    $to = "meine@example.de";
    $message = "Hier steht dann die Nachricht der Mail";
    $boundary = strtoupper(md5(uniqid(time())));
    $mail_header  = "From:Test <$from>";
    $mail_header .= "\nMIME-Version: 1.0";
    $mail_header .= "\nContent-Type: multipart/mixed; boundary=$boundary";
    $mail_header .= "\n--$boundary";
    $mail_header .= "\nContent-Type: text/plain";
    $mail_header .= "\nContent-Transfer-Encoding: 8bit";
    $mail_header .= "\n$message";

    hier schon ein guter Rat und zwei Rügen:
    a) Schreibe die \n ans Zeilenende, das ist intuitiver: Zeilen werden mit einem \n *beendet*.
    b) Entscheide dich für *einen* Content-Type-Header. Zwei sind Unsinn.
    c) Warum packst du die eigentliche Message in den Header? Das würde zwar funktionieren, wenn du einen doppelten Zeilenumbruch davor setzen würdest; PHP fügt beim Absenden auch nur Header und Body mit zwei Zeilenumbrüchen zusammen. Aber "sauber" ist es nicht.

    Egal: So wie du es jetzt hast, wird dein Message-Text als Headerzeile interpretiert, weil die Leerzeile fehlt.

    $file_content = fread(fopen($file,"r"),filesize($file));
    $file_content = chunk_split(base64_encode($file_content));

    Ziehe diese Anweisungen zum Debugging mal auseinander: Liefert fopen() ein gültiges File-Handle? Liefert fread() einen String, der mit der Dateigröße übereinstimmt?

    $mail_header .= "\n--$boundary";

    Immer noch keine Leerzeile. Wir sind also immer noch im globalen Mail-Header.

    $mail_header .= "\nContent-Type: application/octetstream; name="$file_name"";

    Beachte den Bindestrich beim MIME-Typ application/octet-stream.

    $mail_header .= "\nContent-Transfer-Encoding: base64";
    $mail_header .= "\nContent-Disposition: attachment; filename="$file_name"\n";

    $mail_header .= "$file_content\n";
    $mail_header .= "--$boundary--\n";
    mail("$to","Betreff","$message","$mail_header");

    Jetzt wird's wüst: Mal abgesehen davon, dass es einfach Unsinn ist, Stringvariablen nochmal in einen String einzubetten - du willst hier eine Mail verschicken, die die ursprünglich übergebene Message als Text enthält und den ganzen Block, den du eben aufbereitet hast (in dem $message ja auch schon drinsteckt) als Header?

    $to ist selbstverständlich im Test eine korrekte Mailadresse von mir.
    Die Email kommt an. Der Anhang ist 0kb groß. Warum? Die Datei ist auch i.O.

    Theoretisch dürfte dein Mailclient gar keinen Anhang erkennen. Auch nicht in einer Länge von 0 Bytes.

    Ich finde einfach den Fehler nicht.

    Kaum zu glauben. Dabei hast du so viele eingebaut ...

    So long,
     Martin

    --
    Ist die Katze gesund,
    freut sich der Hund.
    1. hallo Martin,

      habe einiges geändert _aber_ jetzt habe ich teile des headers und den Anhang in der Email als Text. Was ist nun noch falsch.

      <?php
      $pdfname = "200707-33.pdf";

      $to = "meine@example.org";
      $subject="TestMail mit Anhang";

      $grenze="grenzlinie";
      $msg="";

      $msg .="\n\n--$grenze\n\n";

      $msg .= "Das ist ein Testtext \nmit Zeilenumbruch";

      $msg.="\n\n--$grenze\n\n";

      $datei = "pdfdatei.pdf";

      $zeiger_auf_datei=@fopen($datei,"rb");
      $inhalt_der_datei=@fread($zeiger_auf_datei,filesize($datei));
      @fclose($zeiger_auf_datei);
      $inhalt_der_datei=chunk_split(base64_encode($inhalt_der_datei));
      $msg.=$inhalt_der_datei;
      $msg.="\n\n";

      $msg.="--$grenze";

      $headers = "FROM: RT test@example.org\n";
      $headers .= "Content-Type: application/octet-stream;\nname=$pdfname\n";
      $headers .= "Content-Transfer-Encoding: base64\n";
      $headers .= "Content-Disposition: attachment;\nfilename=$pdfname\n\n";

      mail($to,$subject,$msg,$headers);
      ?>

      1. Hallo Rainer,

        habe einiges geändert _aber_ jetzt habe ich teile des headers und den Anhang in der Email als Text.

        naja, jetzt hast du vieles korrigiert, aber ein paar Kleinigkeiten falsch gemacht, die vorher richtig waren ...
        Schauen wir uns mal die Header zuerst an.

        $headers = "FROM: RT test@example.org\n";
        $headers .= "Content-Type: application/octet-stream;\nname=$pdfname\n";
        $headers .= "Content-Transfer-Encoding: base64\n";
        $headers .= "Content-Disposition: attachment;\nfilename=$pdfname\n\n";

        Okay, mit den \n am Ende anstatt am Zeilenanfang sieht es doch übersichtlicher aus, findest du nicht auch?
        Die Header-Identifier werden üblicherweise nicht durchgehend groß geschrieben - also "From:" anstatt "FROM:". Das hat aber AFAIK keine negativen Auswirkungen, ist nur eine Konvention.

        Falsch ist aber, dass du nun den *gesamten* Mail-Inhalt als application/octet-stream deklarieren willst. Im ersten Ansatz hattest du da noch multipart/mixed, was richtig war. Die Angabe der Content-Types der einzelnen Teile erfolgt dann in den Headern *innerhalb* dieser Teile.
        Für den globalen Header haben wir dann also:

        $headers  = "From: RT test@example.org\n";
        $headers .= "MIME-Version: 1.0\n";
        $headers .= "Content-Type: multipart/mixed; boundary=$grenze\n";

        Achte darauf, dass $grenze vorher schon definiert sein muss!
        Das war's hier. Alles weitere ist -technisch betrachtet- der Mail-Body (also das, was die Funktion mail() als $message betrachtet). Dass dieser Mail-Body selbst aus mehreren Abschnitten besteht, die jeder wieder einen eigenen Header und Body haben, ist gar nicht so wild, wenn man das Konzept mal verstanden hat.

        $msg="";
        $msg .="\n\n--$grenze\n\n";

        Hier ist es unnötig, mit zwei Zeilenumbrüchen anzufangen; es schadet aber auch nicht. Ich würde sie trotzdem weglassen. Hinter der Boundary-Zeile kommt aber nur *ein* Zeilenumbruch!
        Allerdings fehlen nun die lokalen Header für diesen Abschnitt:

        $msg  = "--$grenze\n";
        $msg .= "Content-Type: text/plain; charset=ISO-8859-1\n";
        $msg .= "Content-Transfer-Encoding: quoted-printable\n\n";

        Beachte den doppelten Zeilenumbruch am Ende: Die lokalen Headerzeilen werden wieder mit einer Leerzeile vom nachfolgenden Nutzinhalt getrennt. Lassen wir also den angekündigten Nutzinhalt vom Typ text/plain folgen:

        $msg .= "Das ist ein Testtext \nmit Zeilenumbruch\n\n";

        Am Ende des Nutzinhalts folgt dann -mit einer Leerzeile, also zwei Zeilenumbrüchen abgetrennt- wieder eine Boundary-Zeile:

        $msg  = "--$grenze\n";

        Und jetzt erst bist du dran mit deinem MIME-Typ application/octet-stream. ;-)
        Aber nicht ganz so, wie du diese Hederzeilen formuliert hattest; die Fortsetzungszeilen ("name=" oder "filename=") dürfen nicht am Zeilenanfang stehen, sondern müssen mit einem Leerzeichen eingerückt sein - sonst würden sie als eigenständige Headerzeilen interpretiert.

        $msg .= "Content-Type: application/octet-stream;\n name=$pdfname\n";
        $msg .= "Content-Transfer-Encoding: base64\n";
        $msg .= "Content-Disposition: attachment;\n filename=$pdfname\n\n";

        Wieder der doppelte Zeilenumbruch zur Abtrennung vom Nutzinhalt, der dann sofort folgt:

        $zeiger_auf_datei=@fopen($datei,"rb");
        $inhalt_der_datei=@fread($zeiger_auf_datei,filesize($datei));
        @fclose($zeiger_auf_datei);
        $inhalt_der_datei=chunk_split(base64_encode($inhalt_der_datei));
        $msg .= $inhalt_der_datei;
        $msg .= "\n\n";
        $msg .= "--$grenze--";

        Vergiss nicht, dass die letzte Boundary-Zeile mit einem zusätzlichen '--' endet.
        Mir wird außerdem nicht ganz klar, warum du bei fopen(), fread() und fclose() den Fehlerunterdrückungs-Operator '@' benutzt, anstatt die Fehlerbedingungen direkt auszuwerten (z.B. dass fopen() im Fehlerfall false liefert).

        Ich hoffe, dass du nun allmählich zu einem funktionsfähigen Beispiel kommst und auch noch verstanden hast, *warum* einzelne Teile wie gewünscht funktionieren oder eben nicht.

        Wenn dir noch etwas unklar ist: Schau dir in deinem Mailclient einfach mal eine korrekt empfangene Mail mit Anhang im Quelltext an, und versuche, die Strukturen wiederzuerkennen, die wir besprochen haben!

        So long,
         Martin

        --
        Rizinus hat sich angeblich als sehr gutes Mittel gegen Husten bewährt.
        1. Hallo Martin,

          danke für die ausführliche erklärung. Ich hatte zwar inzwischen irgendwo was fertiges gefunden und fix eingebaut, werde aber auf alle Fälle genau nach deiner Erklärung den Test nochmal neu schreiben damit ich es endlich kapiere wie das mit mail() funltioniert.

          Nochmals herzlichen Dank und Gruß

          Rainer