mail_body zerlegen

hm,...

Code:
Notice: iconv() [function.iconv]: Detected an illegal character in input string in /var/www/web1053/html/msg/MailInterpreter.class.php on line 165
165:    return iconv(self::$charset, 'utf-8', $value);

Und was kann ich dann mit diesm array anstellen?
Leider hab ich noch nie vorher was mit imap und diesen codierungen gemacht! Ich bekomme ein array zurück, das mir noch komplexer erscheint als der vorherige Wert!
 
Kurz zum Aufbau von E-Mails: E-Mails bestehen aus zwei Dingen: aus Kopfzeilen und aus dem Körper. Beide sind voneinander durch eine Leerzeile getrennt. Wenn du jetzt jedoch einen Anhang verschicken willst, dann musst dieser in druckbaren Zeichen verschickt werden. Dazu wird der Dateiinhalt mit Base64 kodiert und Eigenschaften wie Dateigröße, Format und Name werden als Kopfzeilen verwendet. Solche Bereiche (also Paare aus Kopfzeile und Körper), die separat zum eigentlichen Inhalt einer E-Mail versendet werden, werden zwischen zwei Boundaries (Begrenzungen) als Körper des eigentlichen Inhalts der E-Mail verschickt. Somit kann der Client, der die E-Mail erhält, nun die E-Mail wieder so zusammen bauen, wie sie ursprünglich aussah.

Was mein Skript macht: mein Skript sucht man Boundaries und Leerzeilen und teilt somit die E-Mail in Bereiche ein. Dabei teilt es Kopfzeilen vom eigentlichen Körper eines Bereiches und formatiert Inhalt, wenn es ein solcher ist, entsprechend der angegebenen Kodierung in UTF-8.

E-Mail-QuelltextBeschreibung
--_000_70DA0C594B96434684762CC7B68465410C1D9815BAERSERVERedvba_Boundary (Beginn eines Bereiches)
Content-Type: text/plain; charset="iso-8859-1"
Content-Transfer-Encoding: quoted-printable
Kopfzeilen
Leerzeile
Dieses mal mit etwas l=E4ngeren text um zu testen ob die zeile wieder abgeb=
rochen wird! Ca. 69 zeichen muss ich in eine zeile bringen um zu wissen ob =
es funktioniert! Jetzt sollte ich auf jeden fall etwas mehr zeichen haben, =
ich teste jetzt noch ein paar html funktionen wie fettschrift und zentriert=
!


Test fett

Test center
Körper
--_000_70DA0C594B96434684762CC7B68465410C1D9815BAERSERVERedvba_Boundary (Ende des Bereiches)
Content-Type: text/html; charset="iso-8859-1"
Content-Transfer-Encoding: quoted-printable
Kopfzeilen
Leerzeile








Dieses mal mit etwas l=E4ngeren text um zu testen ob= die zeile wieder abgebrochen wird! Ca. 69 zeichen muss ich in eine zeile b= ringen um zu wissen ob es funktioniert! Jetzt sollte ich auf jeden fall etw= as mehr zeichen haben, ich teste jetzt noch ein paar html funktionen wie fettschrift und zentriert!





Test fett



Test ce= nter
Körper
 
WOW, vielen Dank für die gute erklärung,...

again what learned :)

ok,... soweit hab ich das jetzt,... wie kann ich jetzt weiter verfahren,...

einerseits hab ich noch solche zeichen ('=E4', '= ') und anderer seits, hab ich noch solche emails:
Code:
Array
(
    [0] => Array
        (
            [0] => 1
            [1] => Array
                (
                    [0] => 
                    [1] => This is a MIME-encapsulated message.
                )

        )

    [1] => Array
        (
            [0] => 2
            [1] => 
--A1745A05E2.1335374365/webbox111.irgendein-server.tld
Content-Description: Notification
Content-Type: text/plain; charset=us-ascii

This is the mail system at host webbox111.irgendein-server.tld.

I'm sorry to have to inform you that your message could not
be delivered to one or more recipients. It's attached below.

For further assistance, please send mail to postmaster.

If you do so, please include this problem report. You can
delete your own text from the attached returned message.

                   The mail system

: host mx-ha02.web.de[213.365.27.10] said: 552-Requested
    mail action aborted: exceeded storage allocation 552-Quota exceeded. 552
    For explanation visit http://postmaster.web.de/error-messages (in reply to
    RCPT TO command)

--A1745A05E2.1335374365/webbox111.irgendein-server.tld
Content-Description: Delivery report
Content-Type: message/delivery-status

Reporting-MTA: dns; webbox111.irgendein-server.tld
X-Postfix-Queue-ID: A1745A05E2
X-Postfix-Sender: rfc822; mail@irgendeine-domain.de
Arrival-Date: Wed, 25 Apr 2012 19:19:24 +0200 (CEST)

Final-Recipient: rfc822; name.nachname@web.de
Original-Recipient: rfc822;name.nachname@web.de
Action: failed
Status: 5.0.0
Remote-MTA: dns; mx-ha02.web.de
Diagnostic-Code: smtp; 552-Requested mail action aborted: exceeded storage
    allocation 552-Quota exceeded. 552 For explanation visit
    http://postmaster.web.de/error-messages

--A1745A05E2.1335374365/webbox111.irgendein-server.tld
Content-Description: Undelivered Message
Content-Type: message/rfc822
Content-Transfer-Encoding: 8bit

Return-Path: 
Received: from smtp.domain.tld (unknown [83.220.144.77])
	by webbox111.irgendein-server.tld (Postfix) with ESMTPA id A1745A05E2
	for ; Wed, 25 Apr 2012 19:19:24 +0200 (CEST)
Date: Wed, 25 Apr 2012 19:19:24 +0200
To: Julius Gracz 
From: "MAIL - domain.de" 
Reply-to: "MAIL - domain.de" 
Subject: Neuer Blogeintrag von baer
Message-ID: <29187703a4b88ba094539ed751f8365a@smtp.domain.tld>
X-Priority: 3
X-Mailer: PHPMailer 5.1 (phpmailer.sourceforge.net)
MIME-Version: 1.0
Content-Type: multipart/alternative;
	boundary="b1_29187703a4b88ba094539ed751f8365a"


--b1_29187703a4b88ba094539ed751f8365a
Content-Type: text/plain; charset = "UTF-8"
Content-Transfer-Encoding: 8bit

Um diese Email anzeigen zu k
        )

)


-> ursprünglich habe ich gehofft, das es fertige klassen dafür gibt! aber egal, was ich bisher versucht habe ich habe es nicht hinbekommen, emails die von web, outlook oder googlemail,... kommen in ner vernünftigen formatierung hinzubekommen,...

was jetzt noch nicht wichtig ist, aber auch noch auf mich zukommt sind dann die anhänge! bilder die mit in die email eingebunden ist,...

?

was ist mein nächster schritt?

und der Fehler steht auch noch aus:

Code:
Notice: iconv() [function.iconv]: Detected an illegal character in input string in /var/www/web1053/html/msg/MailInterpreter.class.php on line 165
165:    return iconv(self::$charset, 'utf-8', $value);
 
Ich arbeite daran. Das Problem besteht aus zwei Teilen: laut Spezifikation darf die erste Zeile keine Leerzeile sein, ist sie bei dir aber. Außerdem ist dein erster Block ein Boundary-Block, was eigentlich auch nicht erlaubt ist.
 
So. Ich habe jetzt mein Skript komplett überarbeitet, also eher komplett neu geschrieben, und damit alle möglichen Fehler beseitigt, die ich mir erdenken konnte, und es parset deinen Quelltext jetzt auch fehlerfrei.. wenn er vollständig ist. Denn das, was du uns hier als Schippsel gezeigt hast, kann nur ein Ausschnitt des ganzen Quelltextes sein. Ansonsten solltest du mal bei deinem E-Mail-Anbieter fragen, ob dessen Server kaputt ist.
PHP:
class MailSourceParser
{
  public static function parse ($source)
  {
    # split source into lines
    $lines = explode("\n", str_replace("\r", '', $source));
    
    # remove empty lines
    foreach ($lines as $i => $line)
    {
      # break at first non-empty line
      if (trim($line) !== '')
      {
        break;
      }
      # remove empty line
      else
      {
        unset($lines[$i]);
      }
    }
    
    # give new indices to the array values, starting at 0
    $lines = array_values($lines);
    
    # exceptional handling for this line of code
    if (($_ = strtolower($lines[0])) === 'this is a' && strpos($_, 'mime') !== false && strpos($_, 'message') !== false)
    {
      # remove this line of code and ...
      unset($lines[0]);
      # give new indices to the array values, starting at 0
      $lines = array_values($lines);
    }
    
    $current    = array();
    $blocks     = array();
    $boundary   = null;
    $boundaries = array();
    $mode       = 1;
    
    # collect data
    foreach ($lines as $i => $line)
    {
      $line = str_replace("\r", '', $line);
      
      # seems to be a boundary
      if (substr($line, 0, 2) === '--' && sscanf($line, '--%s', $boundary) === 1)
      {
        # start boundary
        if (substr($line, -2, 2) !== '--')
        {
          # this line is the first of all, the line before was empty or a boundary
          if ($i === 0 || trim($lines[$i - 1]) === '' || sscanf($lines[$i - 1], '--%s', $_) === 1)
          {
            # real boundary (is defined in header)
            if (in_array($boundary, $boundaries))
            {
              # no reason for adding an empty entry to the list
              if ($current !== array())
              {
                $blocks[]   = $current;
              }
              
              # reset all for a new block
              $current    = array();
              $current[0] = $boundary;
              $mode       = 1;
              continue;
            }
          }
        }
        # end boundary
        else
        {
          # real boundary (is defined in header)
          if (in_array(substr($boundary, 0, strlen($boundary) - 2), $boundaries))
          {
            continue;
          }
        }
      }
      
      switch ($mode)
      {
        # header
        case 1:
        {
          if (trim($line) === '')
          {
            $mode = 2;
          }
          else
          {
            $current[1][] = $line;
            
            if (strtolower(substr($line, 0, 14)) === 'content-type: ')
            {
              if (preg_match('/boundary=([^; ]+)/', $line, $match) === 1)
              {
                $boundaries[] = $match[1];
              }
            }
          }
        }
        break;
        
        # body
        case 2:
        {
          $current[2][] = $line;
        }
        break;
      }
    }
    
    if ($current !== array())
    {
      $blocks[] = $current;
    }
    
    $result = array();
    
    # format data and decode the content
    foreach ($blocks as $i => $block)
    {
      $last    = 0;
      $current = array();
      
      # boundary name
      if (isset($block[0]) && trim($block[0]) !== '')
      {
        $current[0] = $block[0];
      }
      else
      {
        $current[0] = null;
      }
      
      # header
      foreach ($block[1] as $i => $line)
      {
        # add current line to last entry
        if (trim(substr($line, 0, 1)) === '')
        {
          $current[1][$last] .= ' ' . trim($line);
        }
        # add current line as new entry
        else
        {
          $current[1][$i] = $line;
          $last = $i;
        }
      }
      
      # give new indices to the array values, starting at 0
      $current[1] = array_values($current[1]);
      
      foreach ($current[1] as $i => $line)
      {
        # split headers into a key and a value
        $temp = explode(':', $line, 2);
        $current[1][$i] = array(
          trim($temp[0]),
          trim($temp[1])
        );
      }
      
      # content
      if (isset($block[2]))
      {
        $content_type     = null;
        $content_encoding = null;
        
        foreach ($current[1] as $entry)
        {
          # get charset
          if (strtolower($entry[0]) === 'content-type')
          {
            if (preg_match('/charset=("([^ ;]*)"|[^ ;]*)/', $entry[1], $match) === 1)
            {
              $content_type = isset($match[2]) ? $match[2] : $match[1];
            }
          }
          # get transfer encoding
          elseif (strtolower($entry[0]) === 'content-transfer-encoding')
          {
            $content_encoding = trim($entry[1]);
          }
        }
        
        # remove last line (is always empty)
        array_pop($block[2]);
        # put all lines together
        $current[2] = implode("\r\n", $block[2]);
        
        # decode content
        if ($content_encoding !== null)
        {
          if ($content_encoding === 'quoted-printable')
          {
            $current[2] = quoted_printable_decode($current[2]);
          }
        }
        
        # encode content to utf-8
        if ($content_type !== null)
        {
          $current[2] = iconv($content_type, 'utf-8', $current[2]);
        }
      }
      
      # add entry to the result array
      $result[] = $current;
    }
    
    return $result;
  }
}
Wie man sieht, haben sich drei Dinge grundsätzlich geändert: erstens heißt die Klasse jetzt anders, zweitens heißt die aufzurufende Methode anders und drittens gibt es nur noch eine Methode. Ansonsten sieht auch das ausgegebene Array jetzt anders aus:
Code:
[
  0 : [
    0 : 'boundary name',
    1 : [ 'liste mit kopfzeilen, wobei jede kopfzeile wiederum
           eine liste ist, wobei der erste eintrag der name
           und der zweite eintrag der wert ist' ],
    2 : 'inhalt des blocks',
  ],
  1 : [ ... ],
  2 : [ ... ],
  ...
]
 
Zuletzt bearbeitet:
Während ich JesusFreak half mein Skript für seine Zwecke in seine Webseite einzupflegen, habe ich noch einige kleine Änderungen, sowie zwei zusätzliche Klassen getätigt. Zu diesen will ich natürlich auch allen anderen Zugang gewähren:

Klasse: MailSourceParser (neu: kleine Bugfixes sowie die statische Methode get, welche ein MailMessage-Objekt zurückgibt)
PHP:
class MailSourceParser
{
  public static function get ($source)
  {
    return new MailMessage(self::parse($source));
  }

  public static function parse ($source)
  {
    # split source into lines
    $lines = explode("\n", str_replace("\r", '', $source));
  
    # remove empty lines
    foreach ($lines as $i => $line)
    {
      # break at first non-empty line
      if (trim($line) !== '')
      {
        break;
      }
      # remove empty line
      else
      {
        unset($lines[$i]);
      }
    }
  
    # give new indices to the array values, starting at 0
    $lines = array_values($lines);
  
    # exceptional handling for this line of code
    if (($_ = strtolower($lines[0])) === 'this is a' && strpos($_, 'mime') !== false && strpos($_, 'message') !== false)
    {
      # remove this line of code and ...
      unset($lines[0]);
      # give new indices to the array values, starting at 0
      $lines = array_values($lines);
    }
  
    $current    = array();
    $blocks     = array();
    $boundary   = null;
    $boundaries = array();
    $mode       = 1;
  
    # collect data
    while (list($i, $line) = each($lines))
    {
      $line = str_replace("\r", '', $line);
    
      # seems to be a boundary
      if (substr($line, 0, 2) === '--' && sscanf($line, '--%s', $boundary) === 1)
      {
        # start boundary
        if (substr($line, -2, 2) !== '--')
        {
          # maybe the whole mail is in a boundary, so add it
          if ($i === 0)
          {
            $boundaries[] = $boundary;
          }
        
          # real boundary (is defined in header)
          if (in_array($boundary, $boundaries))
          {
            # no reason for adding an empty entry to the list
            if ($current !== array())
            {
              $blocks[]   = $current;
            }
          
            # reset all for a new block
            $current    = array();
            $current[0] = $boundary;
            $mode       = 1;
            continue;
          }
        }
        # end boundary
        else
        {
          # real boundary (is defined in header)
          if (in_array(substr($boundary, 0, strlen($boundary) - 2), $boundaries))
          {
            continue;
          }
        }
      }
    
      switch ($mode)
      {
        # header
        case 1:
        {
          if (trim($line) === '')
          {
            $mode = 2;
          }
          else
          {
            # add associating lines together (must begin with a tabulator or space)
            while (isset($lines[$i + 1]) && (($_ = ord(substr($lines[$i + 1], 0, 1))) === 9 || $_ === 32))
            {
              $line .= ' ' . trim($lines[++$i]);
              next($lines);
            }
          
            $current[1][] = $line;
          
            if (strtolower(substr($line, 0, 14)) === 'content-type: ')
            {
              if (preg_match('/boundary=(([^"; ]+)|"([^"; ]+)")/', $line, $match) === 1)
              {
                # boundary with quotation marks
                if (isset($match[3]))
                {
                  $boundaries[] = $match[3];
                }
                # boundary without quotation marks
                else
                {
                  $boundaries[] = $match[2];
                }
              }
            }
          }
        }
        break;
      
        # body
        case 2:
        {
          $current[2][] = $line;
        }
        break;
      }
    }
  
    if ($current !== array())
    {
      $blocks[] = $current;
    }
  
    $result = array();
  
    # format data and decode the content
    foreach ($blocks as $i => $block)
    {
      $last    = 0;
      $current = array();
    
      # boundary name
      if (isset($block[0]) && trim($block[0]) !== '')
      {
        $current[0] = $block[0];
      }
      else
      {
        $current[0] = null;
      }
    
      foreach ($block[1] as $i => $line)
      {
        # split headers into a key and a value
        $temp    = explode(':', $line, 2);
        # trim parts
        $temp[0] = trim($temp[0]);
        $temp[1] = trim($temp[1]);
      
        # decode if there is an charset mark
        if (preg_match('/=\?([^\?]+)\?(Q|q)\?([^\?]+)\?=/', $temp[1], $match))
        {
          # replace encoded string with the decoded one and convert dashes to spaces (and encode it to utf-8)
          $temp[1] = str_replace($match[0], str_replace('_', ' ', iconv($match[1], 'utf-8', quoted_printable_decode($match[3]))), $temp[1]);
        }
      
        # save parts
        $current[1][$i] = $temp;
      }
    
      # content
      if (isset($block[2]))
      {
        $content_type     = null;
        $content_encoding = null;
        $is_attachment    = false;
      
        foreach ($current[1] as $entry)
        {
          # get charset
          if (strtolower($entry[0]) === 'content-type')
          {
            if (preg_match('/charset=("([^ ;]*)"|[^ ;]*)/', $entry[1], $match) === 1)
            {
              $content_type = isset($match[2]) ? $match[2] : $match[1];
            }
          }
          # get transfer encoding
          elseif (strtolower($entry[0]) === 'content-transfer-encoding')
          {
            $content_encoding = trim($entry[1]);
          }
          elseif (strtolower($entry[0]) === 'content-disposition' && strpos($entry[1], 'attachment') !== false)
          {
            $is_attachment = true;
          }
        }
      
        # put all lines together
        $current[2] = implode("\r\n", $block[2]);
      
        # decode content
        if ($content_encoding !== null)
        {
          if ($content_encoding === 'quoted-printable')
          {
            $current[2] = quoted_printable_decode($current[2]);
          }
          elseif ($content_encoding === 'base64' && !$is_attachment)
          {
            $current[2] = base64_decode($current[2]);
          }
        }
      
        # encode content to utf-8
        if ($content_type !== null)
        {
          $current[2] = iconv($content_type, 'utf-8', $current[2]);
        }
      }
    
      # add entry to the result array
      $result[] = $current;
    }
  
    return $result;
  }
}

Klasse: MailMessage (stellt eine E-Mail-Nachricht dar, so dass man bequem auf deren Inhalte sowie Anhänge und Kopfzeilen zugreifen kann)
PHP:
class MailMessage
{
  private $data        = array();
  private $attachments = array();
  private $contents    = array();
  private $headers     = array();
  
  public function __construct (array $data)
  {
    $this->data = $data;
    
    foreach ($data as $block)
    {
      foreach ($block[1] as $header)
      {
        if (strtolower($header[0]) === 'content-disposition' && strpos($header[1], 'attachment') !== false)
        {
          if (preg_match('/(file)name=("([^"]+)"|([^;]+))/', $header[1], $match))
          {
            if (isset($match[4]))
            {
              $name = $match[4];
            }
            else
            {
              $name = $match[3];
            }
          }
          else
          {
            $name = null;
          }
          
          $this->attachments[] = new FileObject(base64_decode($block[2]), $name);
          continue 2;
        }
      }
      
      if (isset($block[2]))
      {
        $this->contents[] = $block;
      }
      elseif ($block[0] === null)
      {
        foreach ($block[1] as $header)
        {
          $this->headers[] = $header;
        }
      }
    }
  }
  
  public function headers ()
  {
    return $this->headers;
  }
  
  public function contents ()
  {
    return $this->contents;
  }
  
  public function attachments ($return_as_zip = false)
  {
    if ($this->attachments === array())
    {
      return null;
    }
    
    if ($return_as_zip !== false)
    {
      if (is_string($return_as_zip))
      {
        $name = $return_as_zip;
      }
      else
      {
        $name = 'attachments.zip';
      }
      
      $zip = new ZipArchive;
      $zip->open($name, ZIPARCHIVE::CREATE);
      
      foreach ($this->attachments as $attachment)
      {
        $zip->addFromString($attachment->name(), $attachment->data());
      }
      
      $zip->close();
      return true;
    }
    else
    {
      return $this->attachments;
    }
  }
}

Klasse: FileObject (stellt eine Datei dar)
PHP:
class FileObject
{
  private $data = '';
  private $name = '';
  
  public static function load ($name)
  {
    $instance = new self(file_get_contents($name));
    $instance->name = $name;
    return $instance;
  }
  
  public function __construct ($data = null, $name = null)
  {
    if ($data !== null)
    {
      $this->data = $data;
    }
    
    if ($name !== null)
    {
      $this->name = $name;
    }
  }
  
  public function save ($name = null)
  {
    if ($name === null)
    {
      if ($this->name === '')
      {
        throw new RuntimeException('invalid file name');
      }
      
      $name = $this->name;
    }
    
    if (($bytes = file_put_contents($name, $this->data)) === false)
    {
      throw new RuntimeException('could not save file as "' . $name . '"');
    }
    else
    {
      return $bytes;
    }
  }
  
  public function name ()
  {
    return $this->name;
  }
  
  public function data ()
  {
    return $this->data;
  }
}

Verwendung:
PHP:
$source = ''; # Quelltext einer E-Mail
$message = MailSourceParser::get($source);

# gibt ein Array mit allen Anhängen zurück oder null, wenn keine vorhanden
$attachments = $message->attachments();
# wähle den ersten Anhang aus
$file = $attachments[0];
# gib den Namen der Datei aus
$file->name();
# gib die Daten der Datei aus
$file->data();
# speichert die Datei unter dem Original-Dateinamen
$file->save();
# speichert die Datei unter neuem Namen
$file->save('neuer_dateiname.png');

# erzeugt eine ZIP-Datei, die alle Anhänge enthält, und speichert diese
# im gleichen Verzeichnis wie das Skript
$message->attachments(true);
# erzeugt eine ZIP-Datei, die alle Anhänge enthält, und speichert sie
# unter dem angegebenen Namen
$message->attachments('../attachments/anhang.zip');

# gibt alle Kopfzeilen der E-Mail zurück (nur die Kopfzeilen,
# die außerhalb eines Boundary-Blocks liegen)
$message->headers();

# gibt alle Blöcke zurück, die keinen Anhang darstellen
$message->contents();
 
Zuletzt bearbeitet:

Neue Beiträge

Zurück