array(name => value), 'body' => content), null if no parts found * @throws Zend_Exception */ public static function splitMessageStruct( $message, $boundary, $EOL = Zend_Mime::LINEEND ) { $parts = self::splitMime($message, $boundary); if (count($parts) <= 0) { return null; } $result = array(); foreach ($parts as $part) { self::splitMessage($part, $headers, $body, $EOL); $result[] = array( 'header' => $headers, 'body' => $body ); } return $result; } /** * split a message in header and body part, if no header or an * invalid header is found $headers is empty * * The charset of the returned headers depend on your iconv settings. * * @param string $message raw message with header and optional content * @param array $headers output param, array with headers as array(name => value) * @param string $body output param, content of message * @param string $EOL EOL string; defaults to {@link Zend_Mime::LINEEND} * @return null */ public static function splitMessage( $message, &$headers, &$body, $EOL = Zend_Mime::LINEEND ) { self::splitMessageRaw($message, $headers, $body, $EOL); $headers = iconv_mime_decode_headers( $headers, ICONV_MIME_DECODE_CONTINUE_ON_ERROR ); if ($headers === false) { // an error occurs during the decoding return; } // normalize header names foreach ($headers as $name => $header) { $lower = strtolower($name); if ($lower == $name) { continue; } unset($headers[$name]); if (!isset($headers[$lower])) { $headers[$lower] = $header; continue; } if (is_array($headers[$lower])) { $headers[$lower][] = $header; continue; } $headers[$lower] = array( $headers[$lower], $header ); } } /** * split a content type in its different parts * * @param string $type content-type * @param string $wantedPart the wanted part, else an array with all parts is returned * @return string|array wanted part or all parts as array('type' => content-type, partname => value) */ public static function splitContentType($type, $wantedPart = null) { return self::splitHeaderField($type, $wantedPart, 'type'); } /** * split a header field like content type in its different parts * * @param string $field * @param string $wantedPart the wanted part, else an array with all parts is returned * @param int|string $firstName key name for the first part * @throws Zend_Exception * @return string|array wanted part or all parts as array($firstName => firstPart, partname => value) */ public static function splitHeaderField( $field, $wantedPart = null, $firstName = 0 ) { $wantedPart = strtolower($wantedPart); $firstName = strtolower($firstName); // special case - a bit optimized if ($firstName === $wantedPart) { $field = strtok($field, ';'); return $field[0] == '"' ? substr($field, 1, -1) : $field; } $field = $firstName . '=' . $field; if (!preg_match_all('%([^=\s]+)\s*=\s*("[^"]+"|[^;]+)(;\s*|$)%', $field, $matches)) { throw new Zend_Exception('not a valid header field'); } if ($wantedPart) { foreach ($matches[1] as $key => $name) { if (strcasecmp($name, $wantedPart)) { continue; } if ($matches[2][$key][0] != '"') { return $matches[2][$key]; } return substr($matches[2][$key], 1, -1); } return null; } $split = array(); foreach ($matches[1] as $key => $name) { $name = strtolower($name); if ($matches[2][$key][0] == '"') { $split[$name] = substr($matches[2][$key], 1, -1); } else { $split[$name] = $matches[2][$key]; } } return $split; } /** * decode a quoted printable encoded string * * The charset of the returned string depends on your iconv settings. * * @param string $string Encoded string * @return string Decoded string */ public static function decodeQuotedPrintable($string) { return quoted_printable_decode($string); } /** cut the original splitMessage() function to get the raw headers, SJ */ public static function splitMessageRaw( $message, &$headers, &$body, $EOL = Zend_Mime::LINEEND ) { // check for valid header at first line $firstline = strtok($message, "\n"); if (!preg_match('%^[^\s]+[^:]*:%', $firstline)) { $headers = array(); // TODO: we're ignoring \r for now - is this function fast enough and is it safe to asume noone needs \r? $body = str_replace( array( "\r", "\n" ), array( '', $EOL ), $message ); return; } // find an empty line between headers and body // default is set new line if (strpos($message, $EOL . $EOL)) { list($headers, $body) = explode($EOL . $EOL, $message, 2); // next is the standard new line } else { if ($EOL != "\r\n" && strpos($message, "\r\n\r\n")) { list($headers, $body) = explode("\r\n\r\n", $message, 2); // next is the other "standard" new line } else { if ($EOL != "\n" && strpos($message, "\n\n")) { list($headers, $body) = explode("\n\n", $message, 2); // at last resort find anything that looks like a new line } else { @list($headers, $body) = @preg_split("%([\r\n]+)\\1%U", $message, 2); } } } } }