Implements the following IMAP-related RFCs (see
http://www.iana.org/assignments/imap4-capabilities):
- RFC 2086/4314: ACL
- RFC 2087: QUOTA
- RFC 2088: LITERAL+
- RFC 2195: AUTH=CRAM-MD5
- RFC 2221: LOGIN-REFERRALS
- RFC 2342: NAMESPACE
- RFC 2595/4616: TLS & AUTH=PLAIN
- RFC 2831: DIGEST-MD5 authentication mechanism (obsoleted by RFC 6331)
- RFC 2971: ID
- RFC 3348: CHILDREN
- RFC 3501: IMAP4rev1 specification
- RFC 3502: MULTIAPPEND
- RFC 3516: BINARY
- RFC 3691: UNSELECT
- RFC 4315: UIDPLUS
- RFC 4422: SASL Authentication (for DIGEST-MD5)
- RFC 4466: Collected extensions (updates RFCs 2088, 3501, 3502, 3516)
- RFC 4469/5550: CATENATE
- RFC 4551: CONDSTORE
- RFC 4731: ESEARCH
- RFC 4959: SASL-IR
- RFC 5032: WITHIN
- RFC 5161: ENABLE
- RFC 5162: QRESYNC
- RFC 5182: SEARCHRES
- RFC 5255: LANGUAGE/I18NLEVEL
- RFC 5256: THREAD/SORT
- RFC 5258: LIST-EXTENDED
- RFC 5267: ESORT; PARTIAL search return option
- RFC 5464: METADATA
- RFC 5530: IMAP Response Codes
- RFC 5819: LIST-STATUS
- RFC 5957: SORT=DISPLAY
- RFC 6154: SPECIAL-USE/CREATE-SPECIAL-USE
- RFC 6203: SEARCH=FUZZY
Implements the following non-RFC extensions:
- draft-ietf-morg-inthread-01: THREAD=REFS
- draft-daboo-imap-annotatemore-07: ANNOTATEMORE
- draft-daboo-imap-annotatemore-08: ANNOTATEMORE2
- XIMAPPROXY
- Requires imapproxy v1.2.7-rc1 or later
- See https://squirrelmail.svn.sourceforge.net/svnroot/squirrelmail/trunk/imap_proxy/README
- RFC 2177: IDLE
- Probably not necessary due to the limited connection time of each HTTP/PHP request
- RFC 2193: MAILBOX-REFERRALS
- RFC 4467/5092/5524/5550/5593: URLAUTH, URLAUTH=BINARY, URL-PARTIAL
- RFC 4978: COMPRESS=DEFLATE
- See: http://bugs.php.net/bug.php?id=48725
- RFC 5257: ANNOTATE (Experimental)
- RFC 5259: CONVERT
- RFC 5267: CONTEXT=SEARCH; CONTEXT=SORT
- RFC 5465: NOTIFY
- RFC 5466: FILTERS
- RFC 5738: UTF8 (Very limited support currently)
- RFC 6237: MULTISEARCH
- draft-ietf-morg-inthread-01: SEARCH=INTHREAD
- Appears to be dead
- draft-krecicki-imap-move-01.txt: MOVE
- Appears to be dead
/** * Given an IMAP URL, fetches the corresponding part. * * @param Horde_Imap_Client_Url_Imap $url An IMAP URL. * * @return resource The section contents in a stream. Returns null if * the part could not be found. * * @throws Horde_Imap_Client_Exception */ public function fetchFromUrl(Horde_Imap_Client_Url_Imap $url) { $ids_ob = $this->_socket->getIdsOb($url->uid); // BODY[] if (is_null($url->section)) { $query = new Horde_Imap_Client_Fetch_Query(); $query->fullText(array('peek' => true)); $fetch = $this->_socket->fetch($url->mailbox, $query, array('ids' => $ids_ob)); return $fetch[$url->uid]->getFullMsg(true); } $section = trim($url->section); // BODY[<#.>HEADER.FIELDS<.NOT>()] if (($pos = stripos($section, 'HEADER.FIELDS')) !== false) { $hdr_pos = strpos($section, '('); $cmd = substr($section, 0, $hdr_pos); $query = new Horde_Imap_Client_Fetch_Query(); $query->headers('section', explode(' ', substr($section, $hdr_pos + 1, strrpos($section, ')') - $hdr_pos)), array('id' => $pos ? substr($section, 0, $pos - 1) : 0, 'notsearch' => stripos($cmd, '.NOT') !== false, 'peek' => true)); $fetch = $this->_socket->fetch($url->mailbox, $query, array('ids' => $ids_ob)); return $fetch[$url->uid]->getHeaders('section', Horde_Imap_Client_Data_Fetch::HEADER_STREAM); } // BODY[#] if (is_numeric(substr($section, -1))) { $query = new Horde_Imap_Client_Fetch_Query(); $query->bodyPart($section, array('peek' => true)); $fetch = $this->_socket->fetch($url->mailbox, $query, array('ids' => $ids_ob)); return $fetch[$url->uid]->getBodyPart($section, true); } // BODY[<#.>HEADER] if (($pos = stripos($section, 'HEADER')) !== false) { $id = $pos ? substr($section, 0, $pos - 1) : 0; $query = new Horde_Imap_Client_Fetch_Query(); $query->headerText(array('id' => $id, 'peek' => true)); $fetch = $this->_socket->fetch($url->mailbox, $query, array('ids' => $ids_ob)); return $fetch[$url->uid]->getHeaderText($id, Horde_Imap_Client_Data_Fetch::HEADER_STREAM); } // BODY[<#.>TEXT] if (($pos = stripos($section, 'TEXT')) !== false) { $id = $pos ? substr($section, 0, $pos - 1) : 0; $query = new Horde_Imap_Client_Fetch_Query(); $query->bodyText(array('id' => $id, 'peek' => true)); $fetch = $this->_socket->fetch($url->mailbox, $query, array('ids' => $ids_ob)); return $fetch[$url->uid]->getBodyText($id, true); } // BODY[<#.>MIMEHEADER] if (($pos = stripos($section, 'MIME')) !== false) { $id = $pos ? substr($section, 0, $pos - 1) : 0; $query = new Horde_Imap_Client_Fetch_Query(); $query->mimeHeader($id, array('peek' => true)); $fetch = $this->_socket->fetch($url->mailbox, $query, array('ids' => $ids_ob)); return $fetch[$url->uid]->getMimeHeader($id, Horde_Imap_Client_Data_Fetch::HEADER_STREAM); } return null; }
public function getImapConnection() { // // TODO: cache connections for / within accounts??? // $host = $this->info['host']; $user = $this->info['user']; $password = $this->info['password']; $port = $this->info['port']; $ssl_mode = $this->info['ssl_mode']; $client = new \Horde_Imap_Client_Socket(array('username' => $user, 'password' => $password, 'hostspec' => $host, 'port' => $port, 'secure' => $ssl_mode, 'timeout' => 2)); $client->login(); return $client; }
private function load() { $headers = []; $fetch_query = new \Horde_Imap_Client_Fetch_Query(); $fetch_query->bodyPart($this->attachmentId); $fetch_query->mimeHeader($this->attachmentId); $headers = array_merge($headers, ['importance', 'list-post', 'x-priority']); $headers[] = 'content-type'; $fetch_query->headers('imp', $headers, ['cache' => true]); // $list is an array of Horde_Imap_Client_Data_Fetch objects. $ids = new \Horde_Imap_Client_Ids($this->messageId); $headers = $this->conn->fetch($this->mailBox, $fetch_query, ['ids' => $ids]); /** @var $fetch Horde_Imap_Client_Data_Fetch */ if (!isset($headers[$this->messageId])) { throw new DoesNotExistException('Unable to load the attachment.'); } $fetch = $headers[$this->messageId]; $mimeHeaders = $fetch->getMimeHeader($this->attachmentId, Horde_Imap_Client_Data_Fetch::HEADER_PARSE); $this->mimePart = new \Horde_Mime_Part(); // To prevent potential problems with the SOP we serve all files with the // MIME type "application/octet-stream" $this->mimePart->setType('application/octet-stream'); // Serve all files with a content-disposition of "attachment" to prevent Cross-Site Scripting $this->mimePart->setDisposition('attachment'); // Extract headers from part $contentDisposition = $mimeHeaders->getValue('content-disposition', \Horde_Mime_Headers::VALUE_PARAMS); if (!is_null($contentDisposition)) { $vars = ['filename']; foreach ($contentDisposition as $key => $val) { if (in_array($key, $vars)) { $this->mimePart->setDispositionParameter($key, $val); } } } else { $contentDisposition = $mimeHeaders->getValue('content-type', \Horde_Mime_Headers::VALUE_PARAMS); $vars = ['name']; foreach ($contentDisposition as $key => $val) { if (in_array($key, $vars)) { $this->mimePart->setContentTypeParameter($key, $val); } } } /* Content transfer encoding. */ if ($tmp = $mimeHeaders->getValue('content-transfer-encoding')) { $this->mimePart->setTransferEncoding($tmp); } $body = $fetch->getBodyPart($this->attachmentId); $this->mimePart->setContents($body); }
/** */ public function valid(array $opts = array()) { if (empty($opts['users'])) { unset($opts['auth']); /* We still need a username as it is required by the IMAP * object. */ $opts['users'] = array('testing'); } switch ($this->tls) { case 'starttls': $secure = 'tls'; break; case 'tls': $secure = 'ssl'; break; default: $secure = !empty($opts['insecure']) ?: 'tls'; break; } foreach ($opts['users'] as $user) { try { $imap = new Horde_Imap_Client_Socket(array('hostspec' => $this->host, 'password' => isset($opts['auth']) ? $opts['auth'] : null, 'port' => $this->port, 'secure' => $secure, 'timeout' => 2, 'username' => $user)); if (isset($opts['auth'])) { $imap->login(); $this->username = $user; } else { $imap->noop(); } if ($secure === 'tls') { $this->tls = 'starttls'; } elseif ($secure === true) { $this->tls = $imap->isSecureConnection() ? 'starttls' : false; } $imap->shutdown(); return true; } catch (Horde_Imap_Client_Exception $e) { } } return false; }
/** * Sort search results client side if the server does not support the SORT * IMAP extension (RFC 5256). * * @param Horde_Imap_Client_Ids $res The search results. * @param array $opts The options to _search(). * * @return array The sort results. * * @throws Horde_Imap_Client_Exception */ public function clientSort($res, $opts) { if (!count($res)) { return $res; } /* Generate the FETCH command needed. */ $query = new Horde_Imap_Client_Fetch_Query(); foreach ($opts['sort'] as $val) { switch ($val) { case Horde_Imap_Client::SORT_ARRIVAL: $query->imapDate(); break; case Horde_Imap_Client::SORT_DATE: $query->imapDate(); $query->envelope(); break; case Horde_Imap_Client::SORT_CC: case Horde_Imap_Client::SORT_DISPLAYFROM: case Horde_Imap_Client::SORT_DISPLAYTO: case Horde_Imap_Client::SORT_FROM: case Horde_Imap_Client::SORT_SUBJECT: case Horde_Imap_Client::SORT_TO: $query->envelope(); break; case Horde_Imap_Client::SORT_SIZE: $query->size(); break; } } if (!count($query)) { return $res; } $mbox = $this->_socket->currentMailbox(); $fetch_res = $this->_socket->fetch($mbox['mailbox'], $query, array('ids' => $res)); return $this->_clientSortProcess($res->ids, $fetch_res, $opts['sort']); }
/** * @param \Horde_Mime_Part $p * @param int $partNo * @return string * @throws DoesNotExistException * @throws \Exception */ private function loadBodyData($p, $partNo) { // DECODE DATA $fetch_query = new \Horde_Imap_Client_Fetch_Query(); $ids = new \Horde_Imap_Client_Ids($this->messageId); $fetch_query->bodyPart($partNo, ['peek' => true]); $fetch_query->bodyPartSize($partNo); $fetch_query->mimeHeader($partNo, ['peek' => true]); $headers = $this->conn->fetch($this->mailBox, $fetch_query, ['ids' => $ids]); /** @var $fetch \Horde_Imap_Client_Data_Fetch */ $fetch = $headers[$this->messageId]; if (is_null($fetch)) { throw new DoesNotExistException("Mail body for this mail({$this->messageId}) could not be loaded"); } $mimeHeaders = $fetch->getMimeHeader($partNo, Horde_Imap_Client_Data_Fetch::HEADER_PARSE); if ($enc = $mimeHeaders->getValue('content-transfer-encoding')) { $p->setTransferEncoding($enc); } $data = $fetch->getBodyPart($partNo); $p->setContents($data); $data = $p->getContents(); $data = iconv($p->getCharset(), 'utf-8//IGNORE', $data); return $data; }
/** * Returns a Horde_Auth_Base driver for the given driver/configuration. * * @param string $driver Driver name. * @param array $orig_params Driver parameters. * * @return Horde_Auth_Base Authentication object. */ protected function _create($driver, $orig_params = null) { /* Get proper driver name now that we have grabbed the * configuration. */ if (strcasecmp($driver, 'application') === 0) { $driver = 'Horde_Core_Auth_Application'; } elseif (strcasecmp($driver, 'httpremote') === 0) { /* BC */ $driver = 'Http_Remote'; } elseif (strcasecmp($driver, 'composite') === 0) { $driver = 'Horde_Core_Auth_Composite'; } elseif (strcasecmp($driver, 'ldap') === 0) { $driver = 'Horde_Core_Auth_Ldap'; } elseif (strcasecmp($driver, 'msad') === 0) { $driver = 'Horde_Core_Auth_Msad'; } elseif (strcasecmp($driver, 'shibboleth') === 0) { $driver = 'Horde_Core_Auth_Shibboleth'; } elseif (strcasecmp($driver, 'imsp') === 0) { $driver = 'Horde_Core_Auth_Imsp'; } elseif (strcasecmp($driver, 'x509') === 0) { $driver = 'Horde_Core_Auth_X509'; } else { $driver = implode('_', array_map('Horde_String::ucwords', explode('_', Horde_String::lower(basename($driver))))); } $params = is_null($orig_params) ? Horde::getDriverConfig('auth', $driver) : $orig_params; $driver = $this->_getDriverName($driver, 'Horde_Auth'); $lc_driver = Horde_String::lower($driver); switch ($lc_driver) { case 'horde_core_auth_composite': // Both of these params are required, but we need to skip if // non-existent to return a useful error message later. if (!empty($params['admin_driver'])) { $params['admin_driver'] = $this->_create($params['admin_driver']['driver'], $params['admin_driver']['params']); } if (!empty($params['auth_driver'])) { $params['auth_driver'] = $this->_create($params['auth_driver']['driver'], $params['auth_driver']['params']); } break; case 'horde_auth_cyrsql': $imap_config = array('hostspec' => empty($params['cyrhost']) ? null : $params['cyrhost'], 'password' => $params['cyrpass'], 'port' => empty($params['cyrport']) ? null : $params['cyrport'], 'secure' => $params['secure'] == 'none' ? null : $params['secure'], 'username' => $params['cyradmin']); try { $ob = new Horde_Imap_Client_Socket($imap_config); $ob->login(); $params['imap'] = $ob; } catch (Horde_Imap_Client_Exception $e) { throw new Horde_Auth_Exception($e); } $params['db'] = $this->_injector->getInstance('Horde_Core_Factory_Db')->create('horde', is_null($orig_params) ? 'auth' : $orig_params); break; case 'horde_auth_http_remote': $params['client'] = $this->_injector->getInstance('Horde_Core_Factory_HttpClient')->create(); break; case 'horde_core_auth_application': if (isset($this->_instances[$params['app']])) { return $this->_instances[$params['app']]; } break; case 'horde_core_auth_imsp': $params['imsp'] = $this->_injector->getInstance('Horde_Core_Factory_Imsp')->create(); break; case 'horde_auth_kolab': $params['kolab'] = $this->_injector->getInstance('Horde_Kolab_Session'); break; case 'horde_core_auth_ldap': case 'horde_core_auth_msad': $params['ldap'] = $this->_injector->getInstance('Horde_Core_Factory_Ldap')->create('horde', is_null($orig_params) ? 'auth' : $orig_params); break; case 'horde_core_auth_x509': if (!empty($params['password_source']) && $params['password_source'] == 'unified') { $params['password'] = $params['unified_password']; unset($params['password_source'], $params['unified_password']); } // @TODO: Add filters break; case 'horde_auth_customsql': case 'horde_auth_sql': $params['db'] = $this->_injector->getInstance('Horde_Core_Factory_Db')->create('horde', is_null($orig_params) ? 'auth' : $orig_params); break; } $params['default_user'] = $GLOBALS['registry']->getAuth(); $params['logger'] = $this->_injector->getInstance('Horde_Log_Logger'); if (!empty($params['count_bad_logins'])) { $params['history_api'] = $this->_injector->getInstance('Horde_History'); } if (!empty($params['login_block'])) { $params['lock_api'] = $this->_injector->getInstance('Horde_Lock'); } $auth_ob = new $driver($params); if ($lc_driver == 'horde_core_auth_application') { $this->_instances[$params['app']] = $auth_ob; } return $auth_ob; }
private function getStatus($flags = \Horde_Imap_Client::STATUS_ALL) { return $this->conn->status($this->folder_id, $flags); }
private static function getImapConnection($account) { // // TODO: cash connections for / within accounts??? // $host = $account['host']; $user = $account['user']; $password = $account['password']; $port = $account['port']; $ssl_mode = $account['ssl_mode']; $client = new \Horde_Imap_Client_Socket(array('username' => $user, 'password' => $password, 'hostspec' => $host, 'port' => $port, 'secure' => $ssl_mode)); $client->login(); return $client; }