/** * Build message part object * * @param array $part * @param int $count * @param string $parent */ protected function structure_part($part, $count = 0, $parent = '', $mime_headers = null) { $struct = new rcube_message_part(); $struct->mime_id = empty($parent) ? (string) $count : "{$parent}.{$count}"; // multipart if (is_array($part[0])) { $struct->ctype_primary = 'multipart'; /* RFC3501: BODYSTRUCTURE fields of multipart part part1 array part2 array part3 array .... 1. subtype 2. parameters (optional) 3. description (optional) 4. language (optional) 5. location (optional) */ // find first non-array entry for ($i = 1; $i < count($part); $i++) { if (!is_array($part[$i])) { $struct->ctype_secondary = strtolower($part[$i]); // read content type parameters if (is_array($part[$i + 1])) { $struct->ctype_parameters = array(); for ($j = 0; $j < count($part[$i + 1]); $j += 2) { $param = strtolower($part[$i + 1][$j]); $struct->ctype_parameters[$param] = $part[$i + 1][$j + 1]; } } break; } } $struct->mimetype = 'multipart/' . $struct->ctype_secondary; // build parts list for headers pre-fetching for ($i = 0; $i < count($part); $i++) { if (!is_array($part[$i])) { break; } // fetch message headers if message/rfc822 // or named part (could contain Content-Location header) if (!is_array($part[$i][0])) { $tmp_part_id = $struct->mime_id ? $struct->mime_id . '.' . ($i + 1) : $i + 1; if (strtolower($part[$i][0]) == 'message' && strtolower($part[$i][1]) == 'rfc822') { $mime_part_headers[] = $tmp_part_id; } else { if (in_array('name', (array) $part[$i][2]) && empty($part[$i][3])) { $mime_part_headers[] = $tmp_part_id; } } } } // pre-fetch headers of all parts (in one command for better performance) // @TODO: we could do this before _structure_part() call, to fetch // headers for parts on all levels if ($mime_part_headers) { $mime_part_headers = $this->conn->fetchMIMEHeaders($this->folder, $this->msg_uid, $mime_part_headers); } $struct->parts = array(); for ($i = 0, $count = 0; $i < count($part); $i++) { if (!is_array($part[$i])) { break; } $tmp_part_id = $struct->mime_id ? $struct->mime_id . '.' . ($i + 1) : $i + 1; $struct->parts[] = $this->structure_part($part[$i], ++$count, $struct->mime_id, $mime_part_headers[$tmp_part_id]); } return $struct; } /* RFC3501: BODYSTRUCTURE fields of non-multipart part 0. type 1. subtype 2. parameters 3. id 4. description 5. encoding 6. size -- text 7. lines -- message/rfc822 7. envelope structure 8. body structure 9. lines -- x. md5 (optional) x. disposition (optional) x. language (optional) x. location (optional) */ // regular part $struct->ctype_primary = strtolower($part[0]); $struct->ctype_secondary = strtolower($part[1]); $struct->mimetype = $struct->ctype_primary . '/' . $struct->ctype_secondary; // read content type parameters if (is_array($part[2])) { $struct->ctype_parameters = array(); for ($i = 0; $i < count($part[2]); $i += 2) { $struct->ctype_parameters[strtolower($part[2][$i])] = $part[2][$i + 1]; } if (isset($struct->ctype_parameters['charset'])) { $struct->charset = $struct->ctype_parameters['charset']; } } // #1487700: workaround for lack of charset in malformed structure if (empty($struct->charset) && !empty($mime_headers) && $mime_headers->charset) { $struct->charset = $mime_headers->charset; } // read content encoding if (!empty($part[5])) { $struct->encoding = strtolower($part[5]); $struct->headers['content-transfer-encoding'] = $struct->encoding; } // get part size if (!empty($part[6])) { $struct->size = intval($part[6]); } // read part disposition $di = 8; if ($struct->ctype_primary == 'text') { $di += 1; } else { if ($struct->mimetype == 'message/rfc822') { $di += 3; } } if (is_array($part[$di]) && count($part[$di]) == 2) { $struct->disposition = strtolower($part[$di][0]); if (is_array($part[$di][1])) { for ($n = 0; $n < count($part[$di][1]); $n += 2) { $struct->d_parameters[strtolower($part[$di][1][$n])] = $part[$di][1][$n + 1]; } } } // get message/rfc822's child-parts if (is_array($part[8]) && $di != 8) { $struct->parts = array(); for ($i = 0, $count = 0; $i < count($part[8]); $i++) { if (!is_array($part[8][$i])) { break; } $struct->parts[] = $this->structure_part($part[8][$i], ++$count, $struct->mime_id); } } // get part ID if (!empty($part[3])) { $struct->content_id = $part[3]; $struct->headers['content-id'] = $part[3]; if (empty($struct->disposition)) { $struct->disposition = 'inline'; } } // fetch message headers if message/rfc822 or named part (could contain Content-Location header) if ($struct->ctype_primary == 'message' || $struct->ctype_parameters['name'] && !$struct->content_id) { if (empty($mime_headers)) { $mime_headers = $this->conn->fetchPartHeader($this->folder, $this->msg_uid, true, $struct->mime_id); } if (is_string($mime_headers)) { $struct->headers = rcube_mime::parse_headers($mime_headers) + $struct->headers; } else { if (is_object($mime_headers)) { $struct->headers = get_object_vars($mime_headers) + $struct->headers; } } // get real content-type of message/rfc822 if ($struct->mimetype == 'message/rfc822') { // single-part if (!is_array($part[8][0])) { $struct->real_mimetype = strtolower($part[8][0] . '/' . $part[8][1]); } else { for ($n = 0; $n < count($part[8]); $n++) { if (!is_array($part[8][$n])) { break; } } $struct->real_mimetype = 'multipart/' . strtolower($part[8][$n]); } } if ($struct->ctype_primary == 'message' && empty($struct->parts)) { if (is_array($part[8]) && $di != 8) { $struct->parts[] = $this->structure_part($part[8], ++$count, $struct->mime_id); } } } // normalize filename property $this->set_part_filename($struct, $mime_headers); return $struct; }