How to determine part numbers of an email retrieved via PHP IMAP functions?
Asked Answered
G

1

8

To fetch the particular section of the body of a message with imap_fetchbody(), you have to pass the section parameter which relates to the IMAP part number and is defined as follow in the PHP documentation:

It is a string of integers delimited by period which index into a body part list as per the IMAP4 specification

The only clue I have is to use imap_fetchstructure() to read the structure of a particular message. However, I don't know how to deduce the part numbers from it.

EDIT

For those interested in the IMAPv4 specification, here is the paragraph about fetching where part numbers are mentioned. Unfortunately, it does not clearly state how to get or compute them.

Gigot answered 8/6, 2017 at 17:51 Comment(2)
RFC 3501. :) It's not particularly clear about it, but there are examples which should help you understand.Diluvial
Not very clear indeed. I'm afraid the given examples are not enough to fully understand.Gigot
N
3

You are right, you have to use the result of the imap_fetchstructure() function.

You can get the full listing of parts and subpart of a mail by using the following function :

 function getPartList($struct, $base="") {
    $res=Array();
    if (!property_exists($struct,"parts")) {
            return [$base?:"0"];
    } else {
            $num=1;
            if (count($struct->parts)==1) return getPartList($struct->parts[0], $base);

            foreach ($struct->parts as $p=>$part) {
                    foreach (getPartList($part, $p+1) as $subpart) {
                            $res[]=($base?"$base.":"").$subpart;
                    }
            }
    }
    return $res;
 }

 $struct=imap_fetchstructure($mbox, $i);
 $res=getPartList($struct);
 print_r($res);

 Result:
 Array(
     [0] => 1
     [1] => 2
     [2] => 3.1
     [3] => 3.2
     [4] => 4.1
     [5] => 4.2
 );

EDIT: be aware that your server may not handle RFC822 subpart

For instance on a Dovecot v2.2 server, it returns an empty string

 telnet imapserver.com 143
 a1 LOGIN [email protected] password
 a2 SELECT "Inbox"
 a3 FETCH 522 (BODY[3.1.2])

 // result:

 * 522 FETCH (BODY[3.1.2] {0}
 )
 a3 OK Fetch completed.
 a4 logout

EDIT2 : it seems that subparts with only one part does not count... See modified code

Neophyte answered 12/6, 2017 at 13:32 Comment(9)
In fact, I previously came up with a similar solution. What remains intriguing to me is that the body of some fetched parts with imap_fetchbody() is empty. Moreover, some others expected parts seem to be missing such as included forwarded messages for instance. Do you know why?Gigot
@Gigot It might occur when you do not handle correctly the recursion. For instance, in my example, the part 3.1 will give you an empty body because it's a multipart and you have to ask for 3.1.1 and 3.1.2Neophyte
Replies and forwarded messages are generally not MIME parts, they're just part of the message and given a header or indented, unless 'Forwarded as Attachment'.Diluvial
@Neophyte I tried on a specific message, namely an undelivered one which was returned to sender. It turns out that whatever the implemented solution I try (yours or mine, both issuing the same results), some computed section numbers lead to empty fetched bodies. Here's the retrieved structure: gist.github.com/davidloubere/…. Returned sections are 1, 2, 3.1.1, 3.1.2 but sections 3.1.x are empty. That's why I'm skeptikal.Gigot
@Gigot This is due to the fact that PHP parses the initial full body to find the structure, but your server does not handle subordinate parts of a message/rfc822-part. See php.net/imap_fetchbody#31685Neophyte
@Neophyte Hum, okay. So, to parse an email retrieved by IMAP with PHP, your advice would be to parse the initial full body got from imap_body()?Gigot
@David, at least, you can fetch the RFC822 main part ("3.1") which works, and then parse it. I have the same problem interrogating directly my dovecot server. The "BODYSTRUCTURE" instruction retrieves the full structure tree while it's impossible to FETCH directly the sub partNeophyte
@Gigot I added one code line which seem to do the trick. When a part only encapsulate one part, then this appears to not count.Neophyte
Warning: imap_fetchbody() and imap_fetchstructure do not use the same structure. It works for most emails but it seems like attachments of forwarded emails get the wrong part number this way. The safest approach is clearly to download the email and parse it yourself. Alternatively, you can try to "fix" the structure: electrictoolbox.com/php-imap-message-partsDonative

© 2022 - 2024 — McMap. All rights reserved.