0

I'm using the below function to get attachments from an email (index $m) on an existing imap connection ($imap)

function parse_parts(&$structure, &$attachments, &$imap, $m, $par){
if(isset($structure->parts) && count($structure->parts) ) {
    for($i = 0; $i < count($structure->parts); $i++) {
        $attachments[] = array(
            'is_attachment' => false,
            'filename' => '',
            'name' => '',
            'attachment' => ''
        );
        $lstk= array_keys($attachments);  $j=end($lstk); // use this instead of $i to avoid overlapping indices when there's sub-parts and recursion

        if($structure->parts[$i]->ifdparameters) {
            foreach($structure->parts[$i]->dparameters as $object) {
                if(strtolower($object->attribute) == 'filename') {
                    $attachments[$j]['is_attachment'] = true;
                    $attachments[$j]['filename'] = $object->value;
                }
            }
        }
        if($structure->parts[$i]->ifparameters) {
            foreach($structure->parts[$i]->parameters as $object) {
                if(strtolower($object->attribute) == 'name') {
                    $attachments[$j]['is_attachment'] = true;
                    $attachments[$j]['filename'] = imap_utf8($object->value);
                }
            }
        }
        if($attachments[$j]['is_attachment']) {
            if($par==''){
                $attachments[$j]['attachment'] = imap_fetchbody($imap, $m,  $i+1);
            }else{
                $attachments[$j]['attachment'] = imap_fetchbody($imap, $m,  $par . $i+1);
            }
            if($structure->parts[$i]->encoding == 3) { // 3 = BASE64
                $attachments[$j]['attachment'] = base64_decode($attachments[$j]['attachment']);
            }
            elseif($structure->parts[$i]->encoding == 4) { // 4 = QUOTED-PRINTABLE
                $attachments[$j]['attachment'] = quoted_printable_decode($attachments[$j]['attachment']);
            }else{echo "encod:". $structure->parts[$i]->encoding . PHP_EOL; }
        } 
        if(isset($structure->parts[$i]->parts) && count($structure->parts[$i]->parts)){ //if the part contains its own parts, recurse
             $prnt = $i;
             $prnt = $prnt . '.';
             parse_parts($structure->parts[$i], $attachments, $imap, $m, $prnt );    
        }
    }
}

}

the later function which calls this looks like:

$structure = imap_fetchstructure($imap, $m);
$attachments = array();
parse_parts($structure, $attachments, $imap, $m, '');

the standard function works fine, but when it has to recurse (i.e. there are nested parts), the resulting file is not readable, though it does appear to be the correct size. Any idea what i'm doing wrong? i've tried changing $prnt to

$prnt = $i+1 .'.';

but then, it just outputs an empty file. i noticed that other people had similar issues here: imap - get attached file

but their solutions don't appear to work for this email/code.

Bobert1234
  • 130
  • 12

2 Answers2

0

thank you to IVO GELOV for finding my mistake. final working function:

function parse_parts(&$structure, &$attachments, &$imap, $m, $par){
if(isset($structure->parts) && count($structure->parts) ) {
    for($i = 0; $i < count($structure->parts); $i++) {
        $attachments[] = array(
            'is_attachment' => false,
            'filename' => '',
            'name' => '',
            'attachment' => ''
        );
        $lstk= array_keys($attachments);  $j=end($lstk); // use this instead of $i to avoid overlapping indices when there's sub-parts and recursion

        if($structure->parts[$i]->ifdparameters) {
            foreach($structure->parts[$i]->dparameters as $object) {
                if(strtolower($object->attribute) == 'filename') {
                    $attachments[$j]['is_attachment'] = true;
                    $attachments[$j]['filename'] = $object->value;
                }
            }
        }
        if($structure->parts[$i]->ifparameters) {
            foreach($structure->parts[$i]->parameters as $object) {
                if(strtolower($object->attribute) == 'name') {
                    $attachments[$j]['is_attachment'] = true;
                    $attachments[$j]['filename'] = imap_utf8($object->value);
                }
            }
        }
        if($attachments[$j]['is_attachment']) {
            if($par==''){
                $attachments[$j]['attachment'] = imap_fetchbody($imap, $m,  ($i+1));
            }else{
                $attachments[$j]['attachment'] = imap_fetchbody($imap, $m,  $par . ($i+1));
            }
            if($structure->parts[$i]->encoding == 3) { // 3 = BASE64
                $attachments[$j]['attachment'] = base64_decode($attachments[$j]['attachment']);
            }
            elseif($structure->parts[$i]->encoding == 4) { // 4 = QUOTED-PRINTABLE
                $attachments[$j]['attachment'] = quoted_printable_decode($attachments[$j]['attachment']);
            }else{echo "encod:". $structure->parts[$i]->encoding . PHP_EOL; }
        } 
        if(isset($structure->parts[$i]->parts) && count($structure->parts[$i]->parts)){ //if the part contains its own parts, recurse
             $prnt = $i+1;
             $prnt = $prnt . '.';
             parse_parts($structure->parts[$i], $attachments, $imap, $m, $prnt );    
        }
    }
}

}

Bobert1234
  • 130
  • 12
0

attachment may be inline or external file too.

so we have to get both type of attachments.

$structure = imap_fetchstructure($this->imap, $id);
    $fileName = $attachments = array();
    /* if any attachments found... */
    if (isset($structure->parts) && count($structure->parts)) {
        $n = 0;
        for($i = 0; $i < count($structure->parts); $i++){
            if(isset($structure->parts[$i]->parts) && !empty($structure->parts[$i]->parts) && count($structure->parts[$i]->parts)>0){
                for($y = 0; $y < count($structure->parts[$i]->parts); $y++) 
                {
                    $attachments[$n] = array('is_attachment' => false,'filename' => '','name' => '','attachment' => '','size' => '','cid' => 0);
                    if($structure->parts[$i]->parts[$y]->ifdparameters==1) {
                        foreach($structure->parts[$i]->parts[$y]->dparameters as $object){
                            if(strtolower($object->attribute) == 'filename'){
                                $attachments[$n]['is_attachment'] = true; 
                                $fileName[0] = $object->value;
                                $filename = $this->filterFileName($fileName);                               
                                $attachments[$n]['filename'] = $filename;
                                $attachments[$n]['name'] = $filename;
                            }
                        }
                    }
                    if($structure->parts[$i]->parts[$y]->ifparameters){
                        foreach($structure->parts[$i]->parts[$y]->parameters as $object){
                            if(strtolower($object->attribute) == 'name'){
                                $attachments[$n]['is_attachment'] = true;
                                $fileName[0] = $object->value;
                                $filename = $this->filterFileName($fileName);
                                $attachments[$n]['name'] = $filename;
                                $attachments[$n]['filename'] = $filename;
                            }
                        }
                    }
                    if($attachments[$n]['is_attachment']){
                        $attachments[$n]['attachment'] = imap_fetchbody($this->imap, $id, ($i+1).'.'.($y+1));                            
                        if($structure->parts[$i]->parts[$y]->encoding == 3){ /* 4 = QUOTED-PRINTABLE encoding */
                            $attachments[$n]['attachment'] = base64_decode($attachments[$n]['attachment']);
                        }                            
                        elseif($structure->parts[$i]->parts[$y]->encoding == 4){ /* 3 = BASE64 encoding */
                            $attachments[$n]['attachment'] = quoted_printable_decode($attachments[$n]['attachment']);
                        }
                        $attachments[$n]['size'] = $structure->parts[$i]->parts[$y]->bytes;
                        $attachments[$n]['cid'] = ($structure->parts[$i]->parts[$y]->ifid) ? str_replace(array('<', '>'), '', $structure->parts[$i]->parts[$y]->id) : 0;                            
                    }
                    $email['attachments'][] = $attachments[$n];$n++;
                }
            }else{
                $attachments[$n] = array('is_attachment' => false,'filename' => '','name' => '','attachment' => '','size' => '','cid' => 0);
                if($structure->parts[$i]->ifdparameters==1){
                    foreach($structure->parts[$i]->dparameters as $object){
                        if(strtolower($object->attribute) == 'filename'){
                            $attachments[$n]['is_attachment'] = true;
                            $fileName[0] = $object->value;
                            $filename = $this->filterFileName($fileName);
                            $attachments[$n]['filename'] = $filename;
                            $attachments[$n]['name'] = $filename;
                        }
                    }
                }
                if($structure->parts[$i]->ifparameters){
                    foreach($structure->parts[$i]->parameters as $object){
                        if(strtolower($object->attribute) == 'name'){
                            $attachments[$n]['is_attachment'] = true;
                            $fileName[0] = $object->value;
                            $filename = $this->filterFileName($fileName);
                            $attachments[$n]['name'] = $filename;
                            $attachments[$n]['filename'] = $filename;
                        }
                    }
                }
                if($attachments[$n]['is_attachment']){
                    $attachments[$n]['attachment'] = imap_fetchbody($this->imap, $id, $i+1);                        
                    if($structure->parts[$i]->encoding == 3){/* 4 = QUOTED-PRINTABLE encoding */ 
                        $attachments[$n]['attachment'] = base64_decode($attachments[$n]['attachment']);
                    }elseif($structure->parts[$i]->encoding == 4){ /* 3 = BASE64 encoding */
                        $attachments[$n]['attachment'] = quoted_printable_decode($attachments[$n]['attachment']);
                    }
                    $attachments[$n]['size'] = $structure->parts[$i]->bytes;
                    $attachments[$n]['cid'] = ($structure->parts[$i]->ifid) ? str_replace(array('<', '>'), '', $structure->parts[$i]->id) : 0;                        
                }
                $email['attachments'][] = $attachments[$n];$n++;
            }
        }            
    }
public function filterFileName($fileName) {
    $newstr = preg_replace('/[^a-zA-Z0-9\-\.\']/', '_', $fileName[0]);
    $fileName = str_replace("'", '', $newstr);
    return $fileName;       
}

after that you have to store this attachments

Dhara Bhalala
  • 224
  • 1
  • 5
  • first thing i notice about this is that it's non-recursive. i guess it's fine for the specific mac-based emails, but my preference was to be able to handle the structure no matter how many levels of parts there are. other than that, and the fact that you keep some additional info (size and cid) and a secondary array for associations with the email (email['attachments']) - all of which is generally useful but not in my use case - this code is basically identical to my earlier answer – Bobert1234 Sep 20 '18 at 10:06