12

How do I get the attached file from this email?

This email is sent from an apple computer and the email struture is not like any other (surprise).. here the part with the disposition is one dimension deeper than else..

The script works with every other email where the part with the file is in the first dimension, but not with this one

$part->dparameters[0]->value returns the file name, but strlen($data) returns 0

imap stream

$structure = imap_fetchstructure($this->stream, $this->msgno);

if(isset($structure->parts)){
    print_r($structure->parts);
    $this->parse_parts($structure->parts);
}

function parse_parts($parts){
    foreach($parts as $section => $part){
        if(isset($part->parts)){

            // some mails have one extra dimension
            $this->parse_parts($part->parts);

        }
        elseif(isset($part->disposition)){
            if(in_array(strtolower($part->disposition), array('attachment','inline'))){
                $data = imap_fetchbody($this->stream, $this->msgno, $section+1);
                echo $part->dparameters[0]->value.' '.strlen($data)."\n";
            }
        }
    }
}

print_r

Array
(
    [0] => stdClass Object
        (
            [type] => 0
            [encoding] => 0
            [ifsubtype] => 1
            [subtype] => PLAIN
            [ifdescription] => 0
            [ifid] => 0
            [lines] => 15
            [bytes] => 173
            [ifdisposition] => 0
            [ifdparameters] => 0
            [ifparameters] => 1
            [parameters] => Array
                (
                    [0] => stdClass Object
                        (
                            [attribute] => CHARSET
                            [value] => us-ascii
                        )

                )

        )

    [1] => stdClass Object
        (
            [type] => 1
            [encoding] => 0
            [ifsubtype] => 1
            [subtype] => MIXED
            [ifdescription] => 0
            [ifid] => 0
            [bytes] => 23420
            [ifdisposition] => 0
            [ifdparameters] => 0
            [ifparameters] => 1
            [parameters] => Array
                (
                    [0] => stdClass Object
                        (
                            [attribute] => BOUNDARY
                            [value] => Apple-Mail=_800896E0-A9C9-456E-B063-79CED9DD4FD7
                        )

                )

            [parts] => Array
                (
                    [0] => stdClass Object
                        (
                            [type] => 0
                            [encoding] => 0
                            [ifsubtype] => 1
                            [subtype] => HTML
                            [ifdescription] => 0
                            [ifid] => 0
                            [bytes] => 136
                            [ifdisposition] => 0
                            [ifdparameters] => 0
                            [ifparameters] => 1
                            [parameters] => Array
                                (
                                    [0] => stdClass Object
                                        (
                                            [attribute] => CHARSET
                                            [value] => us-ascii
                                        )

                                )

                        )

                    [1] => stdClass Object
                        (
                            [type] => 3
                            [encoding] => 3
                            [ifsubtype] => 1
                            [subtype] => PDF
                            [ifdescription] => 0
                            [ifid] => 0
                            [bytes] => 17780
                            [ifdisposition] => 1
                            [disposition] => INLINE
                            [ifdparameters] => 1
                            [dparameters] => Array
                                (
                                    [0] => stdClass Object
                                        (
                                            [attribute] => FILENAME
                                            [value] => 057 - LPJ - Stik og labels.pdf
                                        )

                                )

                            [ifparameters] => 1
                            [parameters] => Array
                                (
                                    [0] => stdClass Object
                                        (
                                            [attribute] => NAME
                                            [value] => 057 - LPJ - Stik og labels.pdf
                                        )

                                )

                        )

                    [2] => stdClass Object
                        (
                            [type] => 0
                            [encoding] => 4
                            [ifsubtype] => 1
                            [subtype] => HTML
                            [ifdescription] => 0
                            [ifid] => 0
                            [lines] => 75
                            [bytes] => 4931
                            [ifdisposition] => 0
                            [ifdparameters] => 0
                            [ifparameters] => 1
                            [parameters] => Array
                                (
                                    [0] => stdClass Object
                                        (
                                            [attribute] => CHARSET
                                            [value] => us-ascii
                                        )

                                )

                        )

                )

        )

)
pixelistik
  • 7,541
  • 3
  • 32
  • 42
clarkk
  • 27,151
  • 72
  • 200
  • 340
  • I don't understand how an email sent *FROM* a Mac can be any different than any others. Have you compared the email headers to an email sent from another type of account to find out exactly how they vary? Unless by "from" you mean that that it's stored on an email server on a Mac. – Mike Jul 05 '12 at 21:30
  • The body structure is different than any other.. the part with the file is one dimension deeper in the structure than else – clarkk Jul 06 '12 at 10:33
  • I said mail headers. Look at the source code of the emails and compare them to see what's different. Compare how each of them sends the attachment and that might give you an idea what's going on. – Mike Jul 07 '12 at 22:55
  • I'd change $part->dparameters[0]->value to a foreach cycle, because sometimes Content-Disposition contains not only filename, but also other parameters (for example, creation-date) that can go first in the array. And to get the filename you should search for it: foreach($part->dparameters as $dp) { if (strcasecmp($dp->attribute, 'filename')==0) $filename=$dp->value; } – mistika Apr 28 '14 at 00:14
  • @Mike the problem isn't so much that it's sent from a mac, but that it's sent with 'mac mailer'. If you use thunderbird on a mac, no problem. – samlev Jun 05 '14 at 22:55
  • Perfect answer!!! https://stackoverflow.com/questions/2649579/downloading-attachments-to-directory-with-imap-in-php-randomly-works/34593248#34593248 – GuRu Aug 18 '17 at 07:13

4 Answers4

16

You are not providing the correct section number for nested attachments. You need to pass in the section number in the recursive step.

function parse_parts($parts, $parentsection = ""){
    foreach($parts as $subsection => $part){
        $section = $parentsection . ($subsection + 1);
        if(isset($part->parts)){

            // some mails have one extra dimension
            $this->parse_parts($part->parts, $section . "." );

        }
        elseif(isset($part->disposition)){
            if(in_array(strtolower($part->disposition), array('attachment','inline'))){
                $data = imap_fetchbody($this->stream, $this->msgno, $section );
                echo 'Getting section ' . $section;
                echo $part->dparameters[0]->value.' '.strlen($data)."\n";
            }
        }
    }
}

(Untested, but should do the right thing...)

Stobor
  • 44,246
  • 6
  • 66
  • 69
6

The next code shows you the directories and the email in the INBOX

    $mailbox = imap_open ("{correo.servidor.com:993/imap/ssl/novalidate-cert}INBOX", "correo@usuario.com", "PASSWORD");
    
    if (!$mailbox){
        die('murio');
    }
    
    echo "<h1>Buzones</h1>\n";
    $carpetas = imap_listmailbox($mailbox, "{correo.servidor.com:993}", "*");

    if ($carpetas == false) {
        echo "Llamada fallida<br />\n";
    } else {
        foreach ($carpetas as $val) {
            echo $val . "<br />\n";
        }
    }
    
    echo "<h1>Cabeceras en INBOX</h1>\n";
    $cabeceras = imap_headers($mailbox);

    if ($cabeceras == false) {
        echo "Llamada fallida<br />\n";
    } else {
        foreach ($cabeceras as $val) {
            echo $val . "<br />\n";
        }
    }
    
    
    
    $numMessages = imap_num_msg($mailbox);
    for ($i = $numMessages; $i > 0; $i--) {
        $header = imap_header($mailbox, $i);
     
        $fromInfo = $header->from[0];
        $replyInfo = $header->reply_to[0];
        
        // print_r($header);
     
        $details = array(
            "fromAddr" => (isset($fromInfo->mailbox) && isset($fromInfo->host))
                ? $fromInfo->mailbox . "@" . $fromInfo->host : "",
            "fromName" => (isset($fromInfo->personal))
                ? $fromInfo->personal : "",
            "replyAddr" => (isset($replyInfo->mailbox) && isset($replyInfo->host))
                ? $replyInfo->mailbox . "@" . $replyInfo->host : "",
            "replyName" => (isset($replyTo->personal))
                ? $replyto->personal : "",
            "subject" => (isset($header->subject))
                ? $header->subject : "",
            "udate" => (isset($header->udate))
                ? $header->udate : "",
            "Unseen" => (isset($header->Unseen))
                ? $header->Unseen  : "-"
        );
        $uid = imap_uid($mailbox, $i);
        
        echo "<ul>";
        echo "<li><strong>From:</strong>" . $details["fromName"];
        echo " " . $details["fromAddr"] . "</li>";
        echo "<li><strong>Subject:</strong> " . $details["subject"] . "</li>";
        echo "<li><strong>Estatus:</strong> " . $details["Unseen"] . "</li>";
        echo '<li><a href="test_imap_attachment.php?folder=' . $folder . '&uid=' . $i . '">Read</a></li>';
        echo "</ul>";
    }
    
    
    imap_close($mailbox);

The test_imap_attachment.php code that shows you the Attachments Files

function getAttachments($imap, $mailNum, $part, $partNum) {
    $attachments = array();
 
    if (isset($part->parts)) {
        foreach ($part->parts as $key => $subpart) {
            if($partNum != "") {
                $newPartNum = $partNum . "." . ($key + 1);
            }
            else {
                $newPartNum = ($key+1);
            }
            $result = getAttachments($imap, $mailNum, $subpart,
                $newPartNum);
            if (count($result) != 0) {
                 array_push($attachments, $result);
             }
        }
    }
    else if (isset($part->disposition)) {
        // print_r($part);
        if (strtoupper($part->disposition) == "ATTACHMENT") {
            $partStruct = imap_bodystruct($imap, $mailNum, $partNum);
            $attachmentDetails = array(
                "name"    => $part->dparameters[0]->value,
                "subtype" => $partStruct->subtype,
                "partNum" => $partNum,
                "enc"     => $partStruct->encoding
            );
            return $attachmentDetails;
        }
    }
 
    return $attachments;
}

$mailbox = imap_open ("{correo.servidor.com:993/imap/ssl/novalidate-cert}INBOX", "correo@usuario.com", "PASSWORD");

$uid = $_GET['uid'];

$mailStruct = imap_fetchstructure($mailbox, $uid);

$attachments = getAttachments($mailbox, $uid, $mailStruct, "");

echo "Attachments: ";
echo "<ul>";
foreach ($attachments as $attachment) {  
    echo '<li><a href="test_imap_download.php?func=' . $func . '&folder=' . $folder . '&uid=' . $uid .
        '&part=' . $attachment["partNum"] . '&enc=' . $attachment["enc"] . '">' .
        $attachment["name"] . "</a></li>";
}
echo "</ul>";

The next code save the file on the same server: test_imap_download.php

function downloadAttachment($imap, $uid) {
    $structure = imap_fetchstructure($imap, $uid);
    $attachments = '';
    if(isset($structure->parts) && count($structure->parts)) {
        for($i=0; $i<count($structure->parts); $i++) {
            if(strtoupper($structure->parts[$i]->disposition) == 'ATTACHMENT') {
        
                $attachments[$i] = array(
                    'is_attachment' => false,
                    'filename' => '',
                    'name' => '',
                    'attachment' => '');

                if($structure->parts[$i]->ifdparameters) {
                    foreach($structure->parts[$i]->dparameters as $object) {
                        if(strtolower($object->attribute) == 'filename') {
                            $attachments[$i]['is_attachment'] = true;
                            $attachments[$i]['filename'] = $object->value;
                        }
                    }
                }

                if($structure->parts[$i]->ifparameters) {
                    foreach($structure->parts[$i]->parameters as $object) {
                        if(strtolower($object->attribute) == 'name') {
                            $attachments[$i]['is_attachment'] = true;
                            $attachments[$i]['name'] = $object->value;
                        }
                    }
                }

                if($attachments[$i]['is_attachment']) {
                    $attachments[$i]['attachment'] = imap_fetchbody($imap, $uid, $i+1);
                    if($structure->parts[$i]->encoding == 3) { // 3 = BASE64
                        $attachments[$i]['attachment'] = base64_decode($attachments[$i]['attachment']);
                    }elseif($structure->parts[$i]->encoding == 4) { // 4 = QUOTED-PRINTABLE
                        $attachments[$i]['attachment'] = quoted_printable_decode($attachments[$i]['attachment']);
                    }
                }
                
                file_put_contents('directorio/'.$attachments[$i]['filename'], $attachments[$i]['attachment']);
                
            }
        } 
    }
}   

$mailbox = imap_open ("{correo.servidor.com:993/imap/ssl/novalidate-cert}INBOX", "correo@usuario.com", "PASSWORD");


$uid        = $_GET["uid"];
$partNum    = $_GET["partNum"];

downloadAttachment($mailbox, $uid);

I have used code from this pages:

http://www.sitepoint.com/exploring-phps-imap-library-2/

Downloading attachments to directory with IMAP in PHP, randomly works

jcromeros1987
  • 155
  • 1
  • 4
1

I don't know specifically about Mac emails, but here is some code I have that goes through each of the parts to see if it's an "ATTACHMENT" and saves the file somewhere so I can call it later.

$body = imap_fetchstructure($box, $i);
$attachments = '';
$att = count($body->parts);
if($att >=2) {
for($a=0;$a<$att;$a++) {
if($body->parts[$a]->disposition == 'ATTACHMENT') {
$file = imap_base64(imap_fetchbody($box, $i, $a+1));
$string = genRandomString();
    if(!file_exists('/var/www/email_store/'.$_SESSION['site_user_id'])) {
        mkdir('/var/www/email_store/'.$_SESSION['site_user_id'].'/');   
    }
    $attachments .= $body->parts[$a]->dparameters[0]->value.'[#]'.$string.',';
    file_put_contents('/var/www/email_store/'.$_SESSION['site_user_id'].'/'.$string,$file);
    }
}       
romo
  • 1,990
  • 11
  • 10
  • This assumes that the mail is stored locally. What if it's stored on a remote IMAP server? – Mike Jul 07 '12 at 22:52
  • That is true, but getting the attachment still works, you could just skip saving it locally and put it somewhere else. – romo Jul 09 '12 at 15:16
  • @Mike: It does not assume that the mail is stored locally - it contacts the IMAP server, gets the attached mail, and then saves it locally. – Stobor Jul 10 '12 at 00:59
0

I've taken code from other users here and I made for myself this function which gives me perfect overview about structure of an e-mail. The result of this function looks like this:

array (
    0 =>
        array (
            'section_num' => '1.1',
            'type' => 0,
            'subtype' => 'PLAIN',
            'size_bytes' => 128,
        ),
    1 =>
        array (
            'section_num' => '1.2.1',
            'type' => 0,
            'subtype' => 'HTML',
            'size_bytes' => 527,
        ),
    2 =>
        array (
            'section_num' => '1.2.2',
            'type' => 5,
            'subtype' => 'GIF',
            'disposition' => 'inline',
            'size_bytes' => 3304,
            'file_name' => 'logo_email.gif',
        ),
    3 =>
        array (
            'section_num' => '2',
            'type' => 3,
            'subtype' => 'VND.OPENXMLFORMATS-OFFICEDOCUMENT.SPREADSHEETML.SHEET',
            'disposition' => 'attachment',
            'size_bytes' => 14742,
            'file_name' => 'my_excel_table.xlsx',
        ),
)

Now it's very easy to traverse it and fetch any part of the email. For example if I want that excel file, here I fetch the section number '2':

$excelData = imap_fetchbody($imapConnection, $msgNumber, '2')

And finally here is my function which gives me this nice overview together with example how to use it:

$imapConnection = imap_open(..........);
$msgs = imap_search($imapConnection, ........);
$msgStructure = imap_fetchstructure($imapConnection, $msgNumber);

$resultOverview = []; // here the result will be stored
analyzeParts($msgStructure->parts, $resultOverview);

function analyzeParts($parts, &$structOverview, $parent = ''){
    foreach ($parts as $sectionNum => $part) {
        $sectionName = $parent . ($sectionNum+1);
        if (isset($part->parts)){
            analyzeParts($part->parts, $structOverview, $sectionName.'.');
        } else {
            $partInfo = [
                'section_num' => $sectionName,
                'type' => $part->type,
                'subtype' => $part->subtype,
            ];
            if (isset($part->bytes)) $partInfo['size_bytes'] = $part->bytes;
            if (isset($part->disposition)) {
                $partInfo['disposition'] = $part->disposition;
                if (in_array($part->disposition, ['attachment', 'inline'])){
                    foreach ($part->parameters as $parameter) {
                        if ($parameter->attribute === 'name')
                            $partInfo['file_name'] = $parameter->value;
                    }
                }
            }
            $structOverview[] = $partInfo;
        }
    }
}
Enriqe
  • 567
  • 1
  • 6
  • 22