<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[2185] 2013/codebykat/post-by-email/trunk: Switched to Horde IMAP library.</title>
</head>
<body>
<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; }
#msg dl a { font-weight: bold}
#msg dl a:link { color:#fc3; }
#msg dl a:active { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://gsoc.trac.wordpress.org/changeset/2185">2185</a></dd>
<dt>Author</dt> <dd>codebykat</dd>
<dt>Date</dt> <dd>2013-08-01 23:53:09 +0000 (Thu, 01 Aug 2013)</dd>
</dl>
<h3>Log Message</h3>
<pre>Switched to Horde IMAP library. Ugly but working.</pre>
<h3>Modified Paths</h3>
<ul>
<li><a href="#2013codebykatpostbyemailtrunkclasspostbyemailphp">2013/codebykat/post-by-email/trunk/class-post-by-email.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkpostbyemailphp">2013/codebykat/post-by-email/trunk/post-by-email.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkreadmetxt">2013/codebykat/post-by-email/trunk/readme.txt</a></li>
</ul>
<h3>Added Paths</h3>
<ul>
<li>2013/codebykat/post-by-email/trunk/include/</li>
<li>2013/codebykat/post-by-email/trunk/include/Horde/</li>
<li>2013/codebykat/post-by-email/trunk/include/Horde/Client/</li>
<li>2013/codebykat/post-by-email/trunk/include/Horde/Client/Auth/</li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientAuthDigestMD5php">2013/codebykat/post-by-email/trunk/include/Horde/Client/Auth/DigestMD5.php</a></li>
<li>2013/codebykat/post-by-email/trunk/include/Horde/Client/Base/</li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientBaseConnectionphp">2013/codebykat/post-by-email/trunk/include/Horde/Client/Base/Connection.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientBaseDebugphp">2013/codebykat/post-by-email/trunk/include/Horde/Client/Base/Debug.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientBaseDeprecatedphp">2013/codebykat/post-by-email/trunk/include/Horde/Client/Base/Deprecated.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientBaseMailboxphp">2013/codebykat/post-by-email/trunk/include/Horde/Client/Base/Mailbox.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientBasephp">2013/codebykat/post-by-email/trunk/include/Horde/Client/Base.php</a></li>
<li>2013/codebykat/post-by-email/trunk/include/Horde/Client/Cache/</li>
<li>2013/codebykat/post-by-email/trunk/include/Horde/Client/Cache/Backend/</li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientCacheBackendCachephp">2013/codebykat/post-by-email/trunk/include/Horde/Client/Cache/Backend/Cache.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientCacheBackendDbphp">2013/codebykat/post-by-email/trunk/include/Horde/Client/Cache/Backend/Db.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientCacheBackendMongophp">2013/codebykat/post-by-email/trunk/include/Horde/Client/Cache/Backend/Mongo.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientCacheBackendNullphp">2013/codebykat/post-by-email/trunk/include/Horde/Client/Cache/Backend/Null.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientCacheBackendphp">2013/codebykat/post-by-email/trunk/include/Horde/Client/Cache/Backend.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientCachephp">2013/codebykat/post-by-email/trunk/include/Horde/Client/Cache.php</a></li>
<li>2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/</li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientDataAclphp">2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Acl.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientDataAclCommonphp">2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/AclCommon.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientDataAclNegativephp">2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/AclNegative.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientDataAclRightsphp">2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/AclRights.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientDataBaseSubjectphp">2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/BaseSubject.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientDataEnvelopephp">2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Envelope.php</a></li>
<li>2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Fetch/</li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientDataFetchPop3php">2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Fetch/Pop3.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientDataFetchphp">2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Fetch.php</a></li>
<li>2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Format/</li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientDataFormatAstringphp">2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Format/Astring.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientDataFormatAtomphp">2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Format/Atom.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientDataFormatDatephp">2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Format/Date.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientDataFormatDateTimephp">2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Format/DateTime.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientDataFormatExceptionphp">2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Format/Exception.php</a></li>
<li>2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Format/Filter/</li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientDataFormatFilterQuotephp">2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Format/Filter/Quote.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientDataFormatFilterStringphp">2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Format/Filter/String.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientDataFormatListphp">2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Format/List.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientDataFormatListMailboxphp">2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Format/ListMailbox.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientDataFormatMailboxphp">2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Format/Mailbox.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientDataFormatNilphp">2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Format/Nil.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientDataFormatNstringphp">2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Format/Nstring.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientDataFormatNumberphp">2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Format/Number.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientDataFormatStringphp">2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Format/String.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientDataFormatphp">2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Format.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientDataSyncphp">2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Sync.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientDataThreadphp">2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Thread.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientDateTimephp">2013/codebykat/post-by-email/trunk/include/Horde/Client/DateTime.php</a></li>
<li>2013/codebykat/post-by-email/trunk/include/Horde/Client/Exception/</li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientExceptionNoSupportExtensionphp">2013/codebykat/post-by-email/trunk/include/Horde/Client/Exception/NoSupportExtension.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientExceptionNoSupportPop3php">2013/codebykat/post-by-email/trunk/include/Horde/Client/Exception/NoSupportPop3.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientExceptionSearchCharsetphp">2013/codebykat/post-by-email/trunk/include/Horde/Client/Exception/SearchCharset.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientExceptionServerResponsephp">2013/codebykat/post-by-email/trunk/include/Horde/Client/Exception/ServerResponse.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientExceptionSyncphp">2013/codebykat/post-by-email/trunk/include/Horde/Client/Exception/Sync.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientExceptionphp">2013/codebykat/post-by-email/trunk/include/Horde/Client/Exception.php</a></li>
<li>2013/codebykat/post-by-email/trunk/include/Horde/Client/Fetch/</li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientFetchQueryphp">2013/codebykat/post-by-email/trunk/include/Horde/Client/Fetch/Query.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientFetchResultsphp">2013/codebykat/post-by-email/trunk/include/Horde/Client/Fetch/Results.php</a></li>
<li>2013/codebykat/post-by-email/trunk/include/Horde/Client/Ids/</li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientIdsMapphp">2013/codebykat/post-by-email/trunk/include/Horde/Client/Ids/Map.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientIdsPop3php">2013/codebykat/post-by-email/trunk/include/Horde/Client/Ids/Pop3.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientIdsphp">2013/codebykat/post-by-email/trunk/include/Horde/Client/Ids.php</a></li>
<li>2013/codebykat/post-by-email/trunk/include/Horde/Client/Interaction/</li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientInteractionClientphp">2013/codebykat/post-by-email/trunk/include/Horde/Client/Interaction/Client.php</a></li>
<li>2013/codebykat/post-by-email/trunk/include/Horde/Client/Interaction/Command/</li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientInteractionCommandContinuationphp">2013/codebykat/post-by-email/trunk/include/Horde/Client/Interaction/Command/Continuation.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientInteractionCommandphp">2013/codebykat/post-by-email/trunk/include/Horde/Client/Interaction/Command.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientInteractionPipelinephp">2013/codebykat/post-by-email/trunk/include/Horde/Client/Interaction/Pipeline.php</a></li>
<li>2013/codebykat/post-by-email/trunk/include/Horde/Client/Interaction/Server/</li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientInteractionServerContinuationphp">2013/codebykat/post-by-email/trunk/include/Horde/Client/Interaction/Server/Continuation.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientInteractionServerTaggedphp">2013/codebykat/post-by-email/trunk/include/Horde/Client/Interaction/Server/Tagged.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientInteractionServerUntaggedphp">2013/codebykat/post-by-email/trunk/include/Horde/Client/Interaction/Server/Untagged.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientInteractionServerphp">2013/codebykat/post-by-email/trunk/include/Horde/Client/Interaction/Server.php</a></li>
<li>2013/codebykat/post-by-email/trunk/include/Horde/Client/Mailbox/</li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientMailboxListphp">2013/codebykat/post-by-email/trunk/include/Horde/Client/Mailbox/List.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientMailboxphp">2013/codebykat/post-by-email/trunk/include/Horde/Client/Mailbox.php</a></li>
<li>2013/codebykat/post-by-email/trunk/include/Horde/Client/Search/</li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientSearchQueryphp">2013/codebykat/post-by-email/trunk/include/Horde/Client/Search/Query.php</a></li>
<li>2013/codebykat/post-by-email/trunk/include/Horde/Client/Socket/</li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientSocketCatenatephp">2013/codebykat/post-by-email/trunk/include/Horde/Client/Socket/Catenate.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientSocketClientSortphp">2013/codebykat/post-by-email/trunk/include/Horde/Client/Socket/ClientSort.php</a></li>
<li>2013/codebykat/post-by-email/trunk/include/Horde/Client/Socket/Connection/</li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientSocketConnectionPop3php">2013/codebykat/post-by-email/trunk/include/Horde/Client/Socket/Connection/Pop3.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientSocketConnectionSocketphp">2013/codebykat/post-by-email/trunk/include/Horde/Client/Socket/Connection/Socket.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientSocketConnectionphp">2013/codebykat/post-by-email/trunk/include/Horde/Client/Socket/Connection.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientSocketPop3php">2013/codebykat/post-by-email/trunk/include/Horde/Client/Socket/Pop3.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientSocketphp">2013/codebykat/post-by-email/trunk/include/Horde/Client/Socket.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientTokenizephp">2013/codebykat/post-by-email/trunk/include/Horde/Client/Tokenize.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientUrlphp">2013/codebykat/post-by-email/trunk/include/Horde/Client/Url.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientUtf7imapphp">2013/codebykat/post-by-email/trunk/include/Horde/Client/Utf7imap.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeClientphp">2013/codebykat/post-by-email/trunk/include/Horde/Client.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeExceptionWrappedphp">2013/codebykat/post-by-email/trunk/include/Horde/Exception-Wrapped.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeExceptionphp">2013/codebykat/post-by-email/trunk/include/Horde/Exception.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeHordeUtilphp">2013/codebykat/post-by-email/trunk/include/Horde/Horde-Util.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeMailRfc822Addressphp">2013/codebykat/post-by-email/trunk/include/Horde/Mail-Rfc822-Address.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeMailRfc822Listphp">2013/codebykat/post-by-email/trunk/include/Horde/Mail-Rfc822-List.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeMailRfc822Objectphp">2013/codebykat/post-by-email/trunk/include/Horde/Mail-Rfc822-Object.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeMailRfc822php">2013/codebykat/post-by-email/trunk/include/Horde/Mail-Rfc822.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeMimeHeadersphp">2013/codebykat/post-by-email/trunk/include/Horde/Mime-Headers.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeMimePartphp">2013/codebykat/post-by-email/trunk/include/Horde/Mime-Part.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeMimephp">2013/codebykat/post-by-email/trunk/include/Horde/Mime.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeStreamFilterEolphp">2013/codebykat/post-by-email/trunk/include/Horde/Stream-Filter-Eol.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeStringphp">2013/codebykat/post-by-email/trunk/include/Horde/String.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeStubphp">2013/codebykat/post-by-email/trunk/include/Horde/Stub.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeSupportCaseInsensitiveArrayphp">2013/codebykat/post-by-email/trunk/include/Horde/Support-CaseInsensitiveArray.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeHordeSupportRandomidphp">2013/codebykat/post-by-email/trunk/include/Horde/Support-Randomid.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludeclasshordeimapclienttranslationphp">2013/codebykat/post-by-email/trunk/include/class-horde-imap-client-translation.php</a></li>
<li><a href="#2013codebykatpostbyemailtrunkincludehordewrapperphp">2013/codebykat/post-by-email/trunk/include/horde-wrapper.php</a></li>
</ul>
</div>
<div id="patch">
<h3>Diff</h3>
<a id="2013codebykatpostbyemailtrunkclasspostbyemailphp"></a>
<div class="modfile"><h4>Modified: 2013/codebykat/post-by-email/trunk/class-post-by-email.php (2184 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/class-post-by-email.php 2013-08-01 16:15:19 UTC (rev 2184)
+++ 2013/codebykat/post-by-email/trunk/class-post-by-email.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -24,7 +24,7 @@
</span><span class="cx"> *
</span><span class="cx"> * @var string
</span><span class="cx"> */
</span><del>- protected $version = '0.9.0';
</del><ins>+ protected $version = '0.9.5';
</ins><span class="cx">
</span><span class="cx"> /**
</span><span class="cx"> * Unique identifier for your plugin.
</span><span class="lines">@@ -189,8 +189,8 @@
</span><span class="cx"> */
</span><span class="cx"> public function check_email() {
</span><span class="cx">
</span><del>- /** Get the POP3 class with which to access the mailbox. */
- require_once( ABSPATH . WPINC . '/class-pop3.php' );
</del><ins>+ /** include the Horde IMAP client class */
+ require_once( plugin_dir_path( __FILE__ ) . 'include/horde-wrapper.php' );
</ins><span class="cx">
</span><span class="cx"> /** Only check at this interval for new messages. */
</span><span class="cx"> if ( ! defined( 'WP_MAIL_INTERVAL' ) )
</span><span class="lines">@@ -214,125 +214,137 @@
</span><span class="cx">
</span><span class="cx"> $phone_delim = '::';
</span><span class="cx">
</span><del>- $pop3 = new POP3();
</del><ins>+ $pop3 = new Horde_Imap_Client_Socket_Pop3( array('username' => $options['mailserver_login'],
+ 'password' => $options['mailserver_pass'],
+ 'hostspec' => $options['mailserver_url'],
+ 'port' => $options['mailserver_port'] ) );
+ $pop3->_setInit('authmethod', 'USER');
</ins><span class="cx">
</span><del>- if ( ! $pop3->connect( $options['mailserver_url'], $options['mailserver_port'] ) || ! $pop3->user( $options['mailserver_login'] ) )
- self::save_log_and_die( __( 'An error occurred: ') . esc_html( $pop3->ERROR ), $log );
</del><ins>+ try {
+ $pop3->login();
+ $test = $pop3->search( 'INBOX' );
+ $uids = $test['match'];
+ }
+ catch( Horde_Imap_Client_Exception $e ) {
+ self::save_log_and_die( __( 'An error occurred: ') . $e->getMessage(), $log );
+ }
</ins><span class="cx">
</span><del>- $count = $pop3->pass( $options['mailserver_pass'] );
-
- if( false === $count )
- self::save_log_and_die( __( 'An error occurred: ') . esc_html( $pop3->ERROR ), $log );
-
- if( 0 === $count ) {
- $pop3->quit();
</del><ins>+ if( 0 === sizeof( $uids ) ) {
+ $pop3->shutdown();
</ins><span class="cx"> self::save_log_and_die( __( 'There doesn’t seem to be any new mail.' ), $log );
</span><span class="cx"> }
</span><span class="cx">
</span><del>- for ( $i = 1; $i <= $count; $i++ ) {
</del><span class="cx">
</span><del>- $message = $pop3->get( $i );
</del><ins>+ foreach( $uids as $id ) {
+ $uid = new Horde_Imap_Client_Ids($id);
</ins><span class="cx">
</span><del>- $bodysignal = false;
- $boundary = '';
- $charset = '';
- $content = '';
- $content_type = '';
- $content_transfer_encoding = '';
</del><ins>+ // get headers
+ $headerquery = new Horde_Imap_Client_Fetch_Query();
+ $headerquery->headerText(array());
+ $headerlist = $pop3->fetch('INBOX', $headerquery, array(
+ 'ids' => $uid
+ ));
+
+ $headers = $headerlist->first()->getHeaderText(0, Horde_Imap_Client_Data_Fetch::HEADER_PARSE);
+
+ /* Subject */
+ // Captures any text in the subject before $phone_delim as the subject
+ $subject = $headers->getValue('Subject');
+ $subject = explode( $phone_delim, $subject );
+ $subject = $subject[0];
+
+ /* Author */
</ins><span class="cx"> $post_author = 1;
</span><span class="cx"> $author_found = false;
</span><del>- $dmonths = array( 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' );
- foreach ( $message as $line ) {
- // body signal
- if ( strlen( $line ) < 3 )
- $bodysignal = true;
- if ( $bodysignal ) {
- $content .= $line;
- } else {
- if ( preg_match( '/Content-Type: /i', $line ) ) {
- $content_type = trim( $line );
- $content_type = substr( $content_type, 14, strlen( $content_type ) - 14 );
- $content_type = explode( ';', $content_type );
- print_r($content_type);
- if ( ! empty( $content_type[1] ) ) {
- $charset = explode( '=', $content_type[1] );
- $charset = ( ! empty( $charset[1] ) ) ? trim( $charset[1] ) : '';
- }
- $content_type = $content_type[0];
- }
- if ( preg_match( '/Content-Transfer-Encoding: /i', $line ) ) {
- $content_transfer_encoding = trim( $line );
- $content_transfer_encoding = substr( $content_transfer_encoding, 27, strlen( $content_transfer_encoding ) - 27 );
- $content_transfer_encoding = explode( ';', $content_transfer_encoding );
- $content_transfer_encoding = $content_transfer_encoding[0];
- }
- if ( ( 'multipart/alternative' == $content_type ) && ( false !== strpos( $line, 'boundary="' ) ) && ( '' == $boundary ) ) {
- $boundary = trim( $line );
- $boundary = explode( '"', $boundary );
- $boundary = $boundary[1];
- }
- if ( preg_match( '/Subject: /i', $line ) ) {
- $subject = trim( $line );
- $subject = substr( $subject, 9, strlen( $subject ) - 9 );
- // Captures any text in the subject before $phone_delim as the subject
- if ( function_exists( 'iconv_mime_decode' ) ) {
- $subject = iconv_mime_decode( $subject, 2, get_option( 'blog_charset' ) );
- } else {
- $subject = wp_iso_descrambler( $subject );
- }
- $subject = explode( $phone_delim, $subject );
- $subject = $subject[0];
- }
</del><span class="cx">
</span><del>- // Set the author using the email address (From or Reply-To, the last used)
- // otherwise use the site admin
- if ( ! $author_found && preg_match( '/^(From|Reply-To): /', $line ) ) {
- if ( preg_match( '|[a-z0-9_.-]+@[a-z0-9_.-]+(?!.*<)|i', $line, $matches ) )
- $author = $matches[0];
- else
- $author = trim( $line );
- $author = sanitize_email( $author );
- if ( is_email( $author ) ) {
- $log['messages'][] = '<p>' . sprintf( __( 'Author is %s' ), $author ) . '</p>';
- $userdata = get_user_by( 'email', $author );
- if ( ! empty( $userdata ) ) {
- $post_author = $userdata->ID;
- $author_found = true;
- }
- }
</del><ins>+ // Set the author using the email address (From or Reply-To, the last used)
+ // otherwise use the site admin
+ $author = $headers->getValue('From');
+ $replyto = $headers->getValue('Reply-To');
+
+ if ( ! $author_found ) {
+ if ( preg_match( '|[a-z0-9_.-]+@[a-z0-9_.-]+(?!.*<)|i', $author, $matches ) )
+ $author = $matches[0];
+ else
+ $author = trim( $author );
+ $author = sanitize_email( $author );
+ if ( is_email( $author ) ) {
+ $log['messages'][] = '<p>' . sprintf( __( 'Author is %s' ), $author ) . '</p>';
+ $userdata = get_user_by( 'email', $author );
+ if ( ! empty( $userdata ) ) {
+ $post_author = $userdata->ID;
+ $author_found = true;
</ins><span class="cx"> }
</span><ins>+ }
+ }
</ins><span class="cx">
</span><del>- if ( preg_match( '/Date: /i', $line ) ) { // of the form '20 Mar 2002 20:32:37'
- $ddate = trim( $line );
- $ddate = str_replace( 'Date: ', '', $ddate );
- if ( strpos( $ddate, ',' ) ) {
- $ddate = trim( substr( $ddate, strpos( $ddate, ',' ) + 1, strlen( $ddate ) ) );
- }
- $date_arr = explode(' ', $ddate);
- $date_time = explode( ':', $date_arr[3] );
</del><span class="cx">
</span><del>- $ddate_H = $date_time[0];
- $ddate_i = $date_time[1];
- $ddate_s = $date_time[2];
</del><ins>+ /* Date */
+ $date = $headers->getValue('Date');
+ $dmonths = array( 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' );
</ins><span class="cx">
</span><del>- $ddate_m = $date_arr[1];
- $ddate_d = $date_arr[0];
- $ddate_Y = $date_arr[2];
- for ( $j = 0; $j < 12; $j++ ) {
- if ( $ddate_m == $dmonths[$j] ) {
- $ddate_m = $j+1;
- }
- }
</del><ins>+ // of the form '20 Mar 2002 20:32:37'
+ $ddate = trim( $date );
+ if ( strpos( $ddate, ',' ) ) {
+ $ddate = trim( substr( $ddate, strpos( $ddate, ',' ) + 1, strlen( $ddate ) ) );
+ }
</ins><span class="cx">
</span><del>- $time_zn = intval( $date_arr[4] ) * 36;
- $ddate_U = gmmktime( $ddate_H, $ddate_i, $ddate_s, $ddate_m, $ddate_d, $ddate_Y );
- $ddate_U = $ddate_U - $time_zn;
- $post_date = gmdate( 'Y-m-d H:i:s', $ddate_U + $time_difference );
- $post_date_gmt = gmdate( 'Y-m-d H:i:s', $ddate_U );
- }
</del><ins>+ $date_arr = explode(' ', $ddate);
+ $date_time = explode( ':', $date_arr[3] );
+
+ $ddate_H = $date_time[0];
+ $ddate_i = $date_time[1];
+ $ddate_s = $date_time[2];
+
+ $ddate_m = $date_arr[1];
+ $ddate_d = $date_arr[0];
+ $ddate_Y = $date_arr[2];
+
+ for ( $j = 0; $j < 12; $j++ ) {
+ if ( $ddate_m == $dmonths[$j] ) {
+ $ddate_m = $j+1;
</ins><span class="cx"> }
</span><span class="cx"> }
</span><span class="cx">
</span><ins>+ $time_zn = intval( $date_arr[4] ) * 36;
+ $ddate_U = gmmktime( $ddate_H, $ddate_i, $ddate_s, $ddate_m, $ddate_d, $ddate_Y );
+ $ddate_U = $ddate_U - $time_zn;
+ $post_date = gmdate( 'Y-m-d H:i:s', $ddate_U + $time_difference );
+ $post_date_gmt = gmdate( 'Y-m-d H:i:s', $ddate_U );
+
+
+
+ /* Message body */
+ $query = new Horde_Imap_Client_Fetch_Query();
+ $query->structure();
+
+ $list = $pop3->fetch('INBOX', $query, array(
+ 'ids' => $uid
+ ));
+
+ $part = $list->first()->getStructure();
+ $id = $part->findBody();
+ $body = $part->getPart($id);
+
+ $query2 = new Horde_Imap_Client_Fetch_Query();
+ $query2->bodyPart($id, array(
+ 'decode' => true,
+ 'peek' => true
+ ));
+
+ $list2 = $pop3->fetch('INBOX', $query2, array(
+ 'ids' => $uid
+ ));
+
+ $message2 = $list2->first();
+ $content = $message2->getBodyPart($id);
+ if (!$message2->getBodyPartDecode($id)) {
+ // Quick way to transfer decode contents
+ $body->setContents($content);
+ $content = $body->getContents();
+ }
+
+
</ins><span class="cx"> // Set $post_status based on $author_found and on author's publish_posts capability
</span><span class="cx"> if ( $author_found ) {
</span><span class="cx"> $user = new WP_User( $post_author );
</span><span class="lines">@@ -344,34 +356,13 @@
</span><span class="cx">
</span><span class="cx"> $subject = trim( $subject );
</span><span class="cx">
</span><del>- if ( 'multipart/alternative' == $content_type ) {
- $content = explode( '--'.$boundary, $content );
- $content = $content[2];
- // match case-insensitive content-transfer-encoding
- if ( preg_match( '/Content-Transfer-Encoding: quoted-printable/i', $content, $delim ) ) {
- $content = explode( $delim[0], $content );
- $content = $content[1];
- }
- if ( preg_match( '/Content-Transfer-Encoding: 7bit/i', $content, $delim ) ) {
- $content = explode( $delim[0], $content );
- $content = $content[1];
- }
- $content = strip_tags( $content, '<img><p><br><i><b><u><em><strong><strike><font><span><div>' );
- }
</del><ins>+ $content = strip_tags( $content, '<img><p><br><i><b><u><em><strong><strike><font><span><div>' );
</ins><span class="cx"> $content = trim( $content );
</span><span class="cx">
</span><span class="cx"> //Give Post-By-Email extending plugins full access to the content
</span><span class="cx"> //Either the raw content or the content of the last quoted-printable section
</span><span class="cx"> $content = apply_filters( 'wp_mail_original_content', $content );
</span><span class="cx">
</span><del>- if ( false !== stripos( $content_transfer_encoding, "quoted-printable" ) ) {
- $content = quoted_printable_decode( $content );
- }
-
- // if ( function_exists( 'iconv' ) && ! empty( $charset ) ) {
- // $content = iconv( $charset, get_option( 'blog_charset' ), $content );
- // }
-
</del><span class="cx"> // Captures any text in the body after $phone_delim as the body
</span><span class="cx"> $content = explode( $phone_delim, $content );
</span><span class="cx"> $content = empty( $content[1] ) ? $content[0] : $content[1];
</span><span class="lines">@@ -403,17 +394,21 @@
</span><span class="cx"> $log['messages'][] = "\n<p>" . sprintf( __( '<strong>Author:</strong> %s' ), esc_html( $post_author ) ) . '</p>';
</span><span class="cx"> $log['messages'][] = "\n<p>" . sprintf( __( '<strong>Posted title:</strong> %s' ), esc_html( $post_title ) ) . '</p>';
</span><span class="cx">
</span><del>- if( ! $pop3->delete( $i ) ) {
- $log['messages'][] = '<p>' . sprintf( __( 'Oops: %s' ), esc_html( $pop3->ERROR ) ) . '</p>';
- $pop3->reset();
- exit;
- } else {
- $log['messages'][] = '<p>' . sprintf( __( 'Mission complete. Message <strong>%s</strong> deleted.' ), $i ) . '</p>';
- }
</del><ins>+ } // end foreach
</ins><span class="cx">
</span><ins>+ // delete all processed emails
+ try {
+ $pop3->store( 'INBOX', array(
+ 'add' => array( Horde_Imap_Client::FLAG_DELETED ),
+ 'ids' => $uids
+ ) );
</ins><span class="cx"> }
</span><del>- $pop3->quit();
</del><ins>+ catch ( Horde_Imap_Client_Exception $e ) {
+ self::save_log_and_die( __( 'An error occurred: ') . $e->getMessage(), $log );
+ }
</ins><span class="cx">
</span><ins>+ $pop3->shutdown();
+
</ins><span class="cx"> foreach( $log['messages'] as $message ) { echo $message; }
</span><span class="cx"> update_option( 'post_by_email_log', $log );
</span><span class="cx"> }
</span></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientAuthDigestMD5php"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Auth/DigestMD5.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Auth/DigestMD5.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Auth/DigestMD5.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,186 @@
</span><ins>+<?php
+/**
+ * Copyright (c) 2002-2003 Richard Heyes
+ * Copyright 2011-2013 Horde LLC (http://www.horde.org/)
+ *
+ * This code is based on the original code contained in the PEAR Auth_SASL
+ * package (v0.5.1):
+ * $Id: DigestMD5.php 294702 2010-02-07 16:03:55Z cweiske $
+ *
+ * That code is covered by the BSD 3-Clause license, as set forth below:
+ * +-----------------------------------------------------------------------+
+ * | Copyright (c) 2002-2003 Richard Heyes |
+ * | All rights reserved. |
+ * | |
+ * | Redistribution and use in source and binary forms, with or without |
+ * | modification, are permitted provided that the following conditions |
+ * | are met: |
+ * | |
+ * | o Redistributions of source code must retain the above copyright |
+ * | notice, this list of conditions and the following disclaimer. |
+ * | o Redistributions in binary form must reproduce the above copyright |
+ * | notice, this list of conditions and the following disclaimer in the |
+ * | documentation and/or other materials provided with the distribution.|
+ * | o The names of the authors may not be used to endorse or promote |
+ * | products derived from this software without specific prior written |
+ * | permission. |
+ * | |
+ * | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
+ * | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
+ * | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
+ * | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
+ * | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
+ * | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
+ * | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
+ * | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
+ * | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
+ * | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
+ * | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
+ * +-----------------------------------------------------------------------+
+ *
+ * @category Horde
+ * @copyright 2002-2003 Richard Heyes
+ * @copyright 2011-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * Provides the code needed to authenticate via the DIGEST-MD5 SASL mechanism
+ * (defined in RFC 2831). This method has been obsoleted by RFC 6331, but
+ * still is in use on legacy servers.
+ *
+ * @author Richard Heyes <richard@php.net>
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @copyright 2002-2003 Richard Heyes
+ * @copyright 2011-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+class Horde_Imap_Client_Auth_DigestMD5
+{
+ /**
+ * Digest response components.
+ *
+ * @var string
+ */
+ protected $_response;
+
+ /**
+ * Generate the Digest-MD5 response.
+ *
+ * @param string $id Authentication id (username).
+ * @param string $pass Password.
+ * @param string $challenge The digest challenge sent by the server.
+ * @param string $hostname The hostname of the machine connecting to.
+ * @param string $service The service name (e.g. 'imap', 'pop3').
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ public function __construct($id, $pass, $challenge, $hostname, $service)
+ {
+ $challenge = $this->_parseChallenge($challenge);
+ $cnonce = $this->_getCnonce();
+ $digest_uri = sprintf('%s/%s', $service, $hostname);
+
+ /* Get response value. */
+ $A1 = sprintf('%s:%s:%s', pack('H32', hash('md5', sprintf('%s:%s:%s', $id, $challenge['realm'], $pass))), $challenge['nonce'], $cnonce);
+ $A2 = 'AUTHENTICATE:' . $digest_uri;
+ $response_value = hash('md5', sprintf('%s:%s:00000001:%s:auth:%s', hash('md5', $A1), $challenge['nonce'], $cnonce, hash('md5', $A2)));
+
+ $this->_response = array(
+ 'cnonce' => '"' . $cnonce . '"',
+ 'digest-uri' => '"' . $digest_uri . '"',
+ 'maxbuf' => $challenge['maxbuf'],
+ 'nc' => '00000001',
+ 'nonce' => '"' . $challenge['nonce'] . '"',
+ 'qop' => 'auth',
+ 'response' => $response_value,
+ 'username' => '"' . $id . '"'
+ );
+
+ if (strlen($challenge['realm'])) {
+ $this->_response['realm'] = '"' . $challenge['realm'] . '"';
+ }
+ }
+
+ /**
+ * Cooerce to string.
+ *
+ * @return string The digest response (not base64 encoded).
+ */
+ public function __toString()
+ {
+ $out = array();
+ foreach ($this->_response as $key => $val) {
+ $out[] = $key . '=' . $val;
+ }
+ return implode(',', $out);
+ }
+
+ /**
+ * Return specific digest response directive.
+ *
+ * @return mixed Requested directive, or null if it does not exist.
+ */
+ public function __get($name)
+ {
+ return isset($this->_response[$name])
+ ? $this->_response[$name]
+ : null;
+ }
+
+ /**
+ * Parses and verifies the digest challenge.
+ *
+ * @param string $challenge The digest challenge
+ *
+ * @return array The parsed challenge as an array with directives as keys.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ protected function _parseChallenge($challenge)
+ {
+ $tokens = array(
+ 'maxbuf' => 65536,
+ 'realm' => ''
+ );
+
+ preg_match_all('/([a-z-]+)=("[^"]+(?<!\\\)"|[^,]+)/i', $challenge, $matches, PREG_SET_ORDER);
+
+ foreach ($matches as $val) {
+ $tokens[$val[1]] = trim($val[2], '"');
+ }
+
+ // Required directives.
+ if (!isset($tokens['nonce']) || !isset($tokens['algorithm'])) {
+ throw new Horde_Imap_Client_Exception(Horde_Imap_Client_Translation::t("Authentication failure."), 'SERVER_CONNECT');
+ }
+
+ return $tokens;
+ }
+
+ /**
+ * Creates the client nonce for the response
+ *
+ * @return string The cnonce value.
+ */
+ protected function _getCnonce()
+ {
+ if ((@is_readable('/dev/urandom') &&
+ ($fd = @fopen('/dev/urandom', 'r'))) ||
+ (@is_readable('/dev/random') &&
+ ($fd = @fopen('/dev/random', 'r')))) {
+ $str = fread($fd, 32);
+ fclose($fd);
+ } else {
+ $str = '';
+ for ($i = 0; $i < 32; ++$i) {
+ $str .= chr(mt_rand(0, 255));
+ }
+ }
+
+ return base64_encode($str);
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientBaseConnectionphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Base/Connection.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Base/Connection.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Base/Connection.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,112 @@
</span><ins>+<?php
+/**
+ * Copyright 2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * Abstract interface for the object used to connect to remote mail server.
+ *
+ * NOTE: This class is NOT intended to be accessed outside of the package.
+ * There is NO guarantees that the API of this class will not change across
+ * versions.
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2013 Horde LLC
+ * @internal
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ *
+ * @property-read boolean $connected Is there a connection to the server?
+ * @property-read boolean $secure Is the connection secure?
+ */
+abstract class Horde_Imap_Client_Base_Connection
+{
+ /**
+ * Is there a connection to the server?
+ *
+ * @var boolean
+ */
+ protected $_connected = false;
+
+ /**
+ * Debug object.
+ *
+ * @var object
+ */
+ protected $_debug;
+
+ /**
+ * Is the connection secure?
+ *
+ * @var boolean
+ */
+ protected $_secure = false;
+
+ /**
+ * Constructor.
+ *
+ * @param Horde_Imap_Client_Base $base The base client object.
+ * @param object $debug The debug handler.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ public function __construct(Horde_Imap_Client_Base $base, $debug)
+ {
+ if ($base->getParam('secure') && !extension_loaded('openssl')) {
+ throw new InvalidArgumentException('Secure connections require the PHP openssl extension.');
+ }
+
+ $this->_debug = $debug;
+ }
+
+ /**
+ */
+ public function __get($name)
+ {
+ switch ($name) {
+ case 'connected':
+ return $this->_connected;
+
+ case 'secure':
+ return $this->_secure;
+ }
+ }
+
+ /**
+ * This object can not be cloned.
+ */
+ public function __clone()
+ {
+ throw new LogicException('Object cannot be cloned.');
+ }
+
+ /**
+ * This object can not be serialized.
+ */
+ public function __sleep()
+ {
+ throw new LogicException('Object can not be serialized.');
+ }
+
+ /**
+ * Start a TLS connection to the server.
+ *
+ * @return boolean Whether TLS was successfully started.
+ */
+ abstract public function startTls();
+
+ /**
+ * Close the connection to the server.
+ */
+ abstract public function close();
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientBaseDebugphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Base/Debug.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Base/Debug.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Base/Debug.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,152 @@
</span><ins>+<?php
+/**
+ * Copyright 2012-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * An object allowing management of debugging output within a
+ * Horde_Imap_Client_Base object.
+ *
+ * NOTE: This class is NOT intended to be accessed outside of a Base object.
+ * There is NO guarantees that the API of this class will not change across
+ * versions.
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @internal
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+class Horde_Imap_Client_Base_Debug
+{
+ /* Time, in seconds, to be labeled a slow command. */
+ const SLOW_CMD = 5;
+
+ /**
+ * Is debugging active?
+ *
+ * @var boolean
+ */
+ public $debug = true;
+
+ /**
+ * The debug stream.
+ *
+ * @var resource
+ */
+ protected $_stream;
+
+ /**
+ * Timestamp of last command.
+ *
+ * @var integer
+ */
+ protected $_time = null;
+
+ /**
+ * Constructor.
+ *
+ * @param mixed $debug The debug target.
+ */
+ public function __construct($debug)
+ {
+ $this->_stream = is_resource($debug)
+ ? $debug
+ : @fopen($debug, 'a');
+ register_shutdown_function(array($this, 'shutdown'));
+ }
+
+ /**
+ * Shutdown function.
+ */
+ public function shutdown()
+ {
+ if (is_resource($this->_stream)) {
+ fflush($this->_stream);
+ fclose($this->_stream);
+ $this->_stream = null;
+ }
+ }
+
+ /**
+ * Write client output to debug log.
+ *
+ * @param string $msg Debug message.
+ */
+ public function client($msg)
+ {
+ $this->_write($msg . "\n", 'C: ');
+ }
+
+ /**
+ * Write informational message to debug log.
+ *
+ * @param string $msg Debug message.
+ */
+ public function info($msg)
+ {
+ $this->_write($msg . "\n", '>> ');
+ }
+
+ /**
+ * Write server output to debug log.
+ *
+ * @param string $msg Debug message.
+ */
+ public function raw($msg)
+ {
+ $this->_write($msg);
+ }
+
+ /**
+ * Write server output to debug log.
+ *
+ * @param string $msg Debug message.
+ */
+ public function server($msg)
+ {
+ $this->_write($msg . "\n", 'S: ');
+ }
+
+ /**
+ * Write debug information to the output stream.
+ *
+ * @param string $msg Debug data.
+ */
+ protected function _write($msg, $pre = null)
+ {
+ if (!$this->debug || !$this->_stream) {
+ return;
+ }
+
+ if (!is_null($pre)) {
+ $new_time = microtime(true);
+
+ if (is_null($this->_time)) {
+ fwrite(
+ $this->_stream,
+ str_repeat('-', 30) . "\n" . '>> ' . date('r') . "\n"
+ );
+ } elseif (($diff = ($new_time - $this->_time)) > self::SLOW_CMD) {
+ fwrite(
+ $this->_stream,
+ '>> Slow Command: ' . round($diff, 3) . " seconds\n"
+ );
+ }
+
+ $this->_time = $new_time;
+ }
+
+ fwrite($this->_stream, $pre . $msg);
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientBaseDeprecatedphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Base/Deprecated.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Base/Deprecated.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Base/Deprecated.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,109 @@
</span><ins>+<?php
+/**
+ * Copyright 2012-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * Class containing deprecated Horde_Imap_Client_Base methods that will be
+ * removed in version 3.0.
+ *
+ * NOTE: This class is NOT intended to be accessed outside of a Base object.
+ * There is NO guarantees that the API of this class will not change across
+ * versions.
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @internal
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+class Horde_Imap_Client_Base_Deprecated
+{
+ /**
+ * Returns a unique identifier for the current mailbox status.
+ *
+ * @param Horde_Imap_Client_Base $base_ob The base driver object.
+ * @param mixed $mailbox A mailbox. Either a
+ * Horde_Imap_Client_Mailbox
+ * object or a string (UTF-8).
+ * @param boolean $condstore Is CONDSTORE enabled?
+ * @param array $addl Additional cache info to add to
+ * the cache ID string.
+ *
+ * @return string The cache ID string, which will change when the
+ * composition of the mailbox changes. The uidvalidity
+ * will always be the first element, and will be delimited
+ * by the '|' character.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ static public function getCacheId($base_ob, $mailbox, $condstore,
+ array $addl = array())
+ {
+ $query = Horde_Imap_Client::STATUS_UIDVALIDITY | Horde_Imap_Client::STATUS_MESSAGES | Horde_Imap_Client::STATUS_UIDNEXT;
+
+ /* Use MODSEQ as cache ID if CONDSTORE extension is available. */
+ if ($condstore) {
+ $query |= Horde_Imap_Client::STATUS_HIGHESTMODSEQ;
+ } else {
+ $query |= Horde_Imap_Client::STATUS_UIDNEXT_FORCE;
+ }
+
+ $status = $base_ob->status($mailbox, $query);
+
+ if (empty($status['highestmodseq'])) {
+ $parts = array(
+ 'V' . $status['uidvalidity'],
+ 'U' . $status['uidnext'],
+ 'M' . $status['messages']
+ );
+ } else {
+ $parts = array(
+ 'V' . $status['uidvalidity'],
+ 'H' . $status['highestmodseq']
+ );
+ }
+
+ return implode('|', array_merge($parts, $addl));
+ }
+
+ /**
+ * Parses a cacheID created by getCacheId().
+ *
+ * @param string $id The cache ID.
+ *
+ * @return array An array with the following information:
+ * - highestmodseq: (integer)
+ * - messages: (integer)
+ * - uidnext: (integer)
+ * - uidvalidity: (integer) Always present
+ */
+ static public function parseCacheId($id)
+ {
+ $data = array(
+ 'H' => 'highestmodseq',
+ 'M' => 'messages',
+ 'U' => 'uidnext',
+ 'V' => 'uidvalidity'
+ );
+ $info = array();
+
+ foreach (explode('|', $id) as $part) {
+ if (isset($data[$part[0]])) {
+ $info[$data[$part[0]]] = intval(substr($part, 1));
+ }
+ }
+
+ return $info;
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientBaseMailboxphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Base/Mailbox.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Base/Mailbox.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Base/Mailbox.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,176 @@
</span><ins>+<?php
+/**
+ * Copyright 2012-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * An object allowing management of mailbox state within a
+ * Horde_Imap_Client_Base object.
+ *
+ * NOTE: This class is NOT intended to be accessed outside of a Base object.
+ * There is NO guarantees that the API of this class will not change across
+ * versions.
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @internal
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+class Horde_Imap_Client_Base_Mailbox
+{
+ /**
+ * Mapping object.
+ *
+ * @var Horde_Imap_Client_Ids_Map
+ */
+ public $map;
+
+ /**
+ * Is mailbox opened?
+ *
+ * @var boolean
+ */
+ public $open;
+
+ /**
+ * Is mailbox sync'd with remote server (via CONDSTORE/QRESYNC)?
+ *
+ * @var boolean
+ */
+ public $sync;
+
+ /**
+ * Status information.
+ *
+ * @var array
+ */
+ protected $_status = array();
+
+ /**
+ * Constructor.
+ */
+ public function __construct()
+ {
+ $this->reset();
+ }
+
+ /**
+ * Get status information for the mailbox.
+ *
+ * @param integer $entry STATUS_* constant.
+ *
+ * @return mixed Status information.
+ */
+ public function getStatus($entry)
+ {
+ if (isset($this->_status[$entry])) {
+ return $this->_status[$entry];
+ }
+
+ switch ($entry) {
+ case Horde_Imap_Client::STATUS_FIRSTUNSEEN:
+ /* If we know there are no messages in the current mailbox, we
+ * know there are no unseen messages. */
+ return empty($this->_status[Horde_Imap_Client::STATUS_MESSAGES])
+ ? false
+ : null;
+
+ case Horde_Imap_Client::STATUS_RECENT_TOTAL:
+ case Horde_Imap_Client::STATUS_SYNCMODSEQ:
+ return 0;
+
+ case Horde_Imap_Client::STATUS_SYNCFLAGUIDS:
+ case Horde_Imap_Client::STATUS_SYNCVANISHED:
+ return array();
+
+ case Horde_Imap_Client::STATUS_PERMFLAGS:
+ /* If PERMFLAGS is not returned by server, must assume that all
+ * flags can be change permanently (RFC 3501 [6.3.1]). */
+ $flags = isset($this->_status[Horde_Imap_Client::STATUS_FLAGS])
+ ? $this->_status[Horde_Imap_Client::STATUS_FLAGS]
+ : array();
+ $flags[] = "\\*";
+ return $flags;
+
+ case Horde_Imap_Client::STATUS_UIDNOTSTICKY:
+ /* In the absence of explicit uidnotsticky identification, assume
+ * that UIDs are sticky. */
+ return false;
+
+ case Horde_Imap_Client::STATUS_UNSEEN:
+ /* If we know there are no messages in the current mailbox, we
+ * know there are no unseen messages . */
+ return empty($this->_status[Horde_Imap_Client::STATUS_MESSAGES])
+ ? 0
+ : null;
+
+ default:
+ return null;
+ }
+ }
+
+ /**
+ * Set status information for the mailbox.
+ *
+ * @param integer $entry STATUS_* constant.
+ * @param mixed $value Status information.
+ */
+ public function setStatus($entry, $value)
+ {
+ switch ($entry) {
+ case Horde_Imap_Client::STATUS_RECENT:
+ /* Keep track of RECENT_TOTAL information. */
+ $this->_status[Horde_Imap_Client::STATUS_RECENT_TOTAL] = isset($this->_status[Horde_Imap_Client::STATUS_RECENT_TOTAL])
+ ? ($this->_status[Horde_Imap_Client::STATUS_RECENT_TOTAL] + $value)
+ : $value;
+ break;
+
+ case Horde_Imap_Client::STATUS_SYNCMODSEQ:
+ /* This is only set once per access. */
+ if (isset($this->_status[$entry])) {
+ return;
+ }
+ break;
+
+ case Horde_Imap_Client::STATUS_SYNCFLAGUIDS:
+ case Horde_Imap_Client::STATUS_SYNCVANISHED:
+ if (!isset($this->_status[$entry])) {
+ $this->_status[$entry] = array();
+ }
+ $this->_status[$entry] = array_merge($this->_status[$entry], $value);
+ return;
+ }
+
+ $this->_status[$entry] = $value;
+ }
+
+ /**
+ * Reset the mailbox information.
+ */
+ public function reset()
+ {
+ $keep = array(
+ Horde_Imap_Client::STATUS_SYNCFLAGUIDS,
+ Horde_Imap_Client::STATUS_SYNCMODSEQ,
+ Horde_Imap_Client::STATUS_SYNCVANISHED
+ );
+
+ foreach (array_diff(array_keys($this->_status), $keep) as $val) {
+ unset($this->_status[$val]);
+ }
+
+ $this->map = new Horde_Imap_Client_Ids_Map();
+ $this->open = $this->sync = false;
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientBasephp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Base.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Base.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Base.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,4102 @@
</span><ins>+<?php
+/**
+ * Copyright 2008-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2008-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * An abstracted API interface to IMAP backends supporting the IMAP4rev1
+ * protocol (RFC 3501).
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2008-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+abstract class Horde_Imap_Client_Base implements Serializable
+{
+ /* Serialized version. */
+ const VERSION = 2;
+
+ /* Cache names for miscellaneous data. */
+ const CACHE_MODSEQ = '_m';
+ const CACHE_SEARCH = '_s';
+ /* @since 2.9.0 */
+ const CACHE_SEARCHID = '_i';
+
+ /* Cache names used exclusively within this class. @since 2.11.0 */
+ const CACHE_DOWNGRADED = 'HICdg';
+
+ /**
+ * The list of fetch fields that can be cached, and their cache names.
+ *
+ * @var array
+ */
+ public $cacheFields = array(
+ Horde_Imap_Client::FETCH_ENVELOPE => 'HICenv',
+ Horde_Imap_Client::FETCH_FLAGS => 'HICflags',
+ Horde_Imap_Client::FETCH_HEADERS => 'HIChdrs',
+ Horde_Imap_Client::FETCH_IMAPDATE => 'HICdate',
+ Horde_Imap_Client::FETCH_SIZE => 'HICsize',
+ Horde_Imap_Client::FETCH_STRUCTURE => 'HICstruct'
+ );
+
+ /**
+ * Has the internal configuration changed?
+ *
+ * @var boolean
+ */
+ public $changed = false;
+
+ /**
+ * The Horde_Imap_Client_Cache object.
+ *
+ * @var Horde_Imap_Client_Cache
+ */
+ protected $_cache = null;
+
+ /**
+ * Connection to the IMAP server.
+ *
+ * @var Horde_Imap_Client_Base_Connection
+ */
+ protected $_connection = null;
+
+ /**
+ * The debug object.
+ *
+ * @var Horde_Imap_Client_Base_Debug
+ */
+ protected $_debug = null;
+
+ /**
+ * The fetch data object type to return.
+ *
+ * @var string
+ */
+ protected $_fetchDataClass = 'Horde_Imap_Client_Data_Fetch';
+
+ /**
+ * Cached server data.
+ *
+ * @var array
+ */
+ protected $_init;
+
+ /**
+ * Is there an active authenticated connection to the IMAP Server?
+ *
+ * @var boolean
+ */
+ protected $_isAuthenticated = false;
+
+ /**
+ * The current mailbox selection mode.
+ *
+ * @var integer
+ */
+ protected $_mode = 0;
+
+ /**
+ * Hash containing connection parameters.
+ * This hash never changes.
+ *
+ * @var array
+ */
+ protected $_params = array();
+
+ /**
+ * The currently selected mailbox.
+ *
+ * @var Horde_Imap_Client_Mailbox
+ */
+ protected $_selected = null;
+
+ /**
+ * Temp array (destroyed at end of process).
+ *
+ * @var array
+ */
+ protected $_temp = array(
+ 'enabled' => array()
+ );
+
+ /**
+ * Constructor.
+ *
+ * @param array $params Configuration parameters:
+ * <ul>
+ * <li>REQUIRED Parameters
+ * <ul>
+ * <li>password: (string) The IMAP user password.</li>
+ * <li>username: (string) The IMAP username.</li>
+ * </ul>
+ * </li>
+ * <li>Optional Parameters
+ * <ul>
+ * <li>
+ * cache: (array) If set, caches data from fetch(), search(), and
+ * thread() calls. Requires the horde/Cache package to be
+ * installed. The array can contain the following keys (see
+ * Horde_Imap_Client_Cache for default values):
+ * <ul>
+ * <li>
+ * backend: [REQUIRED (or cacheob)] (Horde_Imap_Client_Cache_Backend)
+ * Backend cache driver (as of 2.9.0).
+ * </li>
+ * <li>
+ * cacheob: [REQUIRED (or backend)] (Horde_Cache) The cache object to
+ * use (DEPRECATED).
+ * </li>
+ * <li>
+ * fetch_ignore: (array) A list of mailboxes to ignore when storing
+ * fetch data.
+ * </li>
+ * <li>
+ * fields: (array) The fetch criteria to cache. If not defined, all
+ * cacheable data is cached. The following is a list of
+ * criteria that can be cached:
+ * <ul>
+ * <li>Horde_Imap_Client::FETCH_ENVELOPE</li>
+ * <li>Horde_Imap_Client::FETCH_FLAGS
+ * <ul>
+ * <li>
+ * Only if server supports CONDSTORE extension
+ * </li>
+ * </ul>
+ * </li>
+ * <li>Horde_Imap_Client::FETCH_HEADERS
+ * <ul>
+ * <li>
+ * Only for queries that specifically request caching
+ * </li>
+ * </ul>
+ * </li>
+ * <li>Horde_Imap_Client::FETCH_IMAPDATE</li>
+ * <li>Horde_Imap_Client::FETCH_SIZE</li>
+ * <li>Horde_Imap_Client::FETCH_STRUCTURE</li>
+ * </ul>
+ * </li>
+ * </ul>
+ * </li>
+ * <li>
+ * capability_ignore: (array) A list of IMAP capabilites to ignore,
+ * even if they are supported on the server.
+ * DEFAULT: No supported capabilities are ignored.
+ * </li>
+ * <li>
+ * comparator: (string) The search comparator to use instead of the
+ * default IMAP server comparator. See
+ * Horde_Imap_Client_Base#setComparator() for format.
+ * DEFAULT: Use the server default
+ * </li>
+ * <li>
+ * debug: (string) If set, will output debug information to the stream
+ * provided. The value can be any PHP supported wrapper that
+ * can be opened via fopen().
+ * DEFAULT: No debug output
+ * </li>
+ * <li>
+ * encryptKey: (array) A callback to a function that returns the key
+ * used to encrypt the password. This function MUST be
+ * static.
+ * DEFAULT: No encryption
+ * </li>
+ * <li>
+ * hostspec: (string) The hostname or IP address of the server.
+ * DEFAULT: 'localhost'
+ * </li>
+ * <li>
+ * id: (array) Send ID information to the IMAP server (only if server
+ * supports the ID extension). An array with the keys as the
+ * fields to send and the values being the associated values. See
+ * RFC 2971 [3.3] for a list of defined standard field values.
+ * DEFAULT: No info sent to server
+ * </li>
+ * <li>
+ * lang: (array) A list of languages (in priority order) to be used to
+ * display human readable messages.
+ * DEFAULT: Messages output in IMAP server default language
+ * </li>
+ * <li>
+ * port: (integer) The server port to which we will connect.
+ * DEFAULT: 143 (imap or imap w/TLS) or 993 (imaps)
+ * </li>
+ * <li>
+ * secure: (string) Use SSL or TLS to connect.
+ * VALUES:
+ * <ul>
+ * <li>false</li>
+ * <li>'ssl' (Auto-detect SSL version)</li>
+ * <li>'sslv2' (Force SSL version 3)</li>
+ * <li>'sslv3' (Force SSL version 2)</li>
+ * <li>'tls'</li>
+ * </ul>
+ * DEFAULT: No encryption</li>
+ * </li>
+ * <li>
+ * timeout: (integer) Connection timeout, in seconds.
+ * DEFAULT: 30 seconds
+ * </li>
+ * </ul>
+ * </li>
+ * </ul>
+ */
+ public function __construct(array $params = array())
+ {
+ if (!isset($params['username']) || !isset($params['password'])) {
+ throw new InvalidArgumentException('Horde_Imap_Client requires a username and password.');
+ }
+
+ $this->_setInit();
+
+ // Default values.
+ $params = array_merge(array(
+ 'encryptKey' => null,
+ 'hostspec' => 'localhost',
+ 'secure' => false,
+ 'timeout' => 30
+ ), array_filter($params));
+
+ if ($params['secure'] === true) {
+ $params['secure'] = 'tls';
+ }
+
+ if (!isset($params['port'])) {
+ $params['port'] = (isset($params['secure']) && in_array($params['secure'], array('ssl', 'sslv2', 'sslv3')))
+ ? 993
+ : 143;
+ }
+
+ if (empty($params['cache'])) {
+ $params['cache'] = array('fields' => array());
+ } elseif (empty($params['cache']['fields'])) {
+ $params['cache']['fields'] = $this->cacheFields;
+ } else {
+ $params['cache']['fields'] = array_flip($params['cache']['fields']);
+ }
+
+ if (empty($params['cache']['fetch_ignore'])) {
+ $params['cache']['fetch_ignore'] = array();
+ }
+
+ $this->_params = $params;
+ $this->setParam('password', $params['password']);
+
+ $this->changed = true;
+ $this->_initOb();
+ }
+
+ /**
+ * Get encryption key.
+ *
+ * @return string The encryption key.
+ */
+ protected function _getEncryptKey()
+ {
+ if (is_callable($ekey = $this->getParam('encryptKey'))) {
+ return call_user_func($ekey);
+ }
+
+ throw new InvalidArgumentException('encryptKey parameter is not a valid callback.');
+ }
+
+ /**
+ * Do initialization tasks.
+ */
+ protected function _initOb()
+ {
+ register_shutdown_function(array($this, 'shutdown'));
+ $this->_debug = ($debug = $this->getParam('debug'))
+ ? new Horde_Imap_Client_Base_Debug($debug)
+ : new Horde_Support_Stub();
+ }
+
+ /**
+ * Shutdown actions.
+ */
+ public function shutdown()
+ {
+ $this->logout();
+ }
+
+ /**
+ * This object can not be cloned.
+ */
+ public function __clone()
+ {
+ throw new LogicException('Object cannot be cloned.');
+ }
+
+ /**
+ */
+ public function serialize()
+ {
+ return serialize(array(
+ 'i' => $this->_init,
+ 'p' => $this->_params,
+ 'v' => self::VERSION
+ ));
+ }
+
+ /**
+ */
+ public function unserialize($data)
+ {
+ $data = @unserialize($data);
+ if (!is_array($data) ||
+ !isset($data['v']) ||
+ ($data['v'] != self::VERSION)) {
+ throw new Exception('Cache version change');
+ }
+
+ $this->_init = $data['i'];
+ $this->_params = $data['p'];
+
+ $this->_initOb();
+ }
+
+ /**
+ * Set an initialization value.
+ *
+ * @param string $key The initialization key. If null, resets all keys.
+ * @param mixed $val The cached value. If null, removes the key.
+ */
+ public function _setInit($key = null, $val = null)
+ {
+ if (is_null($key)) {
+ $this->_init = array(
+ 'namespace' => array(),
+ 's_charset' => array()
+ );
+ } elseif (is_null($val)) {
+ unset($this->_init[$key]);
+ } else {
+ switch ($key) {
+ case 'capability':
+ if ($ci = $this->getParam('capability_ignore')) {
+ if ($this->_debug->debug &&
+ ($ignored = array_intersect_key($val, array_flip($ci)))) {
+ $this->_debug->info(sprintf("CONFIG: IGNORING these IMAP capabilities: %s", implode(', ', array_keys($ignored))));
+ }
+
+ $val = array_diff_key($val, array_flip($ci));
+ }
+
+ /* RFC 5162 [1] - QRESYNC implies CONDSTORE and ENABLE, even
+ * if not listed as a capability. */
+ if (!empty($val['QRESYNC'])) {
+ $val['CONDSTORE'] = true;
+ $val['ENABLE'] = true;
+ }
+ break;
+ }
+
+ /* Nothing has changed. */
+ if (isset($this->_init[$key]) && ($this->_init[$key] == $val)) {
+ return;
+ }
+
+ $this->_init[$key] = $val;
+ }
+
+ $this->changed = true;
+ }
+
+ /**
+ * Set the list of enabled extensions.
+ *
+ * @param array $exts The list of extensions.
+ * @param integer $status 1 means to report as ENABLED, although it has
+ * not been formally enabled on server yet. 2 is
+ * verified enabled on the server.
+ */
+ protected function _enabled($exts, $status)
+ {
+ /* RFC 5162 [1] - Enabling QRESYNC also implies enabling of
+ * CONDSTORE. */
+ if (in_array('QRESYNC', $exts)) {
+ $exts[] = 'CONDSTORE';
+ }
+
+ switch ($status) {
+ case 2:
+ $enabled_list = array_intersect(array(2), $this->_temp['enabled']);
+ break;
+
+ case 1:
+ default:
+ $enabled_list = $this->_temp['enabled'];
+ $status = 1;
+ break;
+ }
+
+ $this->_temp['enabled'] = array_merge(
+ $enabled_list,
+ array_fill_keys($exts, $status)
+ );
+ }
+
+ /**
+ * Initialize the Horde_Imap_Client_Cache object, if necessary.
+ *
+ * @param boolean $current If true, we are going to update the currently
+ * selected mailbox. Add an additional check to
+ * see if caching is available in current
+ * mailbox.
+ *
+ * @return boolean Returns true if caching is enabled.
+ */
+ protected function _initCache($current = false)
+ {
+ $c = $this->getParam('cache');
+
+ if (empty($c['fields'])) {
+ return false;
+ }
+
+ if (is_null($this->_cache)) {
+ if (isset($c['backend']) &&
+ ($c['backend'] instanceof Horde_Imap_Client_Cache_Backend)) {
+ $backend = $c['backend'];
+ } elseif (isset($c['cacheob'])) {
+ /* Deprecated */
+ $backend = new Horde_Imap_Client_Cache_Backend_Cache($c);
+ } else {
+ return false;
+ }
+
+ $this->_cache = new Horde_Imap_Client_Cache(array(
+ 'backend' => $backend,
+ 'baseob' => $this,
+ 'debug' => $this->_debug
+ ));
+ }
+
+ return $current
+ /* If UIDs are labeled as not sticky, don't cache since UIDs will
+ * change on every access. */
+ ? !($this->_mailboxOb()->getStatus(Horde_Imap_Client::STATUS_UIDNOTSTICKY))
+ : true;
+ }
+
+ /**
+ * Returns a value from the internal params array.
+ *
+ * @param string $key The param key.
+ *
+ * @return mixed The param value, or null if not found.
+ */
+ public function getParam($key)
+ {
+ /* Passwords may be stored encrypted. */
+ if (($key == 'password') && !empty($this->_params['_passencrypt'])) {
+ try {
+ $secret = new Horde_Secret();
+ return $secret->read($this->_getEncryptKey(), $this->_params['password']);
+ } catch (Exception $e) {
+ return null;
+ }
+ }
+
+ return isset($this->_params[$key])
+ ? $this->_params[$key]
+ : null;
+ }
+
+ /**
+ * Sets a configuration parameter value.
+ *
+ * @param string $key The param key.
+ * @param mixed $val The param value.
+ */
+ public function setParam($key, $val)
+ {
+ switch ($key) {
+ case 'password':
+ // Encrypt password.
+ try {
+ $encrypt_key = $this->_getEncryptKey();
+ if (strlen($encrypt_key)) {
+ $secret = new Horde_Secret();
+ $val = $secret->write($encrypt_key, $val);
+ $this->_params['_passencrypt'] = true;
+ }
+ } catch (Exception $e) {
+ $this->_params['_passencrypt'] = false;
+ }
+ break;
+ }
+
+ $this->_params[$key] = $val;
+ $this->changed = true;
+ }
+
+ /**
+ * Returns the Horde_Imap_Client_Cache object used, if available.
+ *
+ * @return mixed Either the cache object or null.
+ */
+ public function getCache()
+ {
+ $this->_initCache();
+ return $this->_cache;
+ }
+
+ /**
+ * Returns the correct IDs object for use with this driver.
+ *
+ * @param mixed $ids See add().
+ * @param boolean $sequence Are $ids message sequence numbers?
+ *
+ * @return Horde_Imap_Client_Ids The IDs object.
+ */
+ public function getIdsOb($ids = null, $sequence = false)
+ {
+ return new Horde_Imap_Client_Ids($ids, $sequence);
+ }
+
+ /**
+ * Returns whether the IMAP server supports the given capability
+ * (See RFC 3501 [6.1.1]).
+ *
+ * @param string $capability The capability string to query.
+ *
+ * @return mixed True if the server supports the queried capability,
+ * false if it doesn't, or an array if the capability can
+ * contain multiple values.
+ */
+ public function queryCapability($capability)
+ {
+ try {
+ $this->capability();
+ } catch (Horde_Imap_Client_Exception $e) {
+ return false;
+ }
+
+ $capability = strtoupper($capability);
+
+ if (!isset($this->_init['capability'][$capability])) {
+ return false;
+ }
+
+ /* Check for capability requirements. */
+ if (isset(Horde_Imap_Client::$capability_deps[$capability])) {
+ foreach (Horde_Imap_Client::$capability_deps[$capability] as $val) {
+ if (!$this->queryCapability($val)) {
+ return false;
+ }
+ }
+ }
+
+ return $this->_init['capability'][$capability];
+ }
+
+ /**
+ * Get CAPABILITY information from the IMAP server.
+ *
+ * @return array The capability array.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ public function capability()
+ {
+ if (!isset($this->_init['capability'])) {
+ $this->_capability();
+ }
+
+ return $this->_init['capability'];
+ }
+
+ /**
+ * Retrieve CAPABILITY information from the IMAP server.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ abstract protected function _capability();
+
+ /**
+ * Send a NOOP command (RFC 3501 [6.1.2]).
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ public function noop()
+ {
+ // NOOP only useful if we are already authenticated.
+ if ($this->_isAuthenticated) {
+ $this->_noop();
+ }
+ }
+
+ /**
+ * Send a NOOP command.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ abstract protected function _noop();
+
+ /**
+ * Get the NAMESPACE information from the IMAP server (RFC 2342).
+ *
+ * @param array $additional If the server supports namespaces, any
+ * additional namespaces to add to the
+ * namespace list that are not broadcast by
+ * the server. The namespaces must be UTF-8
+ * strings.
+ *
+ * @return array An array of namespace information with the name as the
+ * key (UTF-8) and the following values:
+ * <ul>
+ * <li>delimiter: (string) The namespace delimiter.</li>
+ * <li>hidden: (boolean) Is this a hidden namespace?</li>
+ * <li>name: (string) The namespace name (UTF-8).</li>
+ * <li>
+ * translation: (string) Returns the translated name of the namespace
+ * (UTF-8). Requires RFC 5255 and a previous call to setLanguage().
+ * </li>
+ * <li>
+ * type: (integer) The namespace type. Either:
+ * <ul>
+ * <li>Horde_Imap_Client::NS_PERSONAL</li>
+ * <li>Horde_Imap_Client::NS_OTHER</li>
+ * <li>Horde_Imap_Client::NS_SHARED</li>
+ * </ul>
+ * </li>
+ * </ul>
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ public function getNamespaces(array $additional = array())
+ {
+ $this->login();
+
+ $additional = array_map('strval', $additional);
+ $sig = hash('md5', serialize($additional));
+
+ if (isset($this->_init['namespace'][$sig])) {
+ return $this->_init['namespace'][$sig];
+ }
+
+ $ns = $this->_getNamespaces();
+
+ /* Skip namespaces if we have already auto-detected them. Also, hidden
+ * namespaces cannot be empty. */
+ $to_process = array_diff(array_filter($additional, 'strlen'), array_keys($ns));;
+ if (!empty($to_process)) {
+ foreach ($this->listMailboxes($to_process, Horde_Imap_Client::MBOX_ALL, array('delimiter' => true)) as $val) {
+ $ns[$val] = array(
+ 'delimiter' => $first['delimiter'],
+ 'hidden' => true,
+ 'name' => $val,
+ 'translation' => '',
+ 'type' => Horde_Imap_Client::NS_SHARED
+ );
+ }
+ }
+
+ if (empty($ns)) {
+ /* This accurately determines the namespace information of the
+ * base namespace if the NAMESPACE command is not supported.
+ * See: RFC 3501 [6.3.8] */
+ $mbox = $this->listMailboxes('', Horde_Imap_Client::MBOX_ALL, array('delimiter' => true));
+ $first = reset($mbox);
+ $ns[''] = array(
+ 'delimiter' => $first['delimiter'],
+ 'hidden' => false,
+ 'name' => '',
+ 'translation' => '',
+ 'type' => Horde_Imap_Client::NS_PERSONAL
+ );
+ }
+
+ $this->_setInit('namespace', array_merge($this->_init['namespace'], array($sig => $ns)));
+
+ return $ns;
+ }
+
+ /**
+ * Get the NAMESPACE information from the IMAP server.
+ *
+ * @return array An array of namespace information. See getNamespaces()
+ * for format.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ abstract protected function _getNamespaces();
+
+ /**
+ * Display if connection to the server has been secured via TLS or SSL.
+ *
+ * @return boolean True if the IMAP connection is secured.
+ */
+ public function isSecureConnection()
+ {
+ return ($this->_connection && $this->_connection->secure);
+ }
+
+ /**
+ * Connect to the remote server.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ abstract protected function _connect();
+
+ /**
+ * Return a list of alerts that MUST be presented to the user (RFC 3501
+ * [7.1]).
+ *
+ * @return array An array of alert messages.
+ */
+ abstract public function alerts();
+
+ /**
+ * Login to the IMAP server.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ public function login()
+ {
+ if (!$this->_isAuthenticated && $this->_login()) {
+ if ($this->getParam('id')) {
+ try {
+ $this->sendID();
+ } catch (Horde_Imap_Client_Exception_NoSupportExtension $e) {
+ // Ignore if server doesn't support ID extension.
+ }
+ }
+
+ if ($this->getParam('comparator')) {
+ try {
+ $this->setComparator();
+ } catch (Horde_Imap_Client_Exception_NoSupportExtension $e) {
+ // Ignore if server doesn't support I18NLEVEL=2
+ }
+ }
+ }
+
+ $this->_isAuthenticated = true;
+ }
+
+ /**
+ * Login to the IMAP server.
+ *
+ * @return boolean Return true if global login tasks should be run.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ abstract protected function _login();
+
+ /**
+ * Logout from the IMAP server (see RFC 3501 [6.1.3]).
+ */
+ public function logout()
+ {
+ if ($this->_isAuthenticated && $this->_connection->connected) {
+ $this->_logout();
+ $this->_connection->close();
+ }
+
+ $this->_connection = $this->_selected = null;
+ $this->_isAuthenticated = false;
+ $this->_mode = 0;
+ }
+
+ /**
+ * Logout from the IMAP server (see RFC 3501 [6.1.3]).
+ */
+ abstract protected function _logout();
+
+ /**
+ * Send ID information to the IMAP server (RFC 2971).
+ *
+ * @param array $info Overrides the value of the 'id' param and sends
+ * this information instead.
+ *
+ * @throws Horde_Imap_Client_Exception
+ * @throws Horde_Imap_Client_Exception_NoSupportExtension
+ */
+ public function sendID($info = null)
+ {
+ if (!$this->queryCapability('ID')) {
+ throw new Horde_Imap_Client_Exception_NoSupportExtension('ID');
+ }
+
+ $this->_sendID(is_null($info) ? ($this->getParam('id') ?: array()) : $info);
+ }
+
+ /**
+ * Send ID information to the IMAP server (RFC 2971).
+ *
+ * @param array $info The information to send to the server.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ abstract protected function _sendID($info);
+
+ /**
+ * Return ID information from the IMAP server (RFC 2971).
+ *
+ * @return array An array of information returned, with the keys as the
+ * 'field' and the values as the 'value'.
+ *
+ * @throws Horde_Imap_Client_Exception
+ * @throws Horde_Imap_Client_Exception_NoSupportExtension
+ */
+ public function getID()
+ {
+ if (!$this->queryCapability('ID')) {
+ throw new Horde_Imap_Client_Exception_NoSupportExtension('ID');
+ }
+
+ return $this->_getID();
+ }
+
+ /**
+ * Return ID information from the IMAP server (RFC 2971).
+ *
+ * @return array An array of information returned, with the keys as the
+ * 'field' and the values as the 'value'.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ abstract protected function _getID();
+
+ /**
+ * Sets the preferred language for server response messages (RFC 5255).
+ *
+ * @param array $langs Overrides the value of the 'lang' param and sends
+ * this list of preferred languages instead. The
+ * special string 'i-default' can be used to restore
+ * the language to the server default.
+ *
+ * @return string The language accepted by the server, or null if the
+ * default language is used.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ public function setLanguage($langs = null)
+ {
+ $lang = null;
+
+ if ($this->queryCapability('LANGUAGE')) {
+ $lang = is_null($langs)
+ ? $this->getParam('lang')
+ : $langs;
+ }
+
+ return is_null($lang)
+ ? null
+ : $this->_setLanguage($lang);
+ }
+
+ /**
+ * Sets the preferred language for server response messages (RFC 5255).
+ *
+ * @param array $langs The preferred list of languages.
+ *
+ * @return string The language accepted by the server, or null if the
+ * default language is used.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ abstract protected function _setLanguage($langs);
+
+ /**
+ * Gets the preferred language for server response messages (RFC 5255).
+ *
+ * @param array $list If true, return the list of available languages.
+ *
+ * @return mixed If $list is true, the list of languages available on the
+ * server (may be empty). If false, the language used by
+ * the server, or null if the default language is used.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ public function getLanguage($list = false)
+ {
+ if (!$this->queryCapability('LANGUAGE')) {
+ return $list ? array() : null;
+ }
+
+ return $this->_getLanguage($list);
+ }
+
+ /**
+ * Gets the preferred language for server response messages (RFC 5255).
+ *
+ * @param array $list If true, return the list of available languages.
+ *
+ * @return mixed If $list is true, the list of languages available on the
+ * server (may be empty). If false, the language used by
+ * the server, or null if the default language is used.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ abstract protected function _getLanguage($list);
+
+ /**
+ * Open a mailbox.
+ *
+ * @param mixed $mailbox The mailbox to open. Either a
+ * Horde_Imap_Client_Mailbox object or a string
+ * (UTF-8).
+ * @param integer $mode The access mode. Either
+ * - Horde_Imap_Client::OPEN_READONLY
+ * - Horde_Imap_Client::OPEN_READWRITE
+ * - Horde_Imap_Client::OPEN_AUTO
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ public function openMailbox($mailbox, $mode = Horde_Imap_Client::OPEN_AUTO)
+ {
+ $this->login();
+
+ $change = false;
+ $mailbox = Horde_Imap_Client_Mailbox::get($mailbox);
+
+ if ($mode == Horde_Imap_Client::OPEN_AUTO) {
+ if (is_null($this->_selected) ||
+ !$mailbox->equals($this->_selected)) {
+ $mode = Horde_Imap_Client::OPEN_READONLY;
+ $change = true;
+ }
+ } else {
+ $change = (is_null($this->_selected) ||
+ !$mailbox->equals($this->_selected) ||
+ ($mode != $this->_mode));
+ }
+
+ if ($change) {
+ $this->_openMailbox($mailbox, $mode);
+ $this->_mailboxOb()->open = true;
+ if ($this->_initCache(true)) {
+ $this->_condstoreSync();
+ }
+ }
+ }
+
+ /**
+ * Open a mailbox.
+ *
+ * @param Horde_Imap_Client_Mailbox $mailbox The mailbox to open.
+ * @param integer $mode The access mode.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ abstract protected function _openMailbox(Horde_Imap_Client_Mailbox $mailbox,
+ $mode);
+
+ /**
+ * Called when the selected mailbox is changed.
+ *
+ * @param mixed $mailbox The selected mailbox or null.
+ * @param integer $mode The access mode.
+ */
+ protected function _changeSelected($mailbox = null, $mode = null)
+ {
+ $this->_mode = $mode;
+ if (is_null($mailbox)) {
+ $this->_selected = null;
+ } else {
+ $this->_selected = clone $mailbox;
+ $this->_mailboxOb()->reset();
+ }
+ }
+
+ /**
+ * Return the Horde_Imap_Client_Base_Mailbox object.
+ *
+ * @param string $mailbox The mailbox name. Defaults to currently
+ * selected mailbox.
+ *
+ * @return Horde_Imap_Client_Base_Mailbox Mailbox object.
+ */
+ protected function _mailboxOb($mailbox = null)
+ {
+ $name = is_null($mailbox)
+ ? strval($this->_selected)
+ : strval($mailbox);
+
+ if (!isset($this->_temp['mailbox_ob'][$name])) {
+ $this->_temp['mailbox_ob'][$name] = new Horde_Imap_Client_Base_Mailbox();
+ }
+
+ return $this->_temp['mailbox_ob'][$name];
+ }
+
+ /**
+ * Return the currently opened mailbox and access mode.
+ *
+ * @return mixed Null if no mailbox selected, or an array with two
+ * elements:
+ * - mailbox: (Horde_Imap_Client_Mailbox) The mailbox object.
+ * - mode: (integer) Current mode.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ public function currentMailbox()
+ {
+ return is_null($this->_selected)
+ ? null
+ : array(
+ 'mailbox' => clone $this->_selected,
+ 'mode' => $this->_mode
+ );
+ }
+
+ /**
+ * Create a mailbox.
+ *
+ * @param mixed $mailbox The mailbox to create. Either a
+ * Horde_Imap_Client_Mailbox object or a string
+ * (UTF-8).
+ * @param array $opts Additional options:
+ * - special_use: (array) An array of special-use flags to mark the
+ * mailbox with. The server MUST support RFC 6154.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ public function createMailbox($mailbox, array $opts = array())
+ {
+ $this->login();
+
+ if (!$this->queryCapability('CREATE-SPECIAL-USE')) {
+ unset($opts['special_use']);
+ }
+
+ $this->_createMailbox(Horde_Imap_Client_Mailbox::get($mailbox), $opts);
+ }
+
+ /**
+ * Create a mailbox.
+ *
+ * @param Horde_Imap_Client_Mailbox $mailbox The mailbox to create.
+ * @param array $opts Additional options. See
+ * createMailbox().
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ abstract protected function _createMailbox(Horde_Imap_Client_Mailbox $mailbox,
+ $opts);
+
+ /**
+ * Delete a mailbox.
+ *
+ * @param mixed $mailbox The mailbox to delete. Either a
+ * Horde_Imap_Client_Mailbox object or a string
+ * (UTF-8).
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ public function deleteMailbox($mailbox)
+ {
+ $this->login();
+
+ $mailbox = Horde_Imap_Client_Mailbox::get($mailbox);
+
+ $this->_deleteMailbox($mailbox);
+ $this->_deleteMailboxPost($mailbox);
+ }
+
+ /**
+ * Delete a mailbox.
+ *
+ * @param Horde_Imap_Client_Mailbox $mailbox The mailbox to delete.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ abstract protected function _deleteMailbox(Horde_Imap_Client_Mailbox $mailbox);
+
+ /**
+ * Actions to perform after a mailbox delete.
+ *
+ * @param Horde_Imap_Client_Mailbox $mailbox The deleted mailbox.
+ */
+ protected function _deleteMailboxPost(Horde_Imap_Client_Mailbox $mailbox)
+ {
+ /* Delete mailbox caches. */
+ if ($this->_initCache()) {
+ $this->_cache->deleteMailbox($mailbox);
+ }
+ unset($this->_temp['mailbox_ob'][strval($mailbox)]);
+
+ /* Unsubscribe from mailbox. */
+ try {
+ $this->subscribeMailbox($mailbox, false);
+ } catch (Horde_Imap_Client_Exception $e) {
+ // Ignore failed unsubscribe request
+ }
+ }
+
+ /**
+ * Rename a mailbox.
+ *
+ * @param mixed $old The old mailbox name. Either a
+ * Horde_Imap_Client_Mailbox object or a string (UTF-8).
+ * @param mixed $new The new mailbox name. Either a
+ * Horde_Imap_Client_Mailbox object or a string (UTF-8).
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ public function renameMailbox($old, $new)
+ {
+ // Login will be handled by first listMailboxes() call.
+
+ $old = Horde_Imap_Client_Mailbox::get($old);
+ $new = Horde_Imap_Client_Mailbox::get($new);
+
+ /* Check if old mailbox(es) were subscribed to. */
+ $base = $this->listMailboxes($old, Horde_Imap_Client::MBOX_SUBSCRIBED, array('delimiter' => true));
+ if (empty($base)) {
+ $base = $this->listMailboxes($old, Horde_Imap_Client::MBOX_ALL, array('delimiter' => true));
+ $base = reset($base);
+ $subscribed = array();
+ } else {
+ $base = reset($base);
+ $subscribed = array($base['mailbox']);
+ }
+
+ $all_mboxes = array($base['mailbox']);
+ if (strlen($base['delimiter'])) {
+ $search = $old->list_escape . $base['delimiter'] . '*';
+ $all_mboxes = array_merge($all_mboxes, $this->listMailboxes($search, Horde_Imap_Client::MBOX_ALL, array('flat' => true)));
+ $subscribed = array_merge($subscribed, $this->listMailboxes($search, Horde_Imap_Client::MBOX_SUBSCRIBED, array('flat' => true)));
+ }
+
+ $this->_renameMailbox($old, $new);
+
+ /* Delete mailbox actions. */
+ foreach ($all_mboxes as $val) {
+ $this->_deleteMailboxPost($val);
+ }
+
+ foreach ($subscribed as $val) {
+ try {
+ $this->subscribeMailbox(new Horde_Imap_Client_Mailbox(substr_replace($val, $new, 0, strlen($old))));
+ } catch (Horde_Imap_Client_Exception $e) {
+ // Ignore failed subscription requests
+ }
+ }
+ }
+
+ /**
+ * Rename a mailbox.
+ *
+ * @param Horde_Imap_Client_Mailbox $old The old mailbox name.
+ * @param Horde_Imap_Client_Mailbox $new The new mailbox name.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ abstract protected function _renameMailbox(Horde_Imap_Client_Mailbox $old,
+ Horde_Imap_Client_Mailbox $new);
+
+ /**
+ * Manage subscription status for a mailbox.
+ *
+ * @param mixed $mailbox The mailbox to [un]subscribe to. Either a
+ * Horde_Imap_Client_Mailbox object or a string
+ * (UTF-8).
+ * @param boolean $subscribe True to subscribe, false to unsubscribe.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ public function subscribeMailbox($mailbox, $subscribe = true)
+ {
+ $this->login();
+ $this->_subscribeMailbox(Horde_Imap_Client_Mailbox::get($mailbox), (bool)$subscribe);
+ }
+
+ /**
+ * Manage subscription status for a mailbox.
+ *
+ * @param Horde_Imap_Client_Mailbox $mailbox The mailbox to [un]subscribe
+ * to.
+ * @param boolean $subscribe True to subscribe, false to
+ * unsubscribe.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ abstract protected function _subscribeMailbox(Horde_Imap_Client_Mailbox $mailbox,
+ $subscribe);
+
+ /**
+ * Obtain a list of mailboxes matching a pattern.
+ *
+ * @param mixed $pattern The mailbox search pattern(s) (see RFC 3501
+ * [6.3.8] for the format). A UTF-8 string or an
+ * array of strings. If a Horde_Imap_Client_Mailbox
+ * object is given, it is escaped (i.e. wildcard
+ * patterns are converted to return the miminal
+ * number of matches possible).
+ * @param integer $mode Which mailboxes to return. Either:
+ * - Horde_Imap_Client::MBOX_SUBSCRIBED
+ * - Horde_Imap_Client::MBOX_SUBSCRIBED_EXISTS
+ * - Horde_Imap_Client::MBOX_UNSUBSCRIBED
+ * - Horde_Imap_Client::MBOX_ALL
+ * @param array $options Additional options:
+ * <ul>
+ * <li>
+ * attributes: (boolean) If true, return attribute information under
+ * the 'attributes' key.
+ * DEFAULT: Do not return this information.
+ * </li>
+ * <li>
+ * children: (boolean) Tell server to return children attribute
+ * information (\HasChildren, \HasNoChildren). Requires the
+ * LIST-EXTENDED extension to guarantee this information is returned.
+ * Server MAY return this attribute without this option, or if the
+ * CHILDREN extension is available, but it is not guaranteed.
+ * DEFAULT: false
+ * </li>
+ * <li>
+ * delimiter: (boolean) If true, return delimiter information under the
+ * 'delimiter' key.
+ * DEFAULT: Do not return this information.
+ * </li>
+ * <li>
+ * flat: (boolean) If true, return a flat list of mailbox names only.
+ * Overrides both the 'attributes' and 'delimiter' options.
+ * DEFAULT: Do not return flat list.
+ * </li>
+ * <li>
+ * recursivematch: (boolean) Force the server to return information
+ * about parent mailboxes that don't match other selection options, but
+ * have some submailboxes that do. Information about children is
+ * returned in the CHILDINFO extended data item ('extended'). Requires
+ * the LIST-EXTENDED extension.
+ * DEFAULT: false
+ * </li>
+ * <li>
+ * remote: (boolean) Tell server to return mailboxes that reside on
+ * another server. Requires the LIST-EXTENDED extension.
+ * DEFAULT: false
+ * </li>
+ * <li>
+ * special_use: (boolean) Tell server to return special-use attribute
+ * information (\Drafts, \Flagged, \Junk, \Sent, \Trash, \All,
+ * \Archive). Server must support the SPECIAL-USE return option for this
+ * setting to have any effect. Server MAY return this attribute without
+ * this option.
+ * DEFAULT: false
+ * <li>
+ * status: (integer) Tell server to return status information. The
+ * value is a bitmask that may contain any of:
+ * <ul>
+ * <li>Horde_Imap_Client::STATUS_MESSAGES</li>
+ * <li>Horde_Imap_Client::STATUS_RECENT</li>
+ * <li>Horde_Imap_Client::STATUS_UIDNEXT</li>
+ * <li>Horde_Imap_Client::STATUS_UIDVALIDITY</li>
+ * <li>Horde_Imap_Client::STATUS_UNSEEN</li>
+ * <li>Horde_Imap_Client::STATUS_HIGHESTMODSEQ</li>
+ * </ul>
+ * DEFAULT: 0
+ * </li>
+ * <li>
+ * sort: (boolean) If true, return a sorted list of mailboxes?
+ * DEFAULT: Do not sort the list.
+ * </li>
+ * <li>
+ * sort_delimiter: (string) If 'sort' is true, this is the delimiter
+ * used to sort the mailboxes.
+ * DEFAULT: '.'
+ * </li>
+ * </ul>
+ *
+ * @return array If 'flat' option is true, the array values are a list
+ * of Horde_Imap_Client_Mailbox objects. Otherwise, the
+ * keys are UTF-8 mailbox names and the values are arrays
+ * with these keys:
+ * - attributes: (array) List of lower-cased attributes [only if
+ * 'attributes' option is true].
+ * - delimiter: (string) The delimiter for the mailbox [only if
+ * 'delimiter' option is true].
+ * - extended: (TODO) TODO [only if 'recursivematch' option is true and
+ * LIST-EXTENDED extension is supported on the server].
+ * - mailbox: (Horde_Imap_Client_Mailbox) The mailbox object.
+ * - status: (array) See status() [only if 'status' option is true].
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ public function listMailboxes($pattern,
+ $mode = Horde_Imap_Client::MBOX_ALL,
+ array $options = array())
+ {
+ $this->login();
+
+ $pattern = is_array($pattern)
+ ? array_unique($pattern)
+ : array($pattern);
+
+ /* Prepare patterns. */
+ $plist = array();
+ foreach ($pattern as $val) {
+ if ($val instanceof Horde_Imap_Client_Mailbox) {
+ $val = $val->list_escape;
+ }
+ $plist[] = Horde_Imap_Client_Mailbox::get(preg_replace(
+ array("/\*{2,}/", "/\%{2,}/"),
+ array('*', '%'),
+ Horde_Imap_Client_Utf7imap::Utf8ToUtf7Imap($val)
+ ), true);
+ }
+
+ if (isset($options['special_use']) &&
+ !$this->queryCapability('SPECIAL-USE')) {
+ unset($options['special_use']);
+ }
+
+ $ret = $this->_listMailboxes($plist, $mode, $options);
+
+ if (!empty($options['status']) &&
+ !$this->queryCapability('LIST-STATUS')) {
+ $status = $this->statusMultiple($this->_selected, $options['status']);
+ foreach ($status as $key => $val) {
+ $ret[$key]['status'] = $val;
+ }
+ }
+
+ if (empty($options['sort'])) {
+ return $ret;
+ }
+
+ $list_ob = new Horde_Imap_Client_Mailbox_List(empty($options['flat']) ? array_keys($ret) : $ret);
+ $sorted = $list_ob->sort(array(
+ 'delimiter' => empty($options['sort_delimiter']) ? '.' : $options['sort_delimiter']
+ ));
+
+ if (!empty($options['flat'])) {
+ return $sorted;
+ }
+
+ $out = array();
+ foreach ($sorted as $val) {
+ $out[$val] = $ret[$val];
+ }
+
+ return $out;
+ }
+
+ /**
+ * Obtain a list of mailboxes matching a pattern.
+ *
+ * @param array $pattern The mailbox search patterns
+ * (Horde_Imap_Client_Mailbox objects).
+ * @param integer $mode Which mailboxes to return.
+ * @param array $options Additional options.
+ *
+ * @return array See listMailboxes().
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ abstract protected function _listMailboxes($pattern, $mode, $options);
+
+ /**
+ * Obtain status information for a mailbox.
+ *
+ * @param mixed $mailbox The mailbox(es) to query. Either a
+ * Horde_Imap_Client_Mailbox object, a string
+ * (UTF-8), or an array of objects/strings (since
+ * 2.10.0).
+ * @param integer $flags A bitmask of information requested from the
+ * server. Allowed flags:
+ * <ul>
+ * <li>
+ * Horde_Imap_Client::STATUS_MESSAGES
+ * <ul>
+ * <li>
+ * Return key: messages
+ * </li>
+ * <li>
+ * Return format: (integer) The number of messages in the mailbox.
+ * </li>
+ * </ul>
+ * </li>
+ * <li>
+ * Horde_Imap_Client::STATUS_RECENT
+ * <ul>
+ * <li>
+ * Return key: recent
+ * </li>
+ * <li>
+ * Return format: (integer) The number of messages with the \Recent
+ * flag set as currently reported in the mailbox
+ * </li>
+ * </ul>
+ * </li>
+ * <li>
+ * Horde_Imap_Client::STATUS_RECENT_TOTAL
+ * <ul>
+ * <li>
+ * Return key: recent_total
+ * </li>
+ * <li>
+ * Return format: (integer) The number of messages with the \Recent
+ * flag set. This returns the total number of messages that have
+ * been marked as recent in this mailbox since the PHP process began.
+ * (since 2.12.0)
+ * </li>
+ * </ul>
+ * </li>
+ * <li>
+ * Horde_Imap_Client::STATUS_UIDNEXT
+ * <ul>
+ * <li>
+ * Return key: uidnext
+ * </li>
+ * <li>
+ * Return format: (integer) The next UID to be assigned in the
+ * mailbox. Only returned if the server automatically provides the
+ * data.
+ * </li>
+ * </ul>
+ * </li>
+ * <li>
+ * Horde_Imap_Client::STATUS_UIDNEXT_FORCE
+ * <ul>
+ * <li>
+ * Return key: uidnext
+ * </li>
+ * <li>
+ * Return format: (integer) The next UID to be assigned in the
+ * mailbox. This option will always determine this value, even if the
+ * server does not automatically provide this data.
+ * </li>
+ * </ul>
+ * </li>
+ * <li>
+ * Horde_Imap_Client::STATUS_UIDVALIDITY
+ * <ul>
+ * <li>
+ * Return key: uidvalidity
+ * </li>
+ * <li>
+ * Return format: (integer) The unique identifier validity of the
+ * mailbox.
+ * </li>
+ * </ul>
+ * </li>
+ * <li>
+ * Horde_Imap_Client::STATUS_UNSEEN
+ * <ul>
+ * <li>
+ * Return key: unseen
+ * </li>
+ * <li>
+ * Return format: (integer) The number of messages which do not have
+ * the \Seen flag set.
+ * </li>
+ * </ul>
+ * </li>
+ * <li>
+ * Horde_Imap_Client::STATUS_FIRSTUNSEEN
+ * <ul>
+ * <li>
+ * Return key: firstunseen
+ * </li>
+ * <li>
+ * Return format: (integer) The sequence number of the first unseen
+ * message in the mailbox.
+ * </li>
+ * </ul>
+ * </li>
+ * <li>
+ * Horde_Imap_Client::STATUS_FLAGS
+ * <ul>
+ * <li>
+ * Return key: flags
+ * </li>
+ * <li>
+ * Return format: (array) The list of defined flags in the mailbox
+ * (all flags are in lowercase).
+ * </li>
+ * </ul>
+ * </li>
+ * <li>
+ * Horde_Imap_Client::STATUS_PERMFLAGS
+ * <ul>
+ * <li>
+ * Return key: permflags
+ * </li>
+ * <li>
+ * Return format: (array) The list of flags that a client can change
+ * permanently (all flags are in lowercase).
+ * </li>
+ * </ul>
+ * </li>
+ * <li>
+ * Horde_Imap_Client::STATUS_HIGHESTMODSEQ
+ * <ul>
+ * <li>
+ * Return key: highestmodseq
+ * </li>
+ * <li>
+ * Return format: (integer) If the server supports the CONDSTORE
+ * IMAP extension, this will be the highest mod-sequence value of all
+ * messages in the mailbox. Else 0 if CONDSTORE not available or the
+ * mailbox does not support mod-sequences.
+ * </li>
+ * </ul>
+ * </li>
+ * <li>
+ * Horde_Imap_Client::STATUS_SYNCMODSEQ
+ * <ul>
+ * <li>
+ * Return key: syncmodseq
+ * </li>
+ * <li>
+ * Return format: (integer) If caching, and the server supports the
+ * CONDSTORE IMAP extension, this is the cached mod-sequence value of
+ * the mailbox when it was opened for the first time in this access.
+ * Will be null if not caching, CONDSTORE not available, or the
+ * mailbox does not support mod-sequences.
+ * </li>
+ * </ul>
+ * </li>
+ * <li>
+ * Horde_Imap_Client::STATUS_SYNCFLAGUIDS
+ * <ul>
+ * <li>
+ * Return key: syncflaguids
+ * </li>
+ * <li>
+ * Return format: (Horde_Imap_Client_Ids) If caching, the server
+ * supports the CONDSTORE IMAP extension, and the mailbox contained
+ * cached data when opened for the first time in this access, this is
+ * the list of UIDs in which flags have changed since
+ * STATUS_SYNCMODSEQ.
+ * </li>
+ * </ul>
+ * </li>
+ * <li>
+ * Horde_Imap_Client::STATUS_SYNCVANISHED
+ * <ul>
+ * <li>
+ * Return key: syncvanished
+ * </li>
+ * <li>
+ * Return format: (Horde_Imap_Client_Ids) If caching, the server
+ * supports the CONDSTORE IMAP extension, and the mailbox contained
+ * cached data when opened for the first time in this access, this is
+ * the list of UIDs which have been deleted since STATUS_SYNCMODSEQ.
+ * </li>
+ * </ul>
+ * </li>
+ * <li>
+ * Horde_Imap_Client::STATUS_UIDNOTSTICKY
+ * <ul>
+ * <li>
+ * Return key: uidnotsticky
+ * </li>
+ * <li>
+ * Return format: (boolean) If the server supports the UIDPLUS IMAP
+ * extension, and the queried mailbox does not support persistent
+ * UIDs, this value will be true. In all other cases, this value will
+ * be false.
+ * </li>
+ * </ul>
+ * </li>
+ * <li>
+ * Horde_Imap_Client::STATUS_ALL (DEFAULT)
+ * <ul>
+ * <li>
+ * Shortcut to return 'messages', 'recent', 'uidnext', 'uidvalidity',
+ * and 'unseen' values.
+ * </li>
+ * </ul>
+ * </li>
+ * </ul>
+ * @param array $opts Additional options:
+ * - sort: (boolean) If true, sort the list of mailboxes? (since 2.10.0)
+ * DEFAULT: Do not sort the list.
+ * - sort_delimiter: (string) If 'sort' is true, this is the delimiter
+ * used to sort the mailboxes. (since 2.10.0)
+ * DEFAULT: '.'
+ *
+ * @return array If $mailbox contains multiple mailboxes, an array with
+ * keys being the UTF-8 mailbox name and values as arrays
+ * containing the requested keys (see above).
+ * Otherwise, an array with keys as the requested keys (see
+ * above) and values as the key data.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ public function status($mailbox, $flags = Horde_Imap_Client::STATUS_ALL,
+ array $opts = array())
+ {
+ $opts = array_merge(array(
+ 'sort' => false,
+ 'sort_delimiter' => '.'
+ ), $opts);
+
+ $this->login();
+
+ if (is_array($mailbox)) {
+ if (empty($mailbox)) {
+ return array();
+ }
+ $ret_array = true;
+ } else {
+ $mailbox = array($mailbox);
+ $ret_array = false;
+ }
+
+ $mlist = array_map(array('Horde_Imap_Client_Mailbox', 'get'), $mailbox);
+
+ $unselected_flags = array(
+ 'messages' => Horde_Imap_Client::STATUS_MESSAGES,
+ 'recent' => Horde_Imap_Client::STATUS_RECENT,
+ 'uidnext' => Horde_Imap_Client::STATUS_UIDNEXT,
+ 'uidvalidity' => Horde_Imap_Client::STATUS_UIDVALIDITY,
+ 'unseen' => Horde_Imap_Client::STATUS_UNSEEN
+ );
+
+ if ($flags & Horde_Imap_Client::STATUS_ALL) {
+ foreach ($unselected_flags as $val) {
+ $flags |= $val;
+ }
+ }
+
+ $master = $ret = array();
+
+ /* Catch flags that are not supported. */
+ if (($flags & Horde_Imap_Client::STATUS_HIGHESTMODSEQ) &&
+ !isset($this->_temp['enabled']['CONDSTORE'])) {
+ $master['highestmodseq'] = 0;
+ $flags &= ~Horde_Imap_Client::STATUS_HIGHESTMODSEQ;
+ }
+
+ if (($flags & Horde_Imap_Client::STATUS_UIDNOTSTICKY) &&
+ !$this->queryCapability('UIDPLUS')) {
+ $master['uidnotsticky'] = false;
+ $flags &= ~Horde_Imap_Client::STATUS_UIDNOTSTICKY;
+ }
+
+ /* UIDNEXT return options. */
+ if ($flags & Horde_Imap_Client::STATUS_UIDNEXT_FORCE) {
+ $flags |= Horde_Imap_Client::STATUS_UIDNEXT;
+ }
+
+ foreach ($mlist as $val) {
+ $name = strval($val);
+ $tmp_flags = $flags;
+
+ /* A list of STATUS options (other than those handled directly
+ * below) that require the mailbox to be explicitly opened. */
+ $opened = ($flags & Horde_Imap_Client::STATUS_FIRSTUNSEEN) ||
+ ($flags & Horde_Imap_Client::STATUS_FLAGS) ||
+ ($flags & Horde_Imap_Client::STATUS_PERMFLAGS) ||
+ ($flags & Horde_Imap_Client::STATUS_UIDNOTSTICKY) ||
+ /* Check if already in mailbox. */
+ $val->equals($this->_selected) ||
+ /* Force mailboxes containing wildcards to be accessed via
+ * STATUS so that wildcards do not return a bunch of
+ * mailboxes in the LIST-STATUS response. */
+ (strpbrk($name, '*%') !== false);
+
+ $ret[$name] = $master;
+ $ptr = &$ret[$name];
+
+ /* STATUS_PERMFLAGS requires a read/write mailbox. */
+ if ($flags & Horde_Imap_Client::STATUS_PERMFLAGS) {
+ $this->openMailbox($val, Horde_Imap_Client::OPEN_READWRITE);
+ $opened = true;
+ }
+
+ /* Handle SYNC related return options. These require the mailbox
+ * to be opened at least once. */
+ if ($flags & Horde_Imap_Client::STATUS_SYNCMODSEQ) {
+ $this->openMailbox($val);
+ $ptr['syncmodseq'] = $this->_mailboxOb($val)->getStatus(Horde_Imap_Client::STATUS_SYNCMODSEQ);
+ $tmp_flags &= ~Horde_Imap_Client::STATUS_SYNCMODSEQ;
+ $opened = true;
+ }
+
+ if ($flags & Horde_Imap_Client::STATUS_SYNCFLAGUIDS) {
+ $this->openMailbox($val);
+ $ptr['syncflaguids'] = $this->getIdsOb($this->_mailboxOb($val)->getStatus(Horde_Imap_Client::STATUS_SYNCFLAGUIDS));
+ $tmp_flags &= ~Horde_Imap_Client::STATUS_SYNCFLAGUIDS;
+ $opened = true;
+ }
+
+ if ($flags & Horde_Imap_Client::STATUS_SYNCVANISHED) {
+ $this->openMailbox($val);
+ $ptr['syncvanished'] = $this->getIdsOb($this->_mailboxOb($val)->getStatus(Horde_Imap_Client::STATUS_SYNCVANISHED));
+ $tmp_flags &= ~Horde_Imap_Client::STATUS_SYNCVANISHED;
+ $opened = true;
+ }
+
+ /* Handle RECENT_TOTAL option. */
+ if ($flags & Horde_Imap_Client::STATUS_RECENT_TOTAL) {
+ $this->openMailbox($val);
+ $ptr['recent_total'] = $this->_mailboxOb($val)->getStatus(Horde_Imap_Client::STATUS_RECENT_TOTAL);
+ $tmp_flags &= ~Horde_Imap_Client::STATUS_RECENT_TOTAL;
+ $opened = true;
+ }
+
+ if ($opened) {
+ if ($tmp_flags) {
+ $tmp = $this->_status(array($val), $tmp_flags);
+ $ptr += reset($tmp);
+ }
+ } else {
+ $to_process[] = $val;
+ }
+ }
+
+ if ($flags && !empty($to_process)) {
+ if ((count($to_process) > 1) &&
+ $this->queryCapability('LIST-STATUS')) {
+ foreach ($this->listMailboxes($to_process, Horde_Imap_Client::MBOX_ALL, array('status' => $flags)) as $key => $val) {
+ if (isset($val['status'])) {
+ $ret[$key] += $val['status'];
+ }
+ }
+ } else {
+ foreach ($this->_status($to_process, $flags) as $key => $val) {
+ $ret[$key] += $val;
+ }
+ }
+ }
+
+ if (!$opts['sort'] || (count($ret) == 1)) {
+ return $ret_array
+ ? $ret
+ : reset($ret);
+ }
+
+ $list_ob = new Horde_Imap_Client_Mailbox_List(array_keys($ret));
+ $sorted = $list_ob->sort(array(
+ 'delimiter' => $opts['sort_delimiter']
+ ));
+
+ $out = array();
+ foreach ($sorted as $val) {
+ $out[$val] = $ret[$val];
+ }
+
+ return $out;
+ }
+
+ /**
+ * Obtain status information for mailboxes.
+ *
+ * @param array $mboxes The list of mailbox objects to query.
+ * @param integer $flags A bitmask of information requested from the
+ * server.
+ *
+ * @return array See array return for status().
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ abstract protected function _status($mboxes, $flags);
+
+ /**
+ * Perform a STATUS call on multiple mailboxes at the same time.
+ *
+ * This method leverages the LIST-EXTENDED and LIST-STATUS extensions on
+ * the IMAP server to improve the efficiency of this operation.
+ *
+ * @deprecated Use status() instead.
+ *
+ * @param array $mailboxes The mailboxes to query. Either
+ * Horde_Imap_Client_Mailbox objects, strings
+ * (UTF-8), or a combination of the two.
+ * @param integer $flags See status().
+ * @param array $opts Additional options:
+ * - sort: (boolean) If true, sort the list of mailboxes?
+ * DEFAULT: Do not sort the list.
+ * - sort_delimiter: (string) If 'sort' is true, this is the delimiter
+ * used to sort the mailboxes.
+ * DEFAULT: '.'
+ *
+ * @return array An array with the keys as the mailbox names (UTF-8) and
+ * the values as arrays with the requested keys (from the
+ * mask given in $flags).
+ */
+ public function statusMultiple($mailboxes,
+ $flags = Horde_Imap_Client::STATUS_ALL,
+ array $opts = array())
+ {
+ return $this->status($mailboxes, $flags, $opts);
+ }
+
+ /**
+ * Append message(s) to a mailbox.
+ *
+ * @param mixed $mailbox The mailbox to append the message(s) to. Either
+ * a Horde_Imap_Client_Mailbox object or a string
+ * (UTF-8).
+ * @param array $data The message data to append, along with
+ * additional options. An array of arrays with
+ * each embedded array having the following
+ * entries:
+ * <ul>
+ * <li>
+ * data: (mixed) The data to append. If a string or a stream resource,
+ * this will be used as the entire contents of a single message. If an
+ * array, will catenate all given parts into a single message. This
+ * array contains one or more arrays with two keys:
+ * <ul>
+ * <li>
+ * t: (string) Either 'url' or 'text'.
+ * </li>
+ * <li>
+ * v: (mixed) If 't' is 'url', this is the IMAP URL to the message
+ * part to append. If 't' is 'text', this is either a string or
+ * resource representation of the message part data.
+ * DEFAULT: NONE (entry is MANDATORY)
+ * </li>
+ * </ul>
+ * </li>
+ * <li>
+ * flags: (array) An array of flags/keywords to set on the appended
+ * message.
+ * DEFAULT: Only the \Recent flag is set.
+ * </li>
+ * <li>
+ * internaldate: (DateTime) The internaldate to set for the appended
+ * message.
+ * DEFAULT: internaldate will be the same date as when the message was
+ * appended.
+ * </li>
+ * </ul>
+ * @param array $options Additonal options:
+ * - create: (boolean) Try to create $mailbox if it does not exist?
+ * DEFAULT: No.
+ *
+ * @return Horde_Imap_Client_Ids The UIDs of the appended messages.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ public function append($mailbox, $data, array $options = array())
+ {
+ $this->login();
+
+ $mailbox = Horde_Imap_Client_Mailbox::get($mailbox);
+
+ $ret = $this->_append($mailbox, $data, $options);
+
+ if ($ret instanceof Horde_Imap_Client_Ids) {
+ return $ret;
+ }
+
+ $uids = $this->getIdsOb();
+
+ while (list(,$val) = each($data)) {
+ if (is_string($val['data'])) {
+ $text = $val['data'];
+ } elseif (is_resource($val['data'])) {
+ $text = '';
+ rewind($val['data']);
+ while (!feof($val['data'])) {
+ $text .= fread($val['data'], 512);
+ if (preg_match("/\n\r{2,}/", $text)) {
+ break;
+ }
+ }
+ }
+
+ $headers = Horde_Mime_Headers::parseHeaders($text);
+ $msgid = $headers->getValue('message-id');
+
+ if ($msgid) {
+ $search_query = new Horde_Imap_Client_Search_Query();
+ $search_query->headerText('Message-ID', $msgid);
+ $uidsearch = $this->search($mailbox, $search_query);
+ $uids->add($uidsearch['match']);
+ }
+ }
+
+ return $uids;
+ }
+
+ /**
+ * Append message(s) to a mailbox.
+ *
+ * @param Horde_Imap_Client_Mailbox $mailbox The mailbox to append the
+ * message(s) to.
+ * @param array $data The message data.
+ * @param array $options Additional options.
+ *
+ * @return mixed A Horde_Imap_Client_Ids object containing the UIDs of
+ * the appended messages (if server supports UIDPLUS
+ * extension) or true.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ abstract protected function _append(Horde_Imap_Client_Mailbox $mailbox,
+ $data, $options);
+
+ /**
+ * Request a checkpoint of the currently selected mailbox (RFC 3501
+ * [6.4.1]).
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ public function check()
+ {
+ // CHECK only useful if we are already authenticated.
+ if ($this->_isAuthenticated) {
+ $this->_check();
+ }
+ }
+
+ /**
+ * Request a checkpoint of the currently selected mailbox.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ abstract protected function _check();
+
+ /**
+ * Close the connection to the currently selected mailbox, optionally
+ * expunging all deleted messages (RFC 3501 [6.4.2]).
+ *
+ * @param array $options Additional options:
+ * - expunge: (boolean) Expunge all messages flagged as deleted?
+ * DEFAULT: No
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ public function close(array $options = array())
+ {
+ // This check catches the non-logged in case.
+ if (is_null($this->_selected)) {
+ return;
+ }
+
+ /* If we are caching, search for deleted messages. */
+ if (!empty($options['expunge']) && $this->_initCache(true)) {
+ /* Make sure mailbox is read-write to expunge. */
+ $this->openMailbox($this->_selected, Horde_Imap_Client::OPEN_READWRITE);
+ if ($this->_mode == Horde_Imap_Client::OPEN_READONLY) {
+ throw new Horde_Imap_Client_Exception(
+ Horde_Imap_Client_Translation::t("Cannot expunge read-only mailbox."),
+ Horde_Imap_Client_Exception::MAILBOX_READONLY
+ );
+ }
+
+ $search_query = new Horde_Imap_Client_Search_Query();
+ $search_query->flag(Horde_Imap_Client::FLAG_DELETED, true);
+ $search_res = $this->search($this->_selected, $search_query);
+ $mbox = $this->_selected;
+ } else {
+ $search_res = null;
+ }
+
+ $this->_close($options);
+ $this->_selected = null;
+ $this->_mode = 0;
+
+ if (!is_null($search_res)) {
+ $this->_deleteMsgs($mbox, $search_res['match']);
+ }
+ }
+
+ /**
+ * Close the connection to the currently selected mailbox, optionally
+ * expunging all deleted messages (RFC 3501 [6.4.2]).
+ *
+ * @param array $options Additional options.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ abstract protected function _close($options);
+
+ /**
+ * Expunge deleted messages from the given mailbox.
+ *
+ * @param mixed $mailbox The mailbox to expunge. Either a
+ * Horde_Imap_Client_Mailbox object or a string
+ * (UTF-8).
+ * @param array $options Additional options:
+ * - delete: (boolean) If true, will flag all messages in 'ids' as
+ * deleted (since 2.10.0).
+ * DEFAULT: false
+ * - ids: (Horde_Imap_Client_Ids) A list of messages to expunge. These
+ * messages must already be flagged as deleted (unless 'delete'
+ * is true).
+ * DEFAULT: All messages marked as deleted will be expunged.
+ * - list: (boolean) If true, returns the list of expunged messages
+ * (UIDs only).
+ * DEFAULT: false
+ *
+ * @return Horde_Imap_Client_Ids If 'list' option is true, returns the
+ * UID list of expunged messages.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ public function expunge($mailbox, array $options = array())
+ {
+ // Open mailbox call will handle the login.
+ $this->openMailbox($mailbox, Horde_Imap_Client::OPEN_READWRITE);
+
+ /* Don't expunge if the mailbox is readonly. */
+ if ($this->_mode == Horde_Imap_Client::OPEN_READONLY) {
+ throw new Horde_Imap_Client_Exception(
+ Horde_Imap_Client_Translation::t("Cannot expunge read-only mailbox."),
+ Horde_Imap_Client_Exception::MAILBOX_READONLY
+ );
+ }
+
+ if (empty($options['ids'])) {
+ $options['ids'] = $this->getIdsOb(Horde_Imap_Client_Ids::ALL);
+ } elseif ($options['ids']->isEmpty()) {
+ return $this->getIdsOb();
+ }
+
+ return $this->_expunge($options);
+ }
+
+ /**
+ * Expunge all deleted messages from the given mailbox.
+ *
+ * @param array $options Additional options.
+ *
+ * @return Horde_Imap_Client_Ids If 'list' option is true, returns the
+ * list of expunged messages.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ abstract protected function _expunge($options);
+
+ /**
+ * Search a mailbox.
+ *
+ * @param mixed $mailbox The mailbox to search.
+ * Either a
+ * Horde_Imap_Client_Mailbox
+ * object or a string
+ * (UTF-8).
+ * @param Horde_Imap_Client_Search_Query $query The search query.
+ * Defaults to an ALL
+ * search.
+ * @param array $options Additional options:
+ * <ul>
+ * <li>
+ * nocache: (boolean) Don't cache the results.
+ * DEFAULT: false (results cached, if possible)
+ * </li>
+ * <li>
+ * partial: (mixed) The range of results to return (message sequence
+ * numbers).
+ * DEFAULT: All messages are returned.
+ * </li>
+ * <li>
+ * results: (array) The data to return. Consists of zero or more of
+ * the following flags:
+ * <ul>
+ * <li>Horde_Imap_Client::SEARCH_RESULTS_COUNT</li>
+ * <li>Horde_Imap_Client::SEARCH_RESULTS_MATCH (DEFAULT)</li>
+ * <li>Horde_Imap_Client::SEARCH_RESULTS_MAX</li>
+ * <li>Horde_Imap_Client::SEARCH_RESULTS_MIN</li>
+ * <li>Horde_Imap_Client::SEARCH_RESULTS_SAVE</li>
+ * <li>Horde_Imap_Client::SEARCH_RESULTS_RELEVANCY</li>
+ * </ul>
+ * </li>
+ * <li>
+ * sequence: (boolean) If true, returns an array of sequence numbers.
+ * DEFAULT: Returns an array of UIDs
+ * </li>
+ * <li>
+ * sort: (array) Sort the returned list of messages. Multiple sort
+ * criteria can be specified. Any sort criteria can be sorted in reverse
+ * order (instead of the default ascending order) by adding a
+ * Horde_Imap_Client::SORT_REVERSE element to the array directly before
+ * adding the sort element. The following sort criteria are available:
+ * <ul>
+ * <li>Horde_Imap_Client::SORT_ARRIVAL</li>
+ * <li>Horde_Imap_Client::SORT_CC</li>
+ * <li>Horde_Imap_Client::SORT_DATE</li>
+ * <li>Horde_Imap_Client::SORT_DISPLAYFROM
+ * <ul>
+ * <li>
+ * On servers that don't support SORT=DISPLAY, this criteria will
+ * fallback to doing client-side sorting.
+ * </li>
+ * </ul>
+ * </li>
+ * <li>Horde_Imap_Client::SORT_DISPLAYFROM_FALLBACK
+ * <ul>
+ * <li>
+ * On servers that don't support SORT=DISPLAY, this criteria will
+ * fallback to Horde_Imap_Client::SORT_FROM [since 2.4.0].
+ * </li>
+ * </ul>
+ * </li>
+ * <li>Horde_Imap_Client::SORT_DISPLAYTO
+ * <ul>
+ * <li>
+ * On servers that don't support SORT=DISPLAY, this criteria will
+ * fallback to doing client-side sorting.
+ * </li>
+ * </ul>
+ * </li>
+ * <li>Horde_Imap_Client::SORT_DISPLAYTO_FALLBACK
+ * <ul>
+ * <li>
+ * On servers that don't support SORT=DISPLAY, this criteria will
+ * fallback to Horde_Imap_Client::SORT_TO [since 2.4.0].
+ * </li>
+ * </ul>
+ * </li>
+ * <li>Horde_Imap_Client::SORT_FROM</li>
+ * <li>Horde_Imap_Client::SORT_SEQUENCE</li>
+ * <li>Horde_Imap_Client::SORT_SIZE</li>
+ * <li>Horde_Imap_Client::SORT_SUBJECT</li>
+ * <li>Horde_Imap_Client::SORT_TO</li>
+ * <li>
+ * [On servers that support SEARCH=FUZZY, this criteria is also
+ * available:]
+ * <ul>
+ * <li>Horde_Imap_Client::SORT_RELEVANCY</li>
+ * </ul>
+ * </li>
+ * </ul>
+ * </li>
+ * </ul>
+ *
+ * @return array An array with the following keys:
+ * - count: (integer) The number of messages that match the search
+ * criteria. Always returned.
+ * - match: (Horde_Imap_Client_Ids) The IDs that match $criteria, sorted
+ * if the 'sort' modifier was set. Returned if
+ * Horde_Imap_Client::SEARCH_RESULTS_MATCH is set.
+ * - max: (integer) The UID (default) or message sequence number (if
+ * 'sequence' is true) of the highest message that satisifies
+ * $criteria. Returns null if no matches found. Returned if
+ * Horde_Imap_Client::SEARCH_RESULTS_MAX is set.
+ * - min: (integer) The UID (default) or message sequence number (if
+ * 'sequence' is true) of the lowest message that satisifies
+ * $criteria. Returns null if no matches found. Returned if
+ * Horde_Imap_Client::SEARCH_RESULTS_MIN is set.
+ * - modseq: (integer) The highest mod-sequence for all messages being
+ * returned. Returned if 'sort' is false, the search query
+ * includes a MODSEQ command, and the server supports the
+ * CONDSTORE IMAP extension.
+ * - relevancy: (array) The list of relevancy scores. Returned if
+ * Horde_Imap_Client::SEARCH_RESULTS_RELEVANCY is set and
+ * the server supports FUZZY search matching.
+ * - save: (boolean) Whether the search results were saved. Returned if
+ * Horde_Imap_Client::SEARCH_RESULTS_SAVE is set.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ public function search($mailbox, $query = null, array $options = array())
+ {
+ $this->login();
+
+ if (empty($options['results'])) {
+ $options['results'] = array(
+ Horde_Imap_Client::SEARCH_RESULTS_MATCH,
+ Horde_Imap_Client::SEARCH_RESULTS_COUNT
+ );
+ }
+
+ // Default to an ALL search.
+ if (is_null($query)) {
+ $query = new Horde_Imap_Client_Search_Query();
+ }
+
+ // Check for SEARCHRES support.
+ if ((($pos = array_search(Horde_Imap_Client::SEARCH_RESULTS_SAVE, $options['results'])) !== false) &&
+ !$this->queryCapability('SEARCHRES')) {
+ unset($options['results'][$pos]);
+ }
+
+ // Check for SORT-related options.
+ if (!empty($options['sort'])) {
+ $sort = $this->queryCapability('SORT');
+ foreach ($options['sort'] as $key => $val) {
+ switch ($val) {
+ case Horde_Imap_Client::SORT_DISPLAYFROM_FALLBACK:
+ $options['sort'][$key] = (!is_array($sort) || !in_array('DISPLAY', $sort))
+ ? Horde_Imap_Client::SORT_FROM
+ : Horde_Imap_Client::SORT_DISPLAYFROM;
+ break;
+
+ case Horde_Imap_Client::SORT_DISPLAYTO_FALLBACK:
+ $options['sort'][$key] = (!is_array($sort) || !in_array('DISPLAY', $sort))
+ ? Horde_Imap_Client::SORT_TO
+ : Horde_Imap_Client::SORT_DISPLAYTO;
+ break;
+ }
+ }
+ }
+
+ // Check for supported charset.
+ $options['_query'] = $query->build($this->capability());
+ if (!is_null($options['_query']['charset']) &&
+ array_key_exists($options['_query']['charset'], $this->_init['s_charset']) &&
+ !$this->_init['s_charset'][$options['_query']['charset']]) {
+ foreach (array_merge(array_keys(array_filter($this->_init['s_charset'])), array('US-ASCII')) as $val) {
+ try {
+ $new_query = clone $query;
+ $new_query->charset($val);
+ break;
+ } catch (Horde_Imap_Client_Exception_SearchCharset $e) {
+ unset($new_query);
+ }
+ }
+
+ if (!isset($new_query)) {
+ throw $e;
+ }
+
+ $query = $new_query;
+ $options['_query'] = $query->build($this->capability());
+ }
+
+ /* RFC 6203: MUST NOT request relevancy results if we are not using
+ * FUZZY searching. */
+ if (in_array(Horde_Imap_Client::SEARCH_RESULTS_RELEVANCY, $options['results']) &&
+ !in_array('SEARCH=FUZZY', $options['_query']['exts_used'])) {
+ throw new InvalidArgumentException('Cannot specify RELEVANCY results if not doing a FUZZY search.');
+ }
+
+ /* Optimization - if query is just for a count of either RECENT or
+ * ALL messages, we can send status information instead. Can't
+ * optimize with unseen queries because we may cause an infinite loop
+ * between here and the status() call. */
+ if ((count($options['results']) == 1) &&
+ (reset($options['results']) == Horde_Imap_Client::SEARCH_RESULTS_COUNT)) {
+ switch ($options['_query']['query']) {
+ case 'ALL':
+ $ret = $this->status($this->_selected, Horde_Imap_Client::STATUS_MESSAGES);
+ return array('count' => $ret['messages']);
+
+ case 'RECENT':
+ $ret = $this->status($this->_selected, Horde_Imap_Client::STATUS_RECENT);
+ return array('count' => $ret['recent']);
+ }
+ }
+
+ $this->openMailbox($mailbox, Horde_Imap_Client::OPEN_AUTO);
+
+ /* Take advantage of search result caching. If CONDSTORE available,
+ * we can cache all queries and invalidate the cache when the MODSEQ
+ * changes. If CONDSTORE not available, we can only store queries
+ * that don't involve flags. We store results by hashing the options
+ * array - the generated query is already added to '_query' key
+ * above. */
+ $cache = null;
+ if (empty($options['nocache']) &&
+ $this->_initCache(true) &&
+ (isset($this->_temp['enabled']['CONDSTORE']) ||
+ !$query->flagSearch())) {
+ $cache = $this->_getSearchCache('search', $options);
+ if (isset($cache['data'])) {
+ if (isset($cache['data']['match'])) {
+ $cache['data']['match'] = $this->getIdsOb($cache['data']['match']);
+ }
+ return $cache['data'];
+ }
+ }
+
+ /* Optimization: Catch when there are no messages in a mailbox. */
+ $status_res = $this->status($this->_selected, Horde_Imap_Client::STATUS_MESSAGES | Horde_Imap_Client::STATUS_HIGHESTMODSEQ);
+ if ($status_res['messages'] ||
+ in_array(Horde_Imap_Client::SEARCH_RESULTS_SAVE, $options['results'])) {
+ /* RFC 4551 [3.1] - trying to do a MODSEQ SEARCH on a mailbox that
+ * doesn't support it will return BAD. */
+ if (in_array('CONDSTORE', $options['_query']['exts']) &&
+ !$this->_mailboxOb()->getStatus(Horde_Imap_Client::STATUS_HIGHESTMODSEQ)) {
+ throw new Horde_Imap_Client_Exception(
+ Horde_Imap_Client_Translation::t("Mailbox does not support mod-sequences."),
+ Horde_Imap_Client_Exception::MBOXNOMODSEQ
+ );
+ }
+
+ $ret = $this->_search($query, $options);
+ } else {
+ $ret = array(
+ 'count' => 0
+ );
+
+ foreach ($options['results'] as $val) {
+ switch ($val) {
+ case Horde_Imap_Client::SEARCH_RESULTS_MATCH:
+ $ret['match'] = $this->getIdsOb();
+ break;
+
+ case Horde_Imap_Client::SEARCH_RESULTS_MAX:
+ $ret['max'] = null;
+ break;
+
+ case Horde_Imap_Client::SEARCH_RESULTS_MIN:
+ $ret['min'] = null;
+ break;
+
+ case Horde_Imap_Client::SEARCH_RESULTS_MIN:
+ if (isset($status_res['highestmodseq'])) {
+ $ret['modseq'] = $status_res['highestmodseq'];
+ }
+ break;
+
+ case Horde_Imap_Client::SEARCH_RESULTS_RELEVANCY:
+ $ret['relevancy'] = array();
+ break;
+ }
+ }
+ }
+
+ if ($cache) {
+ $save = $ret;
+ if (isset($save['match'])) {
+ $save['match'] = strval($ret['match']);
+ }
+ $this->_setSearchCache($save, $cache);
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Search a mailbox.
+ *
+ * @param object $query The search query.
+ * @param array $options Additional options. The '_query' key contains
+ * the value of $query->build().
+ *
+ * @return Horde_Imap_Client_Ids An array of IDs.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ abstract protected function _search($query, $options);
+
+ /**
+ * Set the comparator to use for searching/sorting (RFC 5255).
+ *
+ * @param string $comparator The comparator string (see RFC 4790 [3.1] -
+ * "collation-id" - for format). The reserved
+ * string 'default' can be used to select
+ * the default comparator.
+ *
+ * @throws Horde_Imap_Client_Exception
+ * @throws Horde_Imap_Client_Exception_NoSupportExtension
+ */
+ public function setComparator($comparator = null)
+ {
+ $comp = is_null($comparator)
+ ? $this->getParam('comparator')
+ : $comparator;
+ if (is_null($comp)) {
+ return;
+ }
+
+ $this->login();
+
+ $i18n = $this->queryCapability('I18NLEVEL');
+ if (empty($i18n) || (max($i18n) < 2)) {
+ throw new Horde_Imap_Client_Exception_NoSupportExtension(
+ 'I18NLEVEL',
+ 'The IMAP server does not support changing SEARCH/SORT comparators.'
+ );
+ }
+
+ $this->_setComparator($comp);
+ }
+
+ /**
+ * Set the comparator to use for searching/sorting (RFC 5255).
+ *
+ * @param string $comparator The comparator string (see RFC 4790 [3.1] -
+ * "collation-id" - for format). The reserved
+ * string 'default' can be used to select
+ * the default comparator.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ abstract protected function _setComparator($comparator);
+
+ /**
+ * Get the comparator used for searching/sorting (RFC 5255).
+ *
+ * @return mixed Null if the default comparator is being used, or an
+ * array of comparator information (see RFC 5255 [4.8]).
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ public function getComparator()
+ {
+ $this->login();
+
+ $i18n = $this->queryCapability('I18NLEVEL');
+ if (empty($i18n) || (max($i18n) < 2)) {
+ return null;
+ }
+
+ return $this->_getComparator();
+ }
+
+ /**
+ * Get the comparator used for searching/sorting (RFC 5255).
+ *
+ * @return mixed Null if the default comparator is being used, or an
+ * array of comparator information (see RFC 5255 [4.8]).
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ abstract protected function _getComparator();
+
+ /**
+ * Thread sort a given list of messages (RFC 5256).
+ *
+ * @param mixed $mailbox The mailbox to query. Either a
+ * Horde_Imap_Client_Mailbox object or a string
+ * (UTF-8).
+ * @param array $options Additional options:
+ * <ul>
+ * <li>
+ * criteria: (mixed) The following thread criteria are available:
+ * <ul>
+ * <li>Horde_Imap_Client::THREAD_ORDEREDSUBJECT</li>
+ * <li>Horde_Imap_Client::THREAD_REFERENCES</li>
+ * <li>Horde_Imap_Client::THREAD_REFS</li>
+ * <li>
+ * Other algorithms can be explicitly specified by passing the IMAP
+ * thread algorithm in as a string value.
+ * </li>
+ * </ul>
+ * DEFAULT: Horde_Imap_Client::THREAD_ORDEREDSUBJECT
+ * </li>
+ * <li>
+ * search: (Horde_Imap_Client_Search_Query) The search query.
+ * DEFAULT: All messages in mailbox included in thread sort.
+ * </li>
+ * <li>
+ * sequence: (boolean) If true, each message is stored and referred to
+ * by its message sequence number.
+ * DEFAULT: Stored/referred to by UID.
+ * </li>
+ * </ul>
+ *
+ * @return Horde_Imap_Client_Data_Thread A thread data object.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ public function thread($mailbox, array $options = array())
+ {
+ // Open mailbox call will handle the login.
+ $this->openMailbox($mailbox, Horde_Imap_Client::OPEN_AUTO);
+
+ /* Take advantage of search result caching. If CONDSTORE available,
+ * we can cache all queries and invalidate the cache when the MODSEQ
+ * changes. If CONDSTORE not available, we can only store queries
+ * that don't involve flags. See search() for similar caching. */
+ $cache = null;
+ if ($this->_initCache(true) &&
+ (isset($this->_temp['enabled']['CONDSTORE']) ||
+ empty($options['search']) ||
+ !$options['search']->flagSearch())) {
+ $cache = $this->_getSearchCache('thread', $options);
+ if (isset($cache['data']) &&
+ ($cache['data'] instanceof Horde_Imap_Client_Data_Thread)) {
+ return $cache['data'];
+ }
+ }
+
+ $status_res = $this->status($this->_selected, Horde_Imap_Client::STATUS_MESSAGES);
+
+ $ob = $status_res['messages']
+ ? $this->_thread($options)
+ : new Horde_Imap_Client_Data_Thread(array(), empty($options['sequence']) ? 'uid' : 'sequence');
+
+ if ($cache) {
+ $this->_setSearchCache($ob, $cache);
+ }
+
+ return $ob;
+ }
+
+ /**
+ * Thread sort a given list of messages (RFC 5256).
+ *
+ * @param array $options Additional options. See thread().
+ *
+ * @return Horde_Imap_Client_Data_Thread A thread data object.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ abstract protected function _thread($options);
+
+ /**
+ * Fetch message data (see RFC 3501 [6.4.5]).
+ *
+ * @param mixed $mailbox The mailbox to search.
+ * Either a
+ * Horde_Imap_Client_Mailbox
+ * object or a string (UTF-8).
+ * @param Horde_Imap_Client_Fetch_Query $query Fetch query object.
+ * @param array $options Additional options:
+ * - changedsince: (integer) Only return messages that have a
+ * mod-sequence larger than this value. This option
+ * requires the CONDSTORE IMAP extension (if not present,
+ * this value is ignored). Additionally, the mailbox
+ * must support mod-sequences or an exception will be
+ * thrown. If valid, this option implicity adds the
+ * mod-sequence fetch criteria to the fetch command.
+ * DEFAULT: Mod-sequence values are ignored.
+ * - exists: (boolean) Ensure that all ids returned exist on the server.
+ * If false, the list of ids returned in the results object
+ * is not guaranteed to reflect the current state of the
+ * remote mailbox.
+ * DEFAULT: false
+ * - ids: (Horde_Imap_Client_Ids) A list of messages to fetch data from.
+ * DEFAULT: All messages in $mailbox will be fetched.
+ * - nocache: (boolean) If true, will not cache the results (previously
+ * cached data will still be used to generate results) [since
+ * 2.8.0].
+ * DEFAULT: false
+ *
+ * @return Horde_Imap_Client_Fetch_Results A results object.
+ *
+ * @throws Horde_Imap_Client_Exception
+ * @throws Horde_Imap_Client_Exception_NoSupportExtension
+ */
+ public function fetch($mailbox, $query, array $options = array())
+ {
+ try {
+ $ret = $this->_fetchWrapper($mailbox, $query, $options);
+ unset($this->_temp['fetch_nocache']);
+ return $ret;
+ } catch (Exception $e) {
+ unset($this->_temp['fetch_nocache']);
+ throw $e;
+ }
+ }
+
+ /**
+ * Wrapper for fetch() to allow internal state to be rest on exception.
+ *
+ * @internal
+ * @see fetch()
+ */
+ private function _fetchWrapper($mailbox, $query, $options)
+ {
+ $this->login();
+
+ $query = clone $query;
+
+ $cache_array = $header_cache = $new_query = array();
+
+ if (empty($options['ids'])) {
+ $options['ids'] = $this->getIdsOb(Horde_Imap_Client_Ids::ALL);
+ } elseif ($options['ids']->isEmpty()) {
+ return new Horde_Imap_Client_Fetch_Results($this->_fetchDataClass);
+ } elseif ($options['ids']->search_res &&
+ !$this->queryCapability('SEARCHRES')) {
+ /* SEARCHRES requires server support. */
+ throw new Horde_Imap_Client_Exception_NoSupportExtension('SEARCHRES');
+ }
+
+ $this->openMailbox($mailbox, Horde_Imap_Client::OPEN_AUTO);
+ $mbox_ob = $this->_mailboxOb();
+
+ if (!empty($options['nocache'])) {
+ $this->_temp['fetch_nocache'] = true;
+ }
+
+ $cf = $this->_initCache(true)
+ ? $this->_cacheFields()
+ : array();
+
+ if (!empty($cf)) {
+ /* If using cache, we store by UID so we need to return UIDs. */
+ $query->uid();
+ }
+
+ $modseq_check = !empty($options['changedsince']);
+ if ($query->contains(Horde_Imap_Client::FETCH_MODSEQ)) {
+ if (!isset($this->_temp['enabled']['CONDSTORE'])) {
+ unset($query[Horde_Imap_Client::FETCH_MODSEQ]);
+ } elseif (empty($options['changedsince'])) {
+ $modseq_check = true;
+ }
+ }
+
+ if ($modseq_check &&
+ !$mbox_ob->getStatus(Horde_Imap_Client::STATUS_HIGHESTMODSEQ)) {
+ /* RFC 4551 [3.1] - trying to do a MODSEQ FETCH on a mailbox that
+ * doesn't support it will return BAD. */
+ throw new Horde_Imap_Client_Exception(
+ Horde_Imap_Client_Translation::t("Mailbox does not support mod-sequences."),
+ Horde_Imap_Client_Exception::MBOXNOMODSEQ
+ );
+ }
+
+ /* Determine if caching is available and if anything in $query is
+ * cacheable. */
+ foreach ($cf as $k => $v) {
+ if (isset($query[$k])) {
+ switch ($k) {
+ case Horde_Imap_Client::FETCH_ENVELOPE:
+ case Horde_Imap_Client::FETCH_FLAGS:
+ case Horde_Imap_Client::FETCH_IMAPDATE:
+ case Horde_Imap_Client::FETCH_SIZE:
+ case Horde_Imap_Client::FETCH_STRUCTURE:
+ $cache_array[$k] = $v;
+ break;
+
+ case Horde_Imap_Client::FETCH_HEADERS:
+ $this->_temp['headers_caching'] = array();
+
+ foreach ($query[$k] as $key => $val) {
+ /* Only cache if directly requested. Iterate through
+ * requests to ensure at least one can be cached. */
+ if (!empty($val['cache']) && !empty($val['peek'])) {
+ $cache_array[$k] = $v;
+ ksort($val);
+ $header_cache[$key] = hash('md5', serialize($val));
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ $ret = new Horde_Imap_Client_Fetch_Results(
+ $this->_fetchDataClass,
+ $options['ids']->sequence ? Horde_Imap_Client_Fetch_Results::SEQUENCE : Horde_Imap_Client_Fetch_Results::UID
+ );
+
+ /* If nothing is cacheable, we can do a straight search. */
+ if (empty($cache_array)) {
+ $options['_query'] = $query;
+ $this->_fetch($ret, array($options));
+ return $ret;
+ }
+
+ $cs_ret = empty($options['changedsince'])
+ ? null
+ : clone $ret;
+
+ /* Convert special searches to UID lists and create mapping. */
+ $ids = $this->resolveIds($this->_selected, $options['ids'], empty($options['exists']) ? 1 : 2);
+
+ /* Add non-user settable cache fields. */
+ $cache_array[Horde_Imap_Client::FETCH_DOWNGRADED] = self::CACHE_DOWNGRADED;
+
+ /* Get the cached values. */
+ $data = $this->_cache->get($this->_selected, $ids->ids, array_values($cache_array), $mbox_ob->getStatus(Horde_Imap_Client::STATUS_UIDVALIDITY));
+
+ /* Build a list of what we still need. */
+ $map = array_flip($mbox_ob->map->map);
+ $sequence = $options['ids']->sequence;
+ foreach ($ids as $uid) {
+ $crit = clone $query;
+
+ if ($sequence) {
+ if (!isset($map[$uid])) {
+ continue;
+ }
+ $entry_idx = $map[$uid];
+ } else {
+ $entry_idx = $uid;
+ unset($crit[Horde_Imap_Client::FETCH_UID]);
+ }
+
+ $entry = $ret->get($entry_idx);
+
+ if (isset($map[$uid])) {
+ $entry->setSeq($map[$uid]);
+ unset($crit[Horde_Imap_Client::FETCH_SEQ]);
+ }
+
+ $entry->setUid($uid);
+
+ foreach ($cache_array as $key => $cid) {
+ switch ($key) {
+ case Horde_Imap_Client::FETCH_DOWNGRADED:
+ if (!empty($data[$uid][$cid])) {
+ $entry->setDowngraded(true);
+ }
+ break;
+
+ case Horde_Imap_Client::FETCH_ENVELOPE:
+ if (isset($data[$uid][$cid]) &&
+ ($data[$uid][$cid] instanceof Horde_Imap_Client_Data_Envelope)) {
+ $entry->setEnvelope($data[$uid][$cid]);
+ unset($crit[$key]);
+ }
+ break;
+
+ case Horde_Imap_Client::FETCH_FLAGS:
+ if (isset($data[$uid][$cid]) &&
+ is_array($data[$uid][$cid])) {
+ $entry->setFlags($data[$uid][$cid]);
+ unset($crit[$key]);
+ }
+ break;
+
+ case Horde_Imap_Client::FETCH_HEADERS:
+ foreach ($header_cache as $hkey => $hval) {
+ if (isset($data[$uid][$cid][$hval])) {
+ /* We have found a cached entry with the same
+ * MD5 sum. */
+ $entry->setHeaders($hkey, $data[$uid][$cid][$hval]);
+ $crit->remove($key, $hkey);
+ } else {
+ $this->_temp['headers_caching'][$hkey] = $hval;
+ }
+ }
+ break;
+
+ case Horde_Imap_Client::FETCH_IMAPDATE:
+ if (isset($data[$uid][$cid]) &&
+ ($data[$uid][$cid] instanceof Horde_Imap_Client_DateTime)) {
+ $entry->setImapDate($data[$uid][$cid]);
+ unset($crit[$key]);
+ }
+ break;
+
+ case Horde_Imap_Client::FETCH_SIZE:
+ if (isset($data[$uid][$cid])) {
+ $entry->setSize($data[$uid][$cid]);
+ unset($crit[$key]);
+ }
+ break;
+
+ case Horde_Imap_Client::FETCH_STRUCTURE:
+ if (isset($data[$uid][$cid]) &&
+ ($data[$uid][$cid] instanceof Horde_Mime_Part)) {
+ $entry->setStructure($data[$uid][$cid]);
+ unset($crit[$key]);
+ }
+ break;
+ }
+ }
+
+ if (count($crit)) {
+ $sig = $crit->hash();
+ if (isset($new_query[$sig])) {
+ $new_query[$sig]['i'][] = $entry_idx;
+ } else {
+ $new_query[$sig] = array(
+ 'c' => $crit,
+ 'i' => array($entry_idx)
+ );
+ }
+ }
+ }
+
+ $to_fetch = array();
+ foreach ($new_query as $val) {
+ $ids_ob = $this->getIdsOb(null, $sequence);
+ $ids_ob->duplicates = true;
+ $ids_ob->add($val['i']);
+ $to_fetch[] = array_merge($options, array(
+ '_query' => $val['c'],
+ 'ids' => $ids_ob
+ ));
+ }
+
+ if (!empty($to_fetch)) {
+ $this->_fetch(is_null($cs_ret) ? $ret : $cs_ret, $to_fetch);
+ }
+
+ if (is_null($cs_ret)) {
+ return $ret;
+ }
+
+ /* If doing changedsince query, and all other data is cached, we still
+ * need to hit IMAP server to determine proper results set. */
+ if (empty($new_query)) {
+ $squery = new Horde_Imap_Client_Search_Query();
+ $squery->modseq($options['changedsince'] + 1);
+ $squery->ids($options['ids']);
+
+ $cs = $this->search($this->_selected, $squery, array(
+ 'sequence' => $sequence
+ ));
+
+ foreach ($cs['match'] as $val) {
+ $entry = $ret->get($val);
+ if ($sequence) {
+ $entry->setSeq($val);
+ } else {
+ $entry->setUid($val);
+ }
+ $cs_ret[$val] = $entry;
+ }
+ } else {
+ foreach ($cs_ret as $key => $val) {
+ $val->merge($ret->get($key));
+ }
+ }
+
+ return $cs_ret;
+ }
+
+ /**
+ * Fetch message data.
+ *
+ * Fetch queries should be grouped in the $queries argument. Each value
+ * is an array of fetch options, with the fetch query stored in the
+ * '_query' parameter. IMPORTANT: All queries must have the same ID
+ * type (either sequence or UID).
+ *
+ * @param Horde_Imap_Client_Fetch_Results $results Fetch results.
+ * @param array $queries The list of queries.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ abstract protected function _fetch(Horde_Imap_Client_Fetch_Results $results,
+ $queries);
+
+ /**
+ * Get the list of vanished messages (UIDs that have been expunged since a
+ * given mod-sequence value).
+ *
+ * @param mixed $mailbox The mailbox to query. Either a
+ * Horde_Imap_Client_Mailbox object or a string
+ * (UTF-8).
+ * @param integer $modseq Search for expunged messages after this
+ * mod-sequence value.
+ * @param array $opts Additional options:
+ * - ids: (Horde_Imap_Client_Ids) Restrict to these UIDs.
+ * DEFAULT: Returns full list of UIDs vanished (QRESYNC only).
+ * This option is REQUIRED for non-QRESYNC servers or
+ * else an empty list will be returned.
+ *
+ * @return Horde_Imap_Client_Ids List of UIDs that have vanished.
+ *
+ * @throws Horde_Imap_Client_NoSupportExtension
+ */
+ public function vanished($mailbox, $modseq, array $opts = array())
+ {
+ $this->login();
+
+ $qresync = $this->queryCapability('QRESYNC');
+
+ if (empty($opts['ids'])) {
+ if (!$qresync) {
+ return $this->getIdsOb();
+ }
+ $opts['ids'] = $this->getIdsOb(Horde_Imap_Client_Ids::ALL);
+ } elseif ($opts['ids']->isEmpty()) {
+ return $this->getIdsOb();
+ } elseif ($opts['ids']->sequence) {
+ throw new InvalidArgumentException('Vanished requires UIDs.');
+ }
+
+ $this->openMailbox($mailbox, Horde_Imap_Client::OPEN_AUTO);
+
+ if ($qresync) {
+ if (!$this->_mailboxOb()->getStatus(Horde_Imap_Client::STATUS_HIGHESTMODSEQ)) {
+ throw new Horde_Imap_Client_Exception(
+ Horde_Imap_Client_Translation::t("Mailbox does not support mod-sequences."),
+ Horde_Imap_Client_Exception::MBOXNOMODSEQ
+ );
+ }
+
+ return $this->_vanished(max(1, $modseq), $opts['ids']);
+ }
+
+ $ids = $this->resolveIds($mailbox, $opts['ids']);
+
+ $squery = new Horde_Imap_Client_Search_Query();
+ $squery->ids($this->getIdsOb($ids->range_string));
+ $search = $this->search($mailbox, $squery, array(
+ 'nocache' => true
+ ));
+
+ return $this->getIdsOb(array_diff($ids->ids, $search['match']->ids));
+ }
+
+ /**
+ * Get the list of vanished messages.
+ *
+ * @param integer $modseq Mod-sequence value.
+ * @param Horde_Imap_Client_Ids $ids UIDs.
+ *
+ * @return Horde_Imap_Client_Ids List of UIDs that have vanished.
+ */
+ abstract protected function _vanished($modseq, Horde_Imap_Client_Ids $ids);
+
+ /**
+ * Store message flag data (see RFC 3501 [6.4.6]).
+ *
+ * @param mixed $mailbox The mailbox containing the messages to modify.
+ * Either a Horde_Imap_Client_Mailbox object or a
+ * string (UTF-8).
+ * @param array $options Additional options:
+ * - add: (array) An array of flags to add.
+ * DEFAULT: No flags added.
+ * - ids: (Horde_Imap_Client_Ids) The list of messages to modify.
+ * DEFAULT: All messages in $mailbox will be modified.
+ * - remove: (array) An array of flags to remove.
+ * DEFAULT: No flags removed.
+ * - replace: (array) Replace the current flags with this set
+ * of flags. Overrides both the 'add' and 'remove' options.
+ * DEFAULT: No replace is performed.
+ * - unchangedsince: (integer) Only changes flags if the mod-sequence ID
+ * of the message is equal or less than this value.
+ * Requires the CONDSTORE IMAP extension on the server.
+ * Also requires the mailbox to support mod-sequences.
+ * Will throw an exception if either condition is not
+ * met.
+ * DEFAULT: mod-sequence is ignored when applying
+ * changes
+ *
+ * @return Horde_Imap_Client_Ids A Horde_Imap_Client_Ids object
+ * containing the list of IDs that failed
+ * the 'unchangedsince' test.
+ *
+ * @throws Horde_Imap_Client_Exception
+ * @throws Horde_Imap_Client_Exception_NoSupportExtension
+ */
+ public function store($mailbox, array $options = array())
+ {
+ // Open mailbox call will handle the login.
+ $this->openMailbox($mailbox, Horde_Imap_Client::OPEN_READWRITE);
+
+ /* SEARCHRES requires server support. */
+ if (empty($options['ids'])) {
+ $options['ids'] = $this->getIdsOb(Horde_Imap_Client_Ids::ALL);
+ } elseif ($options['ids']->isEmpty()) {
+ return $this->getIdsOb();
+ } elseif ($options['ids']->search_res &&
+ !$this->queryCapability('SEARCHRES')) {
+ throw new Horde_Imap_Client_Exception_NoSupportExtension('SEARCHRES');
+ }
+
+ if (!empty($options['unchangedsince'])) {
+ if (!isset($this->_temp['enabled']['CONDSTORE'])) {
+ throw new Horde_Imap_Client_Exception_NoSupportExtension('CONDSTORE');
+ }
+
+ /* RFC 4551 [3.1] - trying to do a UNCHANGEDSINCE STORE on a
+ * mailbox that doesn't support it will return BAD. */
+ if (!$this->_mailboxOb()->getStatus(Horde_Imap_Client::STATUS_HIGHESTMODSEQ)) {
+ throw new Horde_Imap_Client_Exception(
+ Horde_Imap_Client_Translation::t("Mailbox does not support mod-sequences."),
+ Horde_Imap_Client_Exception::MBOXNOMODSEQ
+ );
+ }
+ }
+
+ return $this->_store($options);
+ }
+
+ /**
+ * Store message flag data.
+ *
+ * @param array $options Additional options.
+ *
+ * @return Horde_Imap_Client_Ids A Horde_Imap_Client_Ids object
+ * containing the list of IDs that failed
+ * the 'unchangedsince' test.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ abstract protected function _store($options);
+
+ /**
+ * Copy messages to another mailbox.
+ *
+ * @param mixed $source The source mailbox. Either a
+ * Horde_Imap_Client_Mailbox object or a string
+ * (UTF-8).
+ * @param mixed $dest The destination mailbox. Either a
+ * Horde_Imap_Client_Mailbox object or a string
+ * (UTF-8).
+ * @param array $options Additional options:
+ * - create: (boolean) Try to create $dest if it does not exist?
+ * DEFAULT: No.
+ * - ids: (Horde_Imap_Client_Ids) The list of messages to copy.
+ * DEFAULT: All messages in $mailbox will be copied.
+ * - move: (boolean) If true, delete the original messages.
+ * DEFAULT: Original messages are not deleted.
+ *
+ * @return mixed An array mapping old UIDs (keys) to new UIDs (values) on
+ * success (if the IMAP server and/or driver support the
+ * UIDPLUS extension) or true.
+ *
+ * @throws Horde_Imap_Client_Exception
+ * @throws Horde_Imap_Client_Exception_NoSupportExtension
+ */
+ public function copy($source, $dest, array $options = array())
+ {
+ // Open mailbox call will handle the login.
+ $this->openMailbox($source, empty($options['move']) ? Horde_Imap_Client::OPEN_AUTO : Horde_Imap_Client::OPEN_READWRITE);
+
+ /* SEARCHRES requires server support. */
+ if (empty($options['ids'])) {
+ $options['ids'] = $this->getIdsOb(Horde_Imap_Client_Ids::ALL);
+ } elseif ($options['ids']->isEmpty()) {
+ return array();
+ } elseif ($options['ids']->search_res &&
+ !$this->queryCapability('SEARCHRES')) {
+ throw new Horde_Imap_Client_Exception_NoSupportExtension('SEARCHRES');
+ }
+
+ return $this->_copy(Horde_Imap_Client_Mailbox::get($dest), $options);
+ }
+
+ /**
+ * Copy messages to another mailbox.
+ *
+ * @param Horde_Imap_Client_Mailbox $dest The destination mailbox.
+ * @param array $options Additional options.
+ *
+ * @return mixed An array mapping old UIDs (keys) to new UIDs (values) on
+ * success (if the IMAP server and/or driver support the
+ * UIDPLUS extension) or true.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ abstract protected function _copy(Horde_Imap_Client_Mailbox $dest,
+ $options);
+
+ /**
+ * Set quota limits. The server must support the IMAP QUOTA extension
+ * (RFC 2087).
+ *
+ * @param mixed $root The quota root. Either a
+ * Horde_Imap_Client_Mailbox object or a string
+ * (UTF-8).
+ * @param array $resources The resource values to set. Keys are the
+ * resource atom name; value is the resource
+ * value.
+ *
+ * @throws Horde_Imap_Client_Exception
+ * @throws Horde_Imap_Client_Exception_NoSupportExtension
+ */
+ public function setQuota($root, array $resources = array())
+ {
+ $this->login();
+
+ if (!$this->queryCapability('QUOTA')) {
+ throw new Horde_Imap_Client_Exception_NoSupportExtension('QUOTA');
+ }
+
+ if (!empty($resources)) {
+ $this->_setQuota(Horde_Imap_Client_Mailbox::get($root), $resources);
+ }
+ }
+
+ /**
+ * Set quota limits.
+ *
+ * @param Horde_Imap_Client_Mailbox $root The quota root.
+ * @param array $resources The resource values to set.
+ *
+ * @return boolean True on success.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ abstract protected function _setQuota(Horde_Imap_Client_Mailbox $root,
+ $resources);
+
+ /**
+ * Get quota limits. The server must support the IMAP QUOTA extension
+ * (RFC 2087).
+ *
+ * @param mixed $root The quota root. Either a Horde_Imap_Client_Mailbox
+ * object or a string (UTF-8).
+ *
+ * @return mixed An array with resource keys. Each key holds an array
+ * with 2 values: 'limit' and 'usage'.
+ *
+ * @throws Horde_Imap_Client_Exception
+ * @throws Horde_Imap_Client_Exception_NoSupportExtension
+ */
+ public function getQuota($root)
+ {
+ $this->login();
+
+ if (!$this->queryCapability('QUOTA')) {
+ throw new Horde_Imap_Client_Exception_NoSupportExtension('QUOTA');
+ }
+
+ return $this->_getQuota(Horde_Imap_Client_Mailbox::get($root));
+ }
+
+ /**
+ * Get quota limits.
+ *
+ * @param Horde_Imap_Client_Mailbox $root The quota root.
+ *
+ * @return mixed An array with resource keys. Each key holds an array
+ * with 2 values: 'limit' and 'usage'.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ abstract protected function _getQuota(Horde_Imap_Client_Mailbox $root);
+
+ /**
+ * Get quota limits for a mailbox. The server must support the IMAP QUOTA
+ * extension (RFC 2087).
+ *
+ * @param mixed $mailbox A mailbox. Either a Horde_Imap_Client_Mailbox
+ * object or a string (UTF-8).
+ *
+ * @return mixed An array with the keys being the quota roots. Each key
+ * holds an array with resource keys: each of these keys
+ * holds an array with 2 values: 'limit' and 'usage'.
+ *
+ * @throws Horde_Imap_Client_Exception
+ * @throws Horde_Imap_Client_Exception_NoSupportExtension
+ */
+ public function getQuotaRoot($mailbox)
+ {
+ $this->login();
+
+ if (!$this->queryCapability('QUOTA')) {
+ throw new Horde_Imap_Client_Exception_NoSupportExtension('QUOTA');
+ }
+
+ return $this->_getQuotaRoot(Horde_Imap_Client_Mailbox::get($mailbox));
+ }
+
+ /**
+ * Get quota limits for a mailbox.
+ *
+ * @param Horde_Imap_Client_Mailbox $mailbox A mailbox.
+ *
+ * @return mixed An array with the keys being the quota roots. Each key
+ * holds an array with resource keys: each of these keys
+ * holds an array with 2 values: 'limit' and 'usage'.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ abstract protected function _getQuotaRoot(Horde_Imap_Client_Mailbox $mailbox);
+
+ /**
+ * Get the ACL rights for a given mailbox. The server must support the
+ * IMAP ACL extension (RFC 2086/4314).
+ *
+ * @param mixed $mailbox A mailbox. Either a Horde_Imap_Client_Mailbox
+ * object or a string (UTF-8).
+ *
+ * @return array An array with identifiers as the keys and
+ * Horde_Imap_Client_Data_Acl objects as the values.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ public function getACL($mailbox)
+ {
+ $this->login();
+ return $this->_getACL(Horde_Imap_Client_Mailbox::get($mailbox));
+ }
+
+ /**
+ * Get ACL rights for a given mailbox.
+ *
+ * @param Horde_Imap_Client_Mailbox $mailbox A mailbox.
+ *
+ * @return array An array with identifiers as the keys and
+ * Horde_Imap_Client_Data_Acl objects as the values.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ abstract protected function _getACL(Horde_Imap_Client_Mailbox $mailbox);
+
+ /**
+ * Set ACL rights for a given mailbox/identifier.
+ *
+ * @param mixed $mailbox A mailbox. Either a Horde_Imap_Client_Mailbox
+ * object or a string (UTF-8).
+ * @param string $identifier The identifier to alter (UTF-8).
+ * @param array $options Additional options:
+ * - rights: (string) The rights to alter or set.
+ * - action: (string, optional) If 'add' or 'remove', adds or removes the
+ * specified rights. Sets the rights otherwise.
+ *
+ * @throws Horde_Imap_Client_Exception
+ * @throws Horde_Imap_Client_Exception_NoSupportExtension
+ */
+ public function setACL($mailbox, $identifier, $options)
+ {
+ $this->login();
+
+ if (!$this->queryCapability('ACL')) {
+ throw new Horde_Imap_Client_Exception_NoSupportExtension('ACL');
+ }
+
+ if (empty($options['rights'])) {
+ if (!isset($options['action']) ||
+ ($options['action'] != 'add' && $options['action'] != 'remove')) {
+ $this->_deleteACL(
+ Horde_Imap_Client_Mailbox::get($mailbox),
+ Horde_Imap_Client_Utf7imap::Utf8ToUtf7Imap($identifier)
+ );
+ }
+ return;
+ }
+
+ $acl = ($options['rights'] instanceof Horde_Imap_Client_Data_Acl)
+ ? $options['rights']
+ : new Horde_Imap_Client_Data_Acl(strval($options['rights']));
+
+ $options['rights'] = $acl->getString(
+ $this->queryCapability('RIGHTS')
+ ? Horde_Imap_Client_Data_AclCommon::RFC_4314
+ : Horde_Imap_Client_Data_AclCommon::RFC_2086
+ );
+ if (isset($options['action'])) {
+ switch ($options['action']) {
+ case 'add':
+ $options['rights'] = '+' . $options['rights'];
+ break;
+ case 'remove':
+ $options['rights'] = '-' . $options['rights'];
+ break;
+ }
+ }
+
+ $this->_setACL(
+ Horde_Imap_Client_Mailbox::get($mailbox),
+ Horde_Imap_Client_Utf7imap::Utf8ToUtf7Imap($identifier),
+ $options
+ );
+ }
+
+ /**
+ * Set ACL rights for a given mailbox/identifier.
+ *
+ * @param Horde_Imap_Client_Mailbox $mailbox A mailbox.
+ * @param string $identifier The identifier to alter
+ * (UTF7-IMAP).
+ * @param array $options Additional options. 'rights'
+ * contains the string of
+ * rights to set on the server.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ abstract protected function _setACL(Horde_Imap_Client_Mailbox $mailbox,
+ $identifier, $options);
+
+ /**
+ * Deletes ACL rights for a given mailbox/identifier.
+ *
+ * @param mixed $mailbox A mailbox. Either a Horde_Imap_Client_Mailbox
+ * object or a string (UTF-8).
+ * @param string $identifier The identifier to delete (UTF-8).
+ *
+ * @throws Horde_Imap_Client_Exception
+ * @throws Horde_Imap_Client_Exception_NoSupportExtension
+ */
+ public function deleteACL($mailbox, $identifier)
+ {
+ $this->login();
+
+ if (!$this->queryCapability('ACL')) {
+ throw new Horde_Imap_Client_Exception_NoSupportExtension('ACL');
+ }
+
+ $this->_deleteACL(
+ Horde_Imap_Client_Mailbox::get($mailbox),
+ Horde_Imap_Client_Utf7imap::Utf8ToUtf7Imap($identifier)
+ );
+ }
+
+ /**
+ * Deletes ACL rights for a given mailbox/identifier.
+ *
+ * @param Horde_Imap_Client_Mailbox $mailbox A mailbox.
+ * @param string $identifier The identifier to delete
+ * (UTF7-IMAP).
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ abstract protected function _deleteACL(Horde_Imap_Client_Mailbox $mailbox,
+ $identifier);
+
+ /**
+ * List the ACL rights for a given mailbox/identifier. The server must
+ * support the IMAP ACL extension (RFC 2086/4314).
+ *
+ * @param mixed $mailbox A mailbox. Either a Horde_Imap_Client_Mailbox
+ * object or a string (UTF-8).
+ * @param string $identifier The identifier to query (UTF-8).
+ *
+ * @return Horde_Imap_Client_Data_AclRights An ACL data rights object.
+ *
+ * @throws Horde_Imap_Client_Exception
+ * @throws Horde_Imap_Client_Exception_NoSupportExtension
+ */
+ public function listACLRights($mailbox, $identifier)
+ {
+ $this->login();
+
+ if (!$this->queryCapability('ACL')) {
+ throw new Horde_Imap_Client_Exception_NoSupportExtension('ACL');
+ }
+
+ return $this->_listACLRights(
+ Horde_Imap_Client_Mailbox::get($mailbox),
+ Horde_Imap_Client_Utf7imap::Utf8ToUtf7Imap($identifier)
+ );
+ }
+
+ /**
+ * Get ACL rights for a given mailbox/identifier.
+ *
+ * @param Horde_Imap_Client_Mailbox $mailbox A mailbox.
+ * @param string $identifier The identifier to query
+ * (UTF7-IMAP).
+ *
+ * @return Horde_Imap_Client_Data_AclRights An ACL data rights object.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ abstract protected function _listACLRights(Horde_Imap_Client_Mailbox $mailbox,
+ $identifier);
+
+ /**
+ * Get the ACL rights for the current user for a given mailbox. The
+ * server must support the IMAP ACL extension (RFC 2086/4314).
+ *
+ * @param mixed $mailbox A mailbox. Either a Horde_Imap_Client_Mailbox
+ * object or a string (UTF-8).
+ *
+ * @return Horde_Imap_Client_Data_Acl An ACL data object.
+ *
+ * @throws Horde_Imap_Client_Exception
+ * @throws Horde_Imap_Client_Exception_NoSupportExtension
+ */
+ public function getMyACLRights($mailbox)
+ {
+ $this->login();
+
+ if (!$this->queryCapability('ACL')) {
+ throw new Horde_Imap_Client_Exception_NoSupportExtension('ACL');
+ }
+
+ return $this->_getMyACLRights(Horde_Imap_Client_Mailbox::get($mailbox));
+ }
+
+ /**
+ * Get the ACL rights for the current user for a given mailbox.
+ *
+ * @param Horde_Imap_Client_Mailbox $mailbox A mailbox.
+ *
+ * @return Horde_Imap_Client_Data_Acl An ACL data object.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ abstract protected function _getMyACLRights(Horde_Imap_Client_Mailbox $mailbox);
+
+ /**
+ * Return master list of ACL rights available on the server.
+ *
+ * @return array A list of ACL rights.
+ */
+ public function allAclRights()
+ {
+ $this->login();
+
+ $rights = array(
+ Horde_Imap_Client::ACL_LOOKUP,
+ Horde_Imap_Client::ACL_READ,
+ Horde_Imap_Client::ACL_SEEN,
+ Horde_Imap_Client::ACL_WRITE,
+ Horde_Imap_Client::ACL_INSERT,
+ Horde_Imap_Client::ACL_POST,
+ Horde_Imap_Client::ACL_ADMINISTER
+ );
+
+ if ($capability = $this->queryCapability('RIGHTS')) {
+ // Add rights defined in CAPABILITY string (RFC 4314).
+ return array_merge($rights, str_split(reset($capability)));
+ }
+
+ // Add RFC 2086 rights (deprecated by RFC 4314, but need to keep for
+ // compatibility with old servers).
+ return array_merge($rights, array(
+ Horde_Imap_Client::ACL_CREATE,
+ Horde_Imap_Client::ACL_DELETE
+ ));
+ }
+
+ /**
+ * Get metadata for a given mailbox. The server must support either the
+ * IMAP METADATA extension (RFC 5464) or the ANNOTATEMORE extension
+ * (http://ietfreport.isoc.org/idref/draft-daboo-imap-annotatemore/).
+ *
+ * @param mixed $mailbox A mailbox. Either a Horde_Imap_Client_Mailbox
+ * object or a string (UTF-8).
+ * @param array $entries The entries to fetch (UTF-8 strings).
+ * @param array $options Additional options:
+ * - depth: (string) Either "0", "1" or "infinity". Returns only the
+ * given value (0), only values one level below the specified
+ * value (1) or all entries below the specified value
+ * (infinity).
+ * - maxsize: (integer) The maximal size the returned values may have.
+ * DEFAULT: No maximal size.
+ *
+ * @return array An array with metadata names as the keys and metadata
+ * values as the values. If 'maxsize' is set, and entries
+ * exist on the server larger than this size, the size will
+ * be returned in the key '*longentries'.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ public function getMetadata($mailbox, $entries, array $options = array())
+ {
+ $this->login();
+
+ if (!is_array($entries)) {
+ $entries = array($entries);
+ }
+
+ return $this->_getMetadata(Horde_Imap_Client_Mailbox::get($mailbox), array_map(array('Horde_Imap_Client_Utf7imap', 'Utf8ToUtf7Imap'), $entries), $options);
+ }
+
+ /**
+ * Get metadata for a given mailbox.
+ *
+ * @param Horde_Imap_Client_Mailbox $mailbox A mailbox.
+ * @param array $entries The entries to fetch
+ * (UTF7-IMAP strings).
+ * @param array $options Additional options.
+ *
+ * @return array An array with metadata names as the keys and metadata
+ * values as the values.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ abstract protected function _getMetadata(Horde_Imap_Client_Mailbox $mailbox,
+ $entries, $options);
+
+ /**
+ * Set metadata for a given mailbox/identifier.
+ *
+ * @param mixed $mailbox A mailbox. Either a Horde_Imap_Client_Mailbox
+ * object or a string (UTF-8). If empty, sets a
+ * server annotation.
+ * @param array $data A set of data values. The metadata values
+ * corresponding to the keys of the array will
+ * be set to the values in the array.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ public function setMetadata($mailbox, $data)
+ {
+ $this->login();
+ $this->_setMetadata(Horde_Imap_Client_Mailbox::get($mailbox), $data);
+ }
+
+ /**
+ * Set metadata for a given mailbox/identifier.
+ *
+ * @param Horde_Imap_Client_Mailbox $mailbox A mailbox.
+ * @param array $data A set of data values. See
+ * setMetadata() for format.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ abstract protected function _setMetadata(Horde_Imap_Client_Mailbox $mailbox,
+ $data);
+
+ /* Public utility functions. */
+
+ /**
+ * Returns a unique identifier for the current mailbox status.
+ *
+ * @deprecated
+ *
+ * @param mixed $mailbox A mailbox. Either a Horde_Imap_Client_Mailbox
+ * object or a string (UTF-8).
+ * @param array $addl Additional cache info to add to the cache ID
+ * string.
+ *
+ * @return string The cache ID string, which will change when the
+ * composition of the mailbox changes. The uidvalidity
+ * will always be the first element, and will be delimited
+ * by the '|' character.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ public function getCacheId($mailbox, array $addl = array())
+ {
+ return Horde_Imap_Client_Base_Deprecated::getCacheId($this, $mailbox, isset($this->_temp['enabled']['CONDSTORE']), $addl);
+ }
+
+ /**
+ * Parses a cacheID created by getCacheId().
+ *
+ * @deprecated
+ *
+ * @param string $id The cache ID.
+ *
+ * @return array An array with the following information:
+ * - highestmodseq: (integer)
+ * - messages: (integer)
+ * - uidnext: (integer)
+ * - uidvalidity: (integer) Always present
+ */
+ public function parseCacheId($id)
+ {
+ return Horde_Imap_Client_Base_Deprecated::parseCacheId($id);
+ }
+
+ /**
+ * Resolves an IDs object into a list of IDs.
+ *
+ * @param Horde_Imap_Client_Mailbox $mailbox The mailbox.
+ * @param Horde_Imap_Client_Ids $ids The Ids object.
+ * @param boolean $convert Convert to UIDs?
+ * - 0: No
+ * - 1: Only if $ids is not already a UIDs object
+ * - 2: Always
+ *
+ * @return Horde_Imap_Client_Ids The list of IDs.
+ */
+ public function resolveIds(Horde_Imap_Client_Mailbox $mailbox,
+ Horde_Imap_Client_Ids $ids, $convert = 0)
+ {
+ $map = $this->_mailboxOb($mailbox)->map;
+
+ if ($ids->special) {
+ /* Optimization for ALL sequence searches. */
+ if (!$convert && $ids->all && $ids->sequence) {
+ $res = $this->status($mailbox, Horde_Imap_Client::STATUS_MESSAGES);
+ return $this->getIdsOb($res['messages'] ? ('1:' . $res['messages']) : array(), true);
+ }
+
+ $convert = 2;
+ } elseif (!$convert || (!$ids->sequence && ($convert == 1))) {
+ return clone $ids;
+ } else {
+ /* Do an all or nothing: either we have all the numbers/UIDs in
+ * memory and can return, or just send the whole ID query to the
+ * server. Any advantage we would get by a partial search are
+ * outweighed by the complexities needed to make the search and
+ * then merge back into the original results. */
+ $lookup = $map->lookup($ids);
+ if (count($lookup) == count($ids)) {
+ return $this->getIdsOb(array_values($lookup));
+ }
+ }
+
+ $query = new Horde_Imap_Client_Search_Query();
+ $query->ids($ids);
+
+ $res = $this->search($mailbox, $query, array(
+ 'results' => array(
+ Horde_Imap_Client::SEARCH_RESULTS_MATCH,
+ Horde_Imap_Client::SEARCH_RESULTS_SAVE
+ ),
+ 'sequence' => (!$convert && $ids->sequence),
+ 'sort' => array(Horde_Imap_Client::SORT_SEQUENCE)
+ ));
+
+ /* Update mapping. */
+ if ($convert) {
+ if ($ids->all) {
+ $ids = $this->getIdsOb('1:' . count($res['match']));
+ } elseif ($ids->special) {
+ return $res['match'];
+ }
+
+ $map->update(array_combine($ids->ids, $res['match']->ids));
+ }
+
+ return $res['match'];
+ }
+
+ /**
+ * Determines if the given charset is valid for search-related queries.
+ * This check pertains just to the basic IMAP SEARCH command.
+ *
+ * @param string $charset The query charset.
+ *
+ * @return boolean True if server supports this charset.
+ */
+ public function validSearchCharset($charset)
+ {
+ $charset = strtoupper($charset);
+
+ if ($charset == 'US-ASCII') {
+ return true;
+ }
+
+ if (!isset($this->_init['s_charset'][$charset])) {
+ $s_charset = $this->_init['s_charset'];
+
+ /* Use a dummy search query and search for BADCHARSET response. */
+ $query = new Horde_Imap_Client_Search_Query();
+ $query->charset($charset, false);
+ $query->ids($this->getIdsOb(1, true));
+ $query->text('a');
+ try {
+ $this->search('INBOX', $query, array(
+ 'nocache' => true,
+ 'sequence' => true
+ ));
+ $s_charset[$charset] = true;
+ } catch (Horde_Imap_Client_Exception $e) {
+ $s_charset[$charset] = ($e->getCode() != Horde_Imap_Client_Exception::BADCHARSET);
+ }
+
+ $this->_setInit('s_charset', $s_charset);
+ }
+
+ return $this->_init['s_charset'][$charset];
+ }
+
+ /* Mailbox syncing functions. */
+
+ /**
+ * Returns a unique token for the current mailbox synchronization status.
+ *
+ * @since 2.2.0
+ *
+ * @param mixed $mailbox A mailbox. Either a Horde_Imap_Client_Mailbox
+ * object or a string (UTF-8).
+ *
+ * @return string The sync token.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ public function getSyncToken($mailbox)
+ {
+ $out = array();
+
+ foreach ($this->_syncStatus($mailbox) as $key => $val) {
+ $out[] = $key . $val;
+ }
+
+ return base64_encode(implode(',', $out));
+ }
+
+ /**
+ * Synchronize a mailbox from a sync token.
+ *
+ * @since 2.2.0
+ *
+ * @param mixed $mailbox A mailbox. Either a Horde_Imap_Client_Mailbox
+ * object or a string (UTF-8).
+ * @param string $token A sync token generated by getSyncToken().
+ * @param array $opts Additional options:
+ * - criteria: (integer) Mask of Horde_Imap_Client::SYNC_* criteria to
+ * return. Defaults to SYNC_ALL.
+ * - ids: (Horde_Imap_Client_Ids) A cached list of UIDs. Unless QRESYNC
+ * is available on the server, failure to specify this option
+ * means SYNC_VANISHEDUIDS information cannot be returned.
+ *
+ * @return Horde_Imap_Client_Data_Sync A sync object.
+ *
+ * @throws Horde_Imap_Client_Exception
+ * @throws Horde_Imap_Client_Exception_Sync
+ */
+ public function sync($mailbox, $token, array $opts = array())
+ {
+ if (($token = base64_decode($token, true)) === false) {
+ throw new Horde_Imap_Client_Exception_Sync('Bad token.', Horde_Imap_Client_Exception_Sync::BAD_TOKEN);
+ }
+
+ $sync = array();
+ foreach (explode(',', $token) as $val) {
+ $sync[substr($val, 0, 1)] = substr($val, 1);
+ }
+
+ return new Horde_Imap_Client_Data_Sync(
+ $this,
+ $mailbox,
+ $sync,
+ $this->_syncStatus($mailbox),
+ (isset($opts['criteria']) ? $opts['criteria'] : Horde_Imap_Client::SYNC_ALL),
+ (isset($opts['ids']) ? $opts['ids'] : null)
+ );
+ }
+
+ /* Private utility functions. */
+
+ /**
+ * Store FETCH data in cache.
+ *
+ * @param Horde_Imap_Client_Fetch_Results $data The fetch results.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ protected function _updateCache(Horde_Imap_Client_Fetch_Results $data)
+ {
+ if (!empty($this->_temp['fetch_nocache']) ||
+ empty($this->_selected) ||
+ !count($data) ||
+ !$this->_initCache(true)) {
+ return;
+ }
+
+ $c = $this->getParam('cache');
+ if (in_array(strval($this->_selected), $c['fetch_ignore'])) {
+ $this->_debug->info(sprintf("CACHE: Ignoring FETCH data (mailbox: %s)", $this->_selected));
+ return;
+ }
+
+ /* Optimization: we can directly use getStatus() here since we know
+ * these values are initialized. */
+ $mbox_ob = $this->_mailboxOb();
+ $highestmodseq = $mbox_ob->getStatus(Horde_Imap_Client::STATUS_HIGHESTMODSEQ);
+ $uidvalidity = $mbox_ob->getStatus(Horde_Imap_Client::STATUS_UIDVALIDITY);
+
+ $mapping = $modseq = $tocache = array();
+ if (count($data)) {
+ $cf = $this->_cacheFields();
+ }
+
+ foreach ($data as $v) {
+ /* It is possible that we received FETCH information that doesn't
+ * contain UID data. This is uncacheable so don't process. */
+ if (!($uid = $v->getUid())) {
+ return;
+ }
+
+ $tmp = array();
+
+ if ($v->isDowngraded()) {
+ $tmp[self::CACHE_DOWNGRADED] = true;
+ }
+
+ foreach ($cf as $key => $val) {
+ if ($v->exists($key)) {
+ switch ($key) {
+ case Horde_Imap_Client::FETCH_ENVELOPE:
+ $tmp[$val] = $v->getEnvelope();
+ break;
+
+ case Horde_Imap_Client::FETCH_FLAGS:
+ if ($highestmodseq) {
+ $modseq[$uid] = $v->getModSeq();
+ $tmp[$val] = $v->getFlags();
+ }
+ break;
+
+ case Horde_Imap_Client::FETCH_HEADERS:
+ foreach ($this->_temp['headers_caching'] as $label => $hash) {
+ if ($hdr = $v->getHeaders($label)) {
+ $tmp[$val][$hash] = $hdr;
+ }
+ }
+ break;
+
+ case Horde_Imap_Client::FETCH_IMAPDATE:
+ $tmp[$val] = $v->getImapDate();
+ break;
+
+ case Horde_Imap_Client::FETCH_SIZE:
+ $tmp[$val] = $v->getSize();
+ break;
+
+ case Horde_Imap_Client::FETCH_STRUCTURE:
+ $tmp[$val] = clone $v->getStructure();
+ break;
+ }
+ }
+ }
+
+ if (!empty($tmp)) {
+ $tocache[$uid] = $tmp;
+ }
+
+ $mapping[$v->getSeq()] = $uid;
+ }
+
+ if (!empty($mapping)) {
+ if (!empty($tocache)) {
+ $this->_cache->set($this->_selected, $tocache, $uidvalidity);
+ }
+
+ $this->_mailboxOb()->map->update($mapping);
+ }
+
+ if (!empty($modseq)) {
+ $this->_updateModSeq(max(array_merge($modseq, array($highestmodseq))));
+ $mbox_ob->setStatus(Horde_Imap_Client::STATUS_SYNCFLAGUIDS, array_keys($modseq));
+ }
+ }
+
+ /**
+ * Moves cache entries from the current mailbox to another mailbox.
+ *
+ * @param Horde_Imap_Client_Mailbox $to The destination mailbox.
+ * @param array $map Mapping of source UIDs (keys) to
+ * destination UIDs (values).
+ * @param string $uidvalid UIDVALIDITY of destination
+ * mailbox.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ protected function _moveCache(Horde_Imap_Client_Mailbox $to, $map,
+ $uidvalid)
+ {
+ if (!$this->_initCache()) {
+ return;
+ }
+
+ $c = $this->getParam('cache');
+ if (in_array(strval($to), $c['fetch_ignore'])) {
+ $this->_debug->info(sprintf("CACHE: Ignoring moving FETCH data (%s => %s)", $this->_selected, $to));
+ return;
+ }
+
+ $old = $this->_cache->get($this->_selected, array_keys($map), null);
+ $new = array();
+
+ foreach ($map as $key => $val) {
+ if (!empty($old[$key])) {
+ $new[$val] = $old[$key];
+ }
+ }
+
+ if (!empty($new)) {
+ $this->_cache->set($to, $new, $uidvalid);
+ }
+ }
+
+ /**
+ * Delete messages in the cache.
+ *
+ * @param Horde_Imap_Client_Mailbox $mailbox The mailbox.
+ * @param Horde_Imap_Client_Ids $ids The list of IDs to delete in
+ * $mailbox.
+ * @param array $opts Additional options (not used
+ * in base class).
+ *
+ * @return Horde_Imap_Client_Ids UIDs that were deleted.
+ * @throws Horde_Imap_Client_Exception
+ */
+ protected function _deleteMsgs(Horde_Imap_Client_Mailbox $mailbox,
+ Horde_Imap_Client_Ids $ids,
+ array $opts = array())
+ {
+ if (!$this->_initCache()) {
+ return $ids;
+ }
+
+ $mbox_ob = $this->_mailboxOb();
+ $ids_ob = $ids->sequence
+ ? $this->getIdsOb($mbox_ob->map->lookup($ids))
+ : $ids;
+
+ $this->_cache->deleteMsgs($mailbox, $ids_ob->ids);
+ $mbox_ob->setStatus(Horde_Imap_Client::STATUS_SYNCVANISHED, $ids_ob->ids);
+ $mbox_ob->map->remove($ids);
+
+ return $ids_ob;
+ }
+
+ /**
+ * Retrieve data from the search cache.
+ *
+ * @param string $type The cache type ('search' or 'thread').
+ * @param array $options The options array of the calling function.
+ *
+ * @return mixed Returns search cache metadata. If search was retrieved,
+ * data is in key 'data'.
+ * Returns null if caching is not available.
+ */
+ protected function _getSearchCache($type, $options)
+ {
+ $status = $this->status($this->_selected, Horde_Imap_Client::STATUS_HIGHESTMODSEQ | Horde_Imap_Client::STATUS_UIDVALIDITY);
+
+ /* Search caching requires MODSEQ, which may not be active for a
+ * mailbox. */
+ if (empty($status['highestmodseq'])) {
+ return null;
+ }
+
+ ksort($options);
+ $cache = hash('md5', $type . serialize($options));
+ $cacheid = $this->getCacheId($this->_selected);
+ $ret = array();
+
+ $md = $this->_cache->getMetaData(
+ $this->_selected,
+ $status['uidvalidity'],
+ array(self::CACHE_SEARCH, self::CACHE_SEARCHID)
+ );
+
+ if (!isset($md[self::CACHE_SEARCHID]) ||
+ ($md[self::CACHE_SEARCHID] != $cacheid)) {
+ $md[self::CACHE_SEARCH] = array();
+ $md[self::CACHE_SEARCHID] = $cacheid;
+ if ($this->_debug->debug &&
+ !isset($this->_temp['searchcacheexpire'][strval($this->_selected)])) {
+ $this->_debug->info(sprintf("SEARCH: Expired from cache (mailbox: %s)", $this->_selected));
+ $this->_temp['searchcacheexpire'][strval($this->_selected)] = true;
+ }
+ } elseif (isset($md[self::CACHE_SEARCH][$cache])) {
+ $this->_debug->info(sprintf("SEARCH: Retrieved %s from cache (mailbox: %s; id: %s)", $type, $this->_selected, $cache));
+ $ret['data'] = $md[self::CACHE_SEARCH][$cache];
+ unset($md[self::CACHE_SEARCHID]);
+ }
+
+ return array_merge($ret, array(
+ 'id' => $cache,
+ 'metadata' => $md,
+ 'type' => $type
+ ));
+ }
+
+ /**
+ * Set data in the search cache.
+ *
+ * @param mixed $data The cache data to store.
+ * @param string $sdata The search data returned from _getSearchCache().
+ */
+ protected function _setSearchCache($data, $sdata)
+ {
+ $sdata['metadata'][self::CACHE_SEARCH][$sdata['id']] = $data;
+
+ $this->_cache->setMetaData($this->_selected, null, $sdata['metadata']);
+
+ if ($this->_debug->debug) {
+ $this->_debug->info(sprintf("SEARCH: Saved %s to cache (mailbox: %s; id: %s)", $sdata['type'], $this->_selected, $sdata['id']));
+ unset($this->_temp['searchcacheexpire'][strval($this->_selected)]);
+ }
+ }
+
+ /**
+ * Updates the cached MODSEQ value.
+ *
+ * @param integer $modseq MODSEQ value to store.
+ *
+ * @return mixed The MODSEQ of the old value if it was replaced (or false
+ * if it didn't exist or is the same).
+ */
+ protected function _updateModSeq($modseq)
+ {
+ if (!$this->_initCache(true)) {
+ return false;
+ }
+
+ $mbox_ob = $this->_mailboxOb();
+ $uidvalid = $mbox_ob->getStatus(Horde_Imap_Client::STATUS_UIDVALIDITY);
+ $md = $this->_cache->getMetaData($this->_selected, $uidvalid, array(self::CACHE_MODSEQ));
+
+ if (isset($md[self::CACHE_MODSEQ])) {
+ if ($md[self::CACHE_MODSEQ] < $modseq) {
+ $set = true;
+ $sync = $md[self::CACHE_MODSEQ];
+ } else {
+ $set = false;
+ $sync = 0;
+ }
+ $mbox_ob->setStatus(Horde_Imap_Client::STATUS_SYNCMODSEQ, $md[self::CACHE_MODSEQ]);
+ } else {
+ $set = true;
+ $sync = 0;
+ }
+
+ if ($set) {
+ $this->_cache->setMetaData($this->_selected, $uidvalid, array(
+ self::CACHE_MODSEQ => $modseq
+ ));
+ }
+
+ return $sync;
+ }
+
+ /**
+ * Synchronizes the current mailbox cache with the server (using CONDSTORE
+ * or QRESYNC).
+ */
+ protected function _condstoreSync()
+ {
+ $mbox_ob = $this->_mailboxOb();
+
+ /* Check that modseqs are available in mailbox. */
+ if (!($highestmodseq = $mbox_ob->getStatus(Horde_Imap_Client::STATUS_HIGHESTMODSEQ)) ||
+ !($modseq = $this->_updateModSeq($highestmodseq))) {
+ $mbox_ob->sync = true;
+ }
+
+ if ($mbox_ob->sync) {
+ return;
+ }
+
+ $uids_ob = $this->getIdsOb($this->_cache->get($this->_selected, array(), array(), $mbox_ob->getStatus(Horde_Imap_Client::STATUS_UIDVALIDITY)));
+
+ /* Are we caching flags? */
+ if (array_key_exists(Horde_Imap_Client::FETCH_FLAGS, $this->_cacheFields())) {
+ $fquery = new Horde_Imap_Client_Fetch_Query();
+ $fquery->flags();
+
+ /* Update flags in cache. Cache will be updated in _fetch(). */
+ $this->_fetch(new Horde_Imap_Client_Fetch_Results(), array(
+ array(
+ '_query' => $fquery,
+ 'changedsince' => $modseq,
+ 'ids' => $uids_ob
+ )
+ ));
+ }
+
+ /* Search for deleted messages, and remove from cache. */
+ $vanished = $this->vanished($this->_selected, $modseq, $uids_ob);
+ $disappear = array_diff($uids_ob->ids, $vanished->ids);
+ if (!empty($disappear)) {
+ $this->_deleteMsgs($this->_selected, $this->getIdsOb($disappear));
+ }
+
+ $mbox_ob->sync = true;
+ }
+
+ /**
+ * Provide the list of available caching fields.
+ *
+ * @return array The list of available caching fields (fields are in the
+ * key).
+ */
+ protected function _cacheFields()
+ {
+ $c = $this->getParam('cache');
+ $out = $c['fields'];
+
+ if (!isset($this->_temp['enabled']['CONDSTORE'])) {
+ unset($out[Horde_Imap_Client::FETCH_FLAGS]);
+ }
+
+ return $out;
+ }
+
+ /**
+ * Return the current mailbox synchronization status.
+ *
+ * @param mixed $mailbox A mailbox. Either a Horde_Imap_Client_Mailbox
+ * object or a string (UTF-8).
+ *
+ * @return array An array with status data. (This data is not guaranteed
+ * to have any specific format).
+ */
+ protected function _syncStatus($mailbox)
+ {
+ $status = $this->status(
+ $mailbox,
+ Horde_Imap_Client::STATUS_HIGHESTMODSEQ |
+ Horde_Imap_Client::STATUS_MESSAGES |
+ Horde_Imap_Client::STATUS_UIDNEXT_FORCE |
+ Horde_Imap_Client::STATUS_UIDVALIDITY
+ );
+
+ $fields = array('uidnext', 'uidvalidity');
+ if (empty($status['highestmodseq'])) {
+ $fields[] = 'messages';
+ } else {
+ $fields[] = 'highestmodseq';
+ }
+
+ $out = array();
+ $sync_map = array_flip(Horde_Imap_Client_Data_Sync::$map);
+
+ foreach ($fields as $val) {
+ $out[$sync_map[$val]] = $status[$val];
+ }
+
+ return array_filter($out);
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientCacheBackendCachephp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Cache/Backend/Cache.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Cache/Backend/Cache.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Cache/Backend/Cache.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,498 @@
</span><ins>+<?php
+/**
+ * Copyright 2005-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2005-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * A Horde_Cache implementation for caching IMAP/POP data.
+ * Requires the Horde_Cache package.
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2005-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+class Horde_Imap_Client_Cache_Backend_Cache extends Horde_Imap_Client_Cache_Backend
+{
+ /** Cache structure version. */
+ const VERSION = 3;
+
+ /**
+ * The cache object.
+ *
+ * @var Horde_Cache
+ */
+ protected $_cache;
+
+ /**
+ * The working data for the current pageload. All changes take place to
+ * this data.
+ *
+ * @var array
+ */
+ protected $_data = array();
+
+ /**
+ * The list of cache slices loaded.
+ *
+ * @var array
+ */
+ protected $_loaded = array();
+
+ /**
+ * The mapping of UIDs to slices.
+ *
+ * @var array
+ */
+ protected $_slicemap = array();
+
+ /**
+ * The list of items to update:
+ * - add: (array) List of IDs that were added.
+ * - slice: (array) List of slices that were modified.
+ * - slicemap: (boolean) Was slicemap info changed?
+ *
+ * @var array
+ */
+ protected $_update = array();
+
+ /**
+ * Constructor.
+ *
+ * @param array $params Configuration parameters:
+ * <ul>
+ * <li>
+ * REQUIRED Parameters:
+ * <ul>
+ * <li>
+ * cacheob: (Horde_Cache) [REQUIRED] The cache object to use.
+ * </li>
+ * </ul>
+ * </li>
+ * <li>
+ * Optional Parameters:
+ * <ul>
+ * <li>
+ * lifetime: (integer) The lifetime of the cache data (in seconds).
+ * DEFAULT: 1 week (604800 seconds)
+ * </li>
+ * <li>
+ * slicesize: (integer) The slicesize to use.
+ * DEFAULT: 50
+ * </li>
+ * </ul>
+ * </li>
+ * </ul>
+ */
+ public function __construct(array $params = array())
+ {
+ // Default parameters.
+ $params = array_merge(array(
+ 'lifetime' => 604800,
+ 'slicesize' => 50
+ ), array_filter($params));
+
+ if (!isset($params['cacheob'])) {
+ throw new InvalidArgumentException('Missing cacheob parameter.');
+ }
+
+ foreach (array('lifetime', 'slicesize') as $val) {
+ $params[$val] = intval($params[$val]);
+ }
+
+ parent::__construct($params);
+ }
+
+ /**
+ * Initialization tasks.
+ */
+ protected function _initOb()
+ {
+ $this->_cache = $this->_params['cacheob'];
+ register_shutdown_function(array($this, 'save'));
+ }
+
+ /**
+ * Updates the cache.
+ */
+ public function save()
+ {
+ $lifetime = $this->_params['lifetime'];
+
+ foreach ($this->_update as $mbox => $val) {
+ $s = &$this->_slicemap[$mbox];
+
+ if (!empty($val['add'])) {
+ if ($s['c'] <= $this->_params['slicesize']) {
+ $val['slice'][] = $s['i'];
+ $this->_loadSlice($mbox, $s['i']);
+ }
+ $val['slicemap'] = true;
+
+ foreach (array_keys(array_flip($val['add'])) as $uid) {
+ if ($s['c']++ > $this->_params['slicesize']) {
+ $s['c'] = 0;
+ $val['slice'][] = ++$s['i'];
+ $this->_loadSlice($mbox, $s['i']);
+ }
+ $s['s'][$uid] = $s['i'];
+ }
+ }
+
+ if (!empty($val['slice'])) {
+ $d = &$this->_data[$mbox];
+ $val['slicemap'] = true;
+
+ foreach (array_keys(array_flip($val['slice'])) as $slice) {
+ $data = array();
+ foreach (array_keys($s['s'], $slice) as $uid) {
+ $data[$uid] = is_array($d[$uid])
+ ? serialize($d[$uid])
+ : $d[$uid];
+ }
+ $this->_cache->set($this->_getCid($mbox, $slice), serialize($data), $lifetime);
+ }
+ }
+
+ if (!empty($val['slicemap'])) {
+ $this->_cache->set($this->_getCid($mbox, 'slicemap'), serialize($s), $lifetime);
+ }
+ }
+
+ $this->_update = array();
+ }
+
+ /**
+ */
+ public function get($mailbox, $uids, $fields, $uidvalid)
+ {
+ $ret = array();
+ $this->_loadUids($mailbox, $uids, $uidvalid);
+
+ if (empty($this->_data[$mailbox])) {
+ return $ret;
+ }
+
+ if (!empty($fields)) {
+ $fields = array_flip($fields);
+ }
+ $ptr = &$this->_data[$mailbox];
+
+ foreach (array_intersect($uids, array_keys($ptr)) as $val) {
+ if (is_string($ptr[$val])) {
+ $ptr[$val] = @unserialize($ptr[$val]);
+ }
+
+ $ret[$val] = (empty($fields) || empty($ptr[$val]))
+ ? $ptr[$val]
+ : array_intersect_key($ptr[$val], $fields);
+ }
+
+ return $ret;
+ }
+
+ /**
+ */
+ public function getCachedUids($mailbox, $uidvalid)
+ {
+ $this->_loadSliceMap($mailbox, $uidvalid);
+ return array_unique(array_merge(
+ array_keys($this->_slicemap[$mailbox]['s']),
+ (isset($this->_update[$mailbox]) ? $this->_update[$mailbox]['add'] : array())
+ ));
+ }
+
+ /**
+ */
+ public function set($mailbox, $data, $uidvalid)
+ {
+ $update = array_keys($data);
+
+ try {
+ $this->_loadUids($mailbox, $update, $uidvalid);
+ } catch (Horde_Imap_Client_Exception $e) {
+ // Ignore invalidity - just start building the new cache
+ }
+
+ $d = &$this->_data[$mailbox];
+ $s = &$this->_slicemap[$mailbox]['s'];
+ $add = $updated = array();
+
+ foreach ($data as $k => $v) {
+ if (isset($d[$k])) {
+ if (is_string($d[$k])) {
+ $d[$k] = @unserialize($d[$k]);
+ }
+ $d[$k] = is_array($d[$k])
+ ? array_merge($d[$k], $v)
+ : $v;
+ if (isset($s[$k])) {
+ $updated[$s[$k]] = true;
+ }
+ } else {
+ $d[$k] = $v;
+ $add[] = $k;
+ }
+ }
+
+ $this->_toUpdate($mailbox, 'add', $add);
+ $this->_toUpdate($mailbox, 'slice', array_keys($updated));
+ }
+
+ /**
+ */
+ public function getMetaData($mailbox, $uidvalid, $entries)
+ {
+ $this->_loadSliceMap($mailbox, $uidvalid);
+
+ return empty($entries)
+ ? $this->_slicemap[$mailbox]['d']
+ : array_intersect_key($this->_slicemap[$mailbox]['d'], array_flip($entries));
+ }
+
+ /**
+ */
+ public function setMetaData($mailbox, $data)
+ {
+ $this->_loadSliceMap($mailbox, isset($data['uidvalid']) ? $data['uidvalid'] : null);
+ $this->_slicemap[$mailbox]['d'] = array_merge($this->_slicemap[$mailbox]['d'], $data);
+ $this->_toUpdate($mailbox, 'slicemap', true);
+ }
+
+ /**
+ */
+ public function deleteMsgs($mailbox, $uids)
+ {
+ $slicemap = &$this->_slicemap[$mailbox];
+ $deleted = array_intersect_key($slicemap['s'], array_flip($uids));
+
+ if (isset($this->_update[$mailbox])) {
+ $this->_update[$mailbox]['add'] = array_diff(
+ $this->_update[$mailbox]['add'],
+ $uids
+ );
+ }
+
+ if (empty($deleted)) {
+ return;
+ }
+
+ $this->_loadUids($mailbox, array_keys($deleted));
+ $d = &$this->_data[$mailbox];
+
+ foreach (array_keys($deleted) as $id) {
+ unset($d[$id], $slicemap['s'][$id]);
+ }
+
+ foreach (array_unique($deleted) as $slice) {
+ /* Get rid of slice if less than 10% of capacity. */
+ if (($slice != $slicemap['i']) &&
+ ($slice_uids = array_keys($slicemap['s'], $slice)) &&
+ ($this->_params['slicesize'] * 0.1) > count($slice_uids)) {
+ $this->_toUpdate($mailbox, 'add', $slice_uids);
+ $this->_cache->expire($this->_getCid($mailbox, $slice));
+ foreach ($slice_uids as $val) {
+ unset($slicemap['s'][$val]);
+ }
+ } else {
+ $this->_toUpdate($mailbox, 'slice', array($slice));
+ }
+ }
+ }
+
+ /**
+ */
+ public function deleteMailbox($mailbox)
+ {
+ $this->_loadSliceMap($mailbox);
+ $this->_deleteMailbox($mailbox);
+ }
+
+ /**
+ */
+ public function clear($lifetime)
+ {
+ $this->_cache->clear();
+ $this->_data = $this->_loaded = $this->_slicemap = $this->_update = array();
+ }
+
+ /**
+ * Create the unique ID used to store the data in the cache.
+ *
+ * @param string $mailbox The mailbox to cache.
+ * @param string $slice The cache slice.
+ *
+ * @return string The cache ID.
+ */
+ protected function _getCid($mailbox, $slice)
+ {
+ return implode('|', array(
+ 'horde_imap_client',
+ $this->_params['username'],
+ $mailbox,
+ $this->_params['hostspec'],
+ $this->_params['port'],
+ $slice,
+ self::VERSION
+ ));
+ }
+
+ /**
+ * Delete a mailbox from the cache.
+ *
+ * @param string $mbox The mailbox to delete.
+ */
+ protected function _deleteMailbox($mbox)
+ {
+ foreach (array_merge(array_keys(array_flip($this->_slicemap[$mbox]['s'])), array('slicemap')) as $slice) {
+ $cid = $this->_getCid($mbox, $slice);
+ $this->_cache->expire($cid);
+ unset($this->_loaded[$cid]);
+ }
+
+ unset(
+ $this->_data[$mbox],
+ $this->_slicemap[$mbox],
+ $this->_update[$mbox]
+ );
+ }
+
+ /**
+ * Load UIDs by regenerating from the cache.
+ *
+ * @param string $mailbox The mailbox to load.
+ * @param array $uids The UIDs to load.
+ * @param integer $uidvalid The IMAP uidvalidity value of the mailbox.
+ */
+ protected function _loadUids($mailbox, $uids, $uidvalid = null)
+ {
+ if (!isset($this->_data[$mailbox])) {
+ $this->_data[$mailbox] = array();
+ }
+
+ $this->_loadSliceMap($mailbox, $uidvalid);
+
+ if (!empty($uids)) {
+ foreach (array_unique(array_intersect_key($this->_slicemap[$mailbox]['s'], array_flip($uids))) as $slice) {
+ $this->_loadSlice($mailbox, $slice);
+ }
+ }
+ }
+
+ /**
+ * Load UIDs from a cache slice.
+ *
+ * @param string $mailbox The mailbox to load.
+ * @param integer $slice The slice to load.
+ */
+ protected function _loadSlice($mailbox, $slice)
+ {
+ $cache_id = $this->_getCid($mailbox, $slice);
+
+ if (!empty($this->_loaded[$cache_id])) {
+ return;
+ }
+
+ if ((($data = $this->_cache->get($cache_id, 0)) !== false) &&
+ ($data = @unserialize($data)) &&
+ is_array($data)) {
+ $this->_data[$mailbox] += $data;
+ $this->_loaded[$cache_id] = true;
+ } else {
+ $ptr = &$this->_slicemap[$mailbox];
+
+ // Slice data is corrupt; remove from slicemap.
+ foreach (array_keys($ptr['s'], $slice) as $val) {
+ unset($ptr['s'][$val]);
+ }
+
+ if ($slice == $ptr['i']) {
+ $ptr['c'] = 0;
+ }
+ }
+ }
+
+ /**
+ * Load the slicemap for a given mailbox. The slicemap contains
+ * the uidvalidity information, the UIDs->slice lookup table, and any
+ * metadata that needs to be saved for the mailbox.
+ *
+ * @param string $mailbox The mailbox.
+ * @param integer $uidvalid The IMAP uidvalidity value of the mailbox.
+ */
+ protected function _loadSliceMap($mailbox, $uidvalid = null)
+ {
+ if (!isset($this->_slicemap[$mailbox]) &&
+ (($data = $this->_cache->get($this->_getCid($mailbox, 'slicemap'), 0)) !== false) &&
+ ($slice = @unserialize($data)) &&
+ is_array($slice)) {
+ $this->_slicemap[$mailbox] = $slice;
+ }
+
+ if (isset($this->_slicemap[$mailbox])) {
+ $ptr = &$this->_slicemap[$mailbox];
+ if (is_null($ptr['d']['uidvalid'])) {
+ $ptr['d']['uidvalid'] = $uidvalid;
+ return;
+ } elseif (!is_null($uidvalid) &&
+ ($ptr['d']['uidvalid'] != $uidvalid)) {
+ $this->_deleteMailbox($mailbox);
+ } else {
+ return;
+ }
+ }
+
+ $this->_slicemap[$mailbox] = array(
+ // Tracking count for purposes of determining slices
+ 'c' => 0,
+ // Metadata storage
+ // By default includes UIDVALIDITY of mailbox.
+ 'd' => array('uidvalid' => $uidvalid),
+ // The ID of the last slice.
+ 'i' => 0,
+ // The slice list.
+ 's' => array()
+ );
+ }
+
+ /**
+ * Add update entry for a mailbox.
+ *
+ * @param string $mailbox The mailbox.
+ * @param string $type 'add', 'slice', or 'slicemap'.
+ * @param mixed $data The data to update.
+ */
+ protected function _toUpdate($mailbox, $type, $data)
+ {
+ if (!isset($this->_update[$mailbox])) {
+ $this->_update[$mailbox] = array(
+ 'add' => array(),
+ 'slice' => array()
+ );
+ }
+
+ $this->_update[$mailbox][$type] = ($type == 'slicemap')
+ ? $data
+ : array_merge($this->_update[$mailbox][$type], $data);
+ }
+
+ /* Serializable methods. */
+
+ /**
+ */
+ public function serialize()
+ {
+ $this->save();
+ return parent::serialize();
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientCacheBackendDbphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Cache/Backend/Db.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Cache/Backend/Db.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Cache/Backend/Db.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,407 @@
</span><ins>+<?php
+/**
+ * Copyright 2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * A SQL database implementation for caching IMAP/POP data.
+ * Requires the Horde_Db package.
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+class Horde_Imap_Client_Cache_Backend_Db extends Horde_Imap_Client_Cache_Backend
+{
+ /* Table names. */
+ const BASE_TABLE = 'horde_imap_client_data';
+ const MD_TABLE = 'horde_imap_client_metadata';
+ const MSG_TABLE = 'horde_imap_client_message';
+
+ /**
+ * Handle for the database connection.
+ *
+ * @var Horde_Db_Adapter
+ */
+ protected $_db;
+
+ /**
+ * Constructor.
+ *
+ * @param array $params Configuration parameters:
+ * <ul>
+ * <li>
+ * REQUIRED Parameters:
+ * <ul>
+ * <li>
+ * db: (Horde_Db_Adapter) DB object.
+ * </li>
+ * </ul>
+ * </li>
+ * </ul>
+ */
+ public function __construct(array $params = array())
+ {
+ if (!isset($params['db'])) {
+ throw new InvalidArgumentException('Missing db parameter.');
+ }
+
+ parent::__construct($params);
+ }
+
+ /**
+ */
+ protected function _initOb()
+ {
+ $this->_db = $this->_params['db'];
+ }
+
+ /**
+ */
+ public function get($mailbox, $uids, $fields, $uidvalid)
+ {
+ $this->getMetaData($mailbox, $uidvalid, array('uidvalid'));
+
+ $query = $this->_baseSql($mailbox, self::MSG_TABLE);
+ $query[0] = 'SELECT t.data, t.msguid ' . $query[0];
+
+ $uid_query = array();
+ foreach ($uids as $val) {
+ $uid_query[] = 't.msguid = ?';
+ $query[1][] = strval($val);
+ }
+ $query[0] .= ' AND (' . implode(' OR ', $uid_query) . ')';
+
+ $compress = new Horde_Compress_Fast();
+ $out = array();
+
+ try {
+ $columns = $this->_db->columns(self::MSG_TABLE);
+ $res = $this->_db->select($query[0], $query[1]);
+
+ while (($row = $res->fetchObject()) !== false) {
+ $out[$row->msguid] = @unserialize($compress->decompress(
+ $columns['data']->binaryToString($row->data)
+ ));
+ }
+ } catch (Horde_Db_Exception $e) {}
+
+ return $out;
+ }
+
+ /**
+ */
+ public function getCachedUids($mailbox, $uidvalid)
+ {
+ $this->getMetaData($mailbox, $uidvalid, array('uidvalid'));
+
+ $query = $this->_baseSql($mailbox, self::MSG_TABLE);
+ $query[0] = 'SELECT DISTINCT t.msguid ' . $query[0];
+
+ try {
+ return $this->_db->selectValues($query[0], $query[1]);
+ } catch (Horde_Db_Exception $e) {
+ return array();
+ }
+ }
+
+ /**
+ */
+ public function set($mailbox, $data, $uidvalid)
+ {
+ if ($uid = $this->_getUid($mailbox)) {
+ $res = $this->get($mailbox, array_keys($data), array(), $uidvalid);
+ } else {
+ $res = array();
+ $uid = $this->_createUid($mailbox);
+ }
+
+ $compress = new Horde_Compress_Fast();
+
+ foreach ($data as $key => $val) {
+ if (isset($res[$key])) {
+ try {
+ /* Update */
+ $this->_db->update(
+ sprintf('UPDATE %s SET data = ? WHERE uid = ? AND msguid = ?', self::MSG_TABLE),
+ array(
+ new Horde_Db_Value_Binary($compress->compress(serialize(array_merge($res[$key], $val)))),
+ $uid,
+ strval($key)
+ )
+ );
+ } catch (Horde_Db_Exception $e) {}
+ } else {
+ /* Insert */
+ try {
+ $this->_db->insert(
+ sprintf('INSERT INTO %s (data, msguid, uid) VALUES (?, ?, ?)', self::MSG_TABLE),
+ array(
+ new Horde_Db_Value_Binary($compress->compress(serialize($val))),
+ strval($key),
+ $uid
+ )
+ );
+ } catch (Horde_Db_Exception $e) {}
+ }
+ }
+
+ /* Update modified time. */
+ try {
+ $this->_db->update(
+ sprintf(
+ 'UPDATE %s SET modified = ? WHERE uid = ?',
+ self::BASE_TABLE
+ ),
+ array(time(), $uid)
+ );
+ } catch (Horde_Db_Exception $e) {}
+
+ /* Update uidvalidity. */
+ $this->setMetaData($mailbox, array('uidvalid' => $uidvalid));
+ }
+
+ /**
+ */
+ public function getMetaData($mailbox, $uidvalid, $entries)
+ {
+ $query = $this->_baseSql($mailbox, self::MD_TABLE);
+ $query[0] = 'SELECT t.field, t.data ' . $query[0];
+
+ if (!empty($entries)) {
+ $entries[] = 'uidvalid';
+ $entry_query = array();
+
+ foreach (array_unique($entries) as $val) {
+ $entry_query[] = 't.field = ?';
+ $query[1][] = $val;
+ }
+ $query[0] .= ' AND (' . implode(' OR ', $entry_query) . ')';
+ }
+
+ try {
+ if ($res = $this->_db->selectAssoc($query[0], $query[1])) {
+ $columns = $this->_db->columns(self::MD_TABLE);
+ foreach ($res as $key => $val) {
+ switch ($key) {
+ case 'uidvalid':
+ $res[$key] = $columns['data']->binaryToString($val);
+ break;
+
+ default:
+ $res[$key] = @unserialize(
+ $columns['data']->binaryToString($val)
+ );
+ break;
+ }
+ }
+
+ if (is_null($uidvalid) ||
+ !isset($res['uidvalid']) ||
+ ($res['uidvalid'] == $uidvalid)) {
+ return $res;
+ }
+
+ $this->deleteMailbox($mailbox);
+ }
+ } catch (Horde_Db_Exception $e) {}
+
+ return array();
+ }
+
+ /**
+ */
+ public function setMetaData($mailbox, $data)
+ {
+ if (!($uid = $this->_getUid($mailbox))) {
+ $uid = $this->_createUid($mailbox);
+ }
+
+ $query = sprintf('SELECT field FROM %s where uid = ?', self::MD_TABLE);
+ $values = array($uid);
+
+ try {
+ $fields = $this->_db->selectValues($query, $values);
+ } catch (Horde_Db_Exception $e) {
+ return;
+ }
+
+ foreach ($data as $key => $val) {
+ $val = new Horde_Db_Value_Binary(($key == 'uidvalid') ? $val : serialize($val));
+
+ if (in_array($key, $fields)) {
+ /* Update */
+ try {
+ $this->_db->update(
+ sprintf(
+ 'UPDATE %s SET data = ? WHERE field = ? AND uid = ?',
+ self::MD_TABLE
+ ),
+ array($val, $key, $uid)
+ );
+ } catch (Horde_Db_Exception $e) {}
+ } else {
+ /* Insert */
+ try {
+ $this->_db->insert(
+ sprintf(
+ 'INSERT INTO %s (data, field, uid) VALUES (?, ?, ?)',
+ self::MD_TABLE
+ ),
+ array($val, $key, $uid)
+ );
+ } catch (Horde_Db_Exception $e) {}
+ }
+ }
+ }
+
+ /**
+ */
+ public function deleteMsgs($mailbox, $uids)
+ {
+ $query = $this->_baseSql($mailbox);
+ $query[0] = sprintf(
+ 'DELETE FROM %s WHERE uid IN (SELECT uid ' . $query[0] . ')',
+ self::MSG_TABLE
+ );
+
+ $uid_query = array();
+ foreach ($uids as $val) {
+ $uid_query[] = 'msguid = ?';
+ $query[1][] = strval($val);
+ }
+ $query[0] .= ' AND (' . implode(' OR ', $uid_query) . ')';
+
+ try {
+ $this->_db->delete($query[0], $query[1]);
+ } catch (Horde_Db_Exception $e) {}
+ }
+
+ /**
+ */
+ public function deleteMailbox($mailbox)
+ {
+ if (is_null($uid = $this->_getUid($mailbox))) {
+ return;
+ }
+
+ foreach (array(self::BASE_TABLE, self::MD_TABLE, self::MSG_TABLE) as $val) {
+ try {
+ $this->_db->delete(
+ sprintf('DELETE FROM %s WHERE uid = ?', $val),
+ array($uid)
+ );
+ } catch (Horde_Db_Exception $e) {}
+ }
+ }
+
+ /**
+ */
+ public function clear($lifetime)
+ {
+ if (is_null($lifetime)) {
+ try {
+ $this->_db->delete(sprintf('DELETE FROM %s', self::BASE_TABLE));
+ $this->_db->delete(sprintf('DELETE FROM %s', self::MD_TABLE));
+ $this->_db->delete(sprintf('DELETE FROM %s', self::MSG_TABLE));
+ } catch (Horde_Db_Exception $e) {}
+ return;
+ }
+
+ $purge = time() - $lifetime;
+ $sql = 'DELETE FROM %s WHERE uid IN (SELECT uid FROM %s WHERE modified < ?';
+
+ foreach (array(self::MD_TABLE, self::MSG_TABLE) as $val) {
+ try {
+ $this->_db->delete(
+ sprintf($sql, $val, self::BASE_TABLE),
+ array($purge)
+ );
+ } catch (Horde_Db_Exception $e) {}
+ }
+
+ try {
+ $this->_db->delete(
+ sprintf('DELETE FROM %s WHERE modified < ?', self::BASE_TABLE),
+ array($purge)
+ );
+ } catch (Horde_Db_Exception $e) {}
+ }
+
+ /**
+ * Prepare the base SQL query.
+ *
+ * @param string $mailbox The mailbox.
+ * @param string $join The table to join with the base table.
+ *
+ * @return array SQL query and bound parameters.
+ */
+ protected function _baseSql($mailbox, $join = null)
+ {
+ $sql = sprintf('FROM %s d', self::BASE_TABLE);
+
+ if (!is_null($join)) {
+ $sql .= sprintf(' INNER JOIN %s t ON d.uid = t.uid', $join);
+ }
+
+ return array(
+ $sql . ' WHERE d.hostspec = ? AND d.port = ? AND d.username = ? AND d.mailbox = ?',
+ array(
+ $this->_params['hostspec'],
+ $this->_params['port'],
+ $this->_params['username'],
+ $mailbox
+ )
+ );
+ }
+
+ /**
+ * @param string $mailbox
+ *
+ * @return string UID from base table.
+ */
+ protected function _getUid($mailbox)
+ {
+ $query = $this->_baseSql($mailbox);
+ $query[0] = 'SELECT d.uid ' . $query[0];
+
+ try {
+ return $this->_db->selectValue($query[0], $query[1]);
+ } catch (Horde_Db_Exception $e) {
+ return null;
+ }
+ }
+
+ /**
+ * @param string $mailbox
+ *
+ * @return string UID from base table.
+ */
+ protected function _createUid($mailbox)
+ {
+ return $this->_db->insert(
+ sprintf(
+ 'INSERT INTO %s (hostspec, mailbox, port, username) ' .
+ 'VALUES (?, ?, ?, ?)',
+ self::BASE_TABLE
+ ),
+ array(
+ $this->_params['hostspec'],
+ $mailbox,
+ $this->_params['port'],
+ $this->_params['username']
+ )
+ );
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientCacheBackendMongophp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Cache/Backend/Mongo.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Cache/Backend/Mongo.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Cache/Backend/Mongo.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,425 @@
</span><ins>+<?php
+/**
+ * Copyright 2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * A MongoDB database implementation for caching IMAP/POP data.
+ * Requires the Horde_Mongo class.
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+class Horde_Imap_Client_Cache_Backend_Mongo extends Horde_Imap_Client_Cache_Backend implements Horde_Mongo_Collection_Index
+{
+ /* Collection names. */
+ const BASE = 'horde_imap_client_cache_data';
+ const MD = 'horde_imap_client_cache_metadata';
+ const MSG = 'horde_imap_client_cache_message';
+
+ /**
+ * The MongoDB object for the cache data.
+ *
+ * @var MongoDB
+ */
+ protected $_db;
+
+ /**
+ * The list of indices.
+ *
+ * @var array
+ */
+ protected $_indices = array(
+ self::BASE => array(
+ 'base_index_1' => array(
+ 'hostspec' => 1,
+ 'mailbox' => 1,
+ 'port' => 1,
+ 'username' => 1,
+ )
+ ),
+ self::MSG => array(
+ 'msg_index_1' => array(
+ 'uid' => 1,
+ 'msguid' => 1
+ )
+ )
+ );
+
+ /**
+ * Constructor.
+ *
+ * @param array $params Configuration parameters:
+ * <ul>
+ * <li>
+ * REQUIRED parameters:
+ * <ul>
+ * <li>
+ * mongo_db: (Horde_Mongo_Client) A MongoDB client object.
+ * </li>
+ * </ul>
+ * </li>
+ */
+ public function __construct(array $params = array())
+ {
+ if (!isset($params['mongo_db'])) {
+ throw new InvalidArgumentException('Missing mongo_db parameter.');
+ }
+
+ parent::__construct($params);
+ }
+
+ /**
+ */
+ protected function _initOb()
+ {
+ $this->_db = $this->_params['mongo_db']->selectDB(null);
+ }
+
+ /**
+ */
+ public function get($mailbox, $uids, $fields, $uidvalid)
+ {
+ $this->getMetaData($mailbox, $uidvalid, array('uidvalid'));
+
+ if (!($uid = $this->_getUid($mailbox))) {
+ return array();
+ }
+
+ $out = array();
+ $query = array(
+ 'msguid' => array('$in' => array_map('strval', $uids)),
+ 'uid' => $uid
+ );
+
+ try {
+ $cursor = $this->_db->selectCollection(self::MSG)->find(
+ $query,
+ array(
+ 'data' => true,
+ 'msguid' => true
+ )
+ );
+ foreach ($cursor as $val) {
+ $out[$val['msguid']] = $this->_value($val['data']);
+ }
+ } catch (MongoException $e) {}
+
+ return $out;
+ }
+
+ /**
+ */
+ public function getCachedUids($mailbox, $uidvalid)
+ {
+ $this->getMetaData($mailbox, $uidvalid, array('uidvalid'));
+
+ if (!($uid = $this->_getUid($mailbox))) {
+ return array();
+ }
+
+ $out = array();
+ $query = array(
+ 'uid' => $uid
+ );
+
+ try {
+ $cursor = $this->_db->selectCollection(self::MSG)->find(
+ $query,
+ array(
+ 'msguid' => true
+ )
+ );
+ foreach ($cursor as $val) {
+ $out[] = $val['msguid'];
+ }
+ } catch (MongoException $e) {}
+
+ return $out;
+ }
+
+ /**
+ */
+ public function set($mailbox, $data, $uidvalid)
+ {
+ if ($uid = $this->_getUid($mailbox)) {
+ $res = $this->get($mailbox, array_keys($data), array(), $uidvalid);
+ } else {
+ $res = array();
+ $uid = $this->_createUid($mailbox);
+ }
+
+ $coll = $this->_db->selectCollection(self::MSG);
+
+ foreach ($data as $key => $val) {
+ try {
+ if (isset($res[$key])) {
+ $coll->update(array(
+ 'msguid' => strval($key),
+ 'uid' => $uid
+ ), array(
+ 'data' => $this->_value(array_merge($res[$key], $val)),
+ 'msguid' => strval($key),
+ 'uid' => $uid
+ ));
+ } else {
+ $coll->insert(array(
+ 'data' => $this->_value($val),
+ 'msguid' => strval($key),
+ 'uid' => $uid
+ ));
+ }
+ } catch (MongoException $e) {}
+ }
+
+ /* Update modified time. */
+ try {
+ $this->_db->selectCollection(self::BASE)->update(
+ array('uid' => $uid),
+ array('modified' => time())
+ );
+ } catch (MongoException $e) {}
+
+ /* Update uidvalidity. */
+ $this->setMetaData($mailbox, array('uidvalid' => $uidvalid));
+ }
+
+ /**
+ */
+ public function getMetaData($mailbox, $uidvalid, $entries)
+ {
+ if (!($uid = $this->_getUid($mailbox))) {
+ return array();
+ }
+
+ $out = array();
+ $query = array(
+ 'uid' => $uid
+ );
+
+ if (!empty($entries)) {
+ $entries[] = 'uidvalid';
+ $query['field'] = array(
+ '$in' => array_unique($entries)
+ );
+ }
+
+ try {
+ $cursor = $this->_db->selectCollection(self::MD)->find(
+ $query,
+ array(
+ 'data' => true,
+ 'field' => true
+ )
+ );
+ foreach ($cursor as $val) {
+ $out[$val['field']] = $this->_value($val['data']);
+ }
+
+ if (is_null($uidvalid) ||
+ !isset($out['uidvalid']) ||
+ ($out['uidvalid'] == $uidvalid)) {
+ return $out;
+ }
+
+ $this->deleteMailbox($mailbox);
+ } catch (Horde_Db_Exception $e) {}
+
+ return array();
+ }
+
+ /**
+ */
+ public function setMetaData($mailbox, $data)
+ {
+ if (!($uid = $this->_getUid($mailbox))) {
+ $uid = $this->_createUid($mailbox);
+ }
+
+ $coll = $this->_db->selectCollection(self::MD);
+
+ foreach ($data as $key => $val) {
+ try {
+ $coll->update(
+ array(
+ 'field' => $key,
+ 'uid' => $uid
+ ),
+ array(
+ 'data' => $this->_value($val),
+ 'field' => $key,
+ 'uid' => $uid
+ ),
+ array('upsert' => true)
+ );
+ } catch (MongoException $e) {}
+ }
+ }
+
+ /**
+ */
+ public function deleteMsgs($mailbox, $uids)
+ {
+ if ($uid = $this->_getUid($mailbox)) {
+ try {
+ $this->_db->selectCollection(self::MSG)->remove(array(
+ 'msguid' => array('$in' => array_map('strval', $uids)),
+ 'uid' => $uid
+ ));
+ } catch (MongoException $e) {}
+ }
+ }
+
+ /**
+ */
+ public function deleteMailbox($mailbox)
+ {
+ if (!($uid = $this->_getUid($mailbox))) {
+ return;
+ }
+
+ foreach (array(self::BASE, self::MD, self::MSG) as $val) {
+ try {
+ $this->_db->selectCollection($val)->remove(array(
+ 'uid' => $uid
+ ));
+ } catch (MongoException $e) {}
+ }
+ }
+
+ /**
+ */
+ public function clear($lifetime)
+ {
+ if (is_null($lifetime)) {
+ foreach (array(self::BASE, self::MD, self::MSG) as $val) {
+ $this->_db->selectCollection($val)->drop();
+ }
+ return;
+ }
+
+ $query = array(
+ 'modified' => array('$lt' => (time() - $lifetime))
+ );
+ $uids = array();
+
+ try {
+ $cursor = $this->_db->selectCollection(self::BASE)->find($query);
+ foreach ($cursor as $val) {
+ $uids[] = strval($val['_id']);
+ }
+ } catch (MongoException $e) {}
+
+ if (empty($uids)) {
+ return;
+ }
+
+ foreach (array(self::BASE, self::MD, self::MSG) as $val) {
+ try {
+ $this->_db->selectCollection($val)->remove(array(
+ 'uid' => array('$in' => $uids)
+ ));
+ } catch (MongoException $e) {}
+ }
+ }
+
+ /**
+ * Return the UID for a mailbox/user/server combo.
+ *
+ * @param string $mailbox Mailbox name.
+ *
+ * @return string UID from base table.
+ */
+ protected function _getUid($mailbox)
+ {
+ $query = array(
+ 'hostspec' => $this->_params['hostspec'],
+ 'mailbox' => $mailbox,
+ 'port' => $this->_params['port'],
+ 'username' => $this->_params['username']
+ );
+
+ try {
+ if ($result = $this->_db->selectCollection(self::BASE)->findOne($query)) {
+ return strval($result['_id']);
+ }
+ } catch (MongoException $e) {}
+
+ return null;
+ }
+
+ /**
+ * Create and return the UID for a mailbox/user/server combo.
+ *
+ * @param string $mailbox Mailbox name.
+ *
+ * @return string UID from base table.
+ */
+ protected function _createUid($mailbox)
+ {
+ $this->_db->selectCollection(self::BASE)->insert(array(
+ 'hostspec' => $this->_params['hostspec'],
+ 'mailbox' => $mailbox,
+ 'port' => $this->_params['port'],
+ 'username' => $this->_params['username']
+ ));
+
+ return $this->_getUid($mailbox);
+ }
+
+ /**
+ * Convert data from/to storage format.
+ *
+ * @param mixed|MongoBinData $data The data object.
+ *
+ * @return mixed|MongoBinData The converted data.
+ */
+ protected function _value($data)
+ {
+ static $compress;
+
+ if (!isset($compress)) {
+ $compress = new Horde_Compress_Fast();
+ }
+
+ return ($data instanceof MongoBinData)
+ ? @unserialize($compress->decompress($data->bin))
+ : new MongoBinData($compress->compress(serialize($data)), MongoBinData::BYTE_ARRAY);
+ }
+
+ /* Horde_Mongo_Collection_Index methods. */
+
+ /**
+ */
+ public function checkMongoIndices()
+ {
+ foreach ($this->_indices as $key => $val) {
+ if (!$this->_params['mongo_db']->checkIndices($key, $val)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ */
+ public function createMongoIndices()
+ {
+ foreach ($this->_indices as $key => $val) {
+ $this->_params['mongo_db']->createIndices($key, $val);
+ }
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientCacheBackendNullphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Cache/Backend/Null.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Cache/Backend/Null.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Cache/Backend/Null.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,78 @@
</span><ins>+<?php
+/**
+ * Copyright 2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * A null backend class for storing cached IMAP/POP data.
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+class Horde_Imap_Client_Cache_Backend_Null extends Horde_Imap_Client_Cache_Backend
+{
+ /**
+ */
+ public function get($mailbox, $uids, $fields, $uidvalid)
+ {
+ return array();
+ }
+
+ /**
+ */
+ public function getCachedUids($mailbox, $uidvalid)
+ {
+ return array();
+ }
+
+ /**
+ */
+ public function set($mailbox, $data, $uidvalid)
+ {
+ }
+
+ /**
+ */
+ public function getMetaData($mailbox, $uidvalid, $entries)
+ {
+ return array(
+ 'uidvalid' => 0
+ );
+ }
+
+ /**
+ */
+ public function setMetaData($mailbox, $data)
+ {
+ }
+
+ /**
+ */
+ public function deleteMsgs($mailbox, $uids)
+ {
+ }
+
+ /**
+ */
+ public function deleteMailbox($mailbox)
+ {
+ }
+
+ /**
+ */
+ public function clear($lifetime)
+ {
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientCacheBackendphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Cache/Backend.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Cache/Backend.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Cache/Backend.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,165 @@
</span><ins>+<?php
+/**
+ * Copyright 2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * The abstract backend class for storing IMAP cached data.
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+abstract class Horde_Imap_Client_Cache_Backend implements Serializable
+{
+ /**
+ * Configuration paramters.
+ * Values set by the base Cache object: hostspec, port, username
+ *
+ * @var array
+ */
+ protected $_params = array();
+
+ /**
+ * Constructor.
+ *
+ * @param array $params Configuration parameters.
+ */
+ public function __construct(array $params = array())
+ {
+ $this->setParams($params);
+ $this->_initOb();
+ }
+
+ /**
+ * Initialization tasks.
+ */
+ protected function _initOb()
+ {
+ }
+
+ /**
+ * Add configuration parameters.
+ *
+ * @param array $params Configuration parameters.
+ */
+ public function setParams(array $params = array())
+ {
+ $this->_params = array_merge($this->_params, $params);
+ }
+
+ /**
+ * Get information from the cache for a set of UIDs.
+ *
+ * @param string $mailbox An IMAP mailbox string.
+ * @param array $uids The list of message UIDs to retrieve
+ * information for.
+ * @param array $fields An array of fields to retrieve. If empty,
+ * returns all cached fields.
+ * @param integer $uidvalid The IMAP uidvalidity value of the mailbox.
+ *
+ * @return array An array of arrays with the UID of the message as the
+ * key (if found) and the fields as values (will be
+ * undefined if not found).
+ */
+ abstract public function get($mailbox, $uids, $fields, $uidvalid);
+
+ /**
+ * Get the list of cached UIDs.
+ *
+ * @param string $mailbox An IMAP mailbox string.
+ * @param integer $uidvalid The IMAP uidvalidity value of the mailbox.
+ *
+ * @return array The (unsorted) list of cached UIDs.
+ */
+ abstract public function getCachedUids($mailbox, $uidvalid);
+
+ /**
+ * Store data in cache.
+ *
+ * @param string $mailbox An IMAP mailbox string.
+ * @param array $data The list of data to save. The keys are the
+ * UIDs, the values are an array of information
+ * to save.
+ * @param integer $uidvalid The IMAP uidvalidity value of the mailbox.
+ */
+ abstract public function set($mailbox, $data, $uidvalid);
+
+ /**
+ * Get metadata information for a mailbox.
+ *
+ * @param string $mailbox An IMAP mailbox string.
+ * @param integer $uidvalid The IMAP uidvalidity value of the mailbox.
+ * @param array $entries An array of entries to return. If empty,
+ * returns all metadata.
+ *
+ * @return array The requested metadata. Requested entries that do not
+ * exist will be undefined. The following entries are
+ * defaults and always present:
+ * - uidvalid: (integer) The UIDVALIDITY of the mailbox.
+ */
+ abstract public function getMetaData($mailbox, $uidvalid, $entries);
+
+ /**
+ * Set metadata information for a mailbox.
+ *
+ * @param string $mailbox An IMAP mailbox string.
+ * @param array $data The list of data to save. The keys are the
+ * metadata IDs, the values are the associated
+ * data. (If present, uidvalidity appears as
+ * the 'uidvalid' key in $data.)
+ */
+ abstract public function setMetaData($mailbox, $data);
+
+ /**
+ * Delete messages in the cache.
+ *
+ * @param string $mailbox An IMAP mailbox string.
+ * @param array $uids The list of message UIDs to delete.
+ */
+ abstract public function deleteMsgs($mailbox, $uids);
+
+ /**
+ * Delete a mailbox from the cache.
+ *
+ * @param string $mailbox The mailbox to delete.
+ */
+ abstract public function deleteMailbox($mailbox);
+
+ /**
+ * Clear the cache.
+ *
+ * @param integer $lifetime Only delete entries older than this (in
+ * seconds). If null, deletes all entries.
+ */
+ abstract public function clear($lifetime);
+
+
+ /* Serializable methods. */
+
+ /**
+ */
+ public function serialize()
+ {
+ return serialize($this->_params);
+ }
+
+ /**
+ */
+ public function unserialize($data)
+ {
+ $this->_params = unserialize($data);
+ $this->_initOb();
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientCachephp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Cache.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Cache.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Cache.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,253 @@
</span><ins>+<?php
+/**
+ * Copyright 2005-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2005-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * An interface to cache data retrieved from the IMAP server.
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2005-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+class Horde_Imap_Client_Cache
+{
+ /**
+ * Base client object.
+ *
+ * @var Horde_Imap_Client_Base
+ */
+ protected $_baseob;
+
+ /**
+ * Storage backend.
+ *
+ * @var Horde_Imap_Client_Cache_Backend
+ */
+ protected $_backend;
+
+ /**
+ * Debug output.
+ *
+ * @var Horde_Imap_Client_Base_Debug
+ */
+ protected $_debug = false;
+
+ /**
+ * The configuration params.
+ *
+ * @var array
+ */
+ protected $_params = array();
+
+ /**
+ * Constructor.
+ *
+ * @param array $params Configuration parameters:
+ * <ul>
+ * <li>
+ * REQUIRED Parameters:
+ * <ul>
+ * <li>
+ * backend: (Horde_Imap_Client_Cache_Backend) The cache backend.
+ * </li>
+ * <li>
+ * baseob: (Horde_Imap_Client_Base) The base client object.
+ * </li>
+ * </ul>
+ * </li>
+ * <li>
+ * Optional Parameters:
+ * <ul>
+ * <li>
+ * debug: (Horde_Imap_Client_Base_Debug) Debug object.
+ * DEFAULT: No debug output
+ * </li>
+ * </ul>
+ * </li>
+ * </ul>
+ */
+ public function __construct(array $params = array())
+ {
+ $this->_backend = $params['backend'];
+ $this->_baseob = $params['baseob'];
+
+ $this->_backend->setParams(array(
+ 'hostspec' => $this->_baseob->getParam('hostspec'),
+ 'port' => $this->_baseob->getParam('port'),
+ 'username' => $this->_baseob->getParam('username')
+ ));
+
+ if (isset($params['debug']) &&
+ ($params['debug'] instanceof Horde_Imap_Client_Base_Debug)) {
+ $this->_debug = $params['debug'];
+ $this->_debug->info(sprintf("CACHE: Using the %s storage driver.", get_class($this->_backend)));
+ }
+ }
+
+ /**
+ * Get information from the cache.
+ *
+ * @param string $mailbox An IMAP mailbox string.
+ * @param array $uids The list of message UIDs to retrieve
+ * information for. If empty, returns the list
+ * of cached UIDs.
+ * @param array $fields An array of fields to retrieve. If empty,
+ * returns all cached fields.
+ * @param integer $uidvalid The IMAP uidvalidity value of the mailbox.
+ *
+ * @return array An array of arrays with the UID of the message as the
+ * key (if found) and the fields as values (will be
+ * undefined if not found). If $uids is empty, returns the
+ * full (unsorted) list of cached UIDs.
+ */
+ public function get($mailbox, array $uids = array(), $fields = array(),
+ $uidvalid = null)
+ {
+ $mailbox = strval($mailbox);
+
+ if (empty($uids)) {
+ $ret = $this->_backend->getCachedUids($mailbox, $uidvalid);
+ } else {
+ $ret = $this->_backend->get($mailbox, $uids, $fields, $uidvalid);
+
+ if ($this->_debug && !empty($ret)) {
+ $this->_debug->info('CACHE: Retrieved messages (mailbox: ' . $mailbox . '; UIDs: ' . $this->_baseob->getIdsOb(array_keys($ret))->tostring_sort . ")");
+ }
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Store information in cache.
+ *
+ * @param string $mailbox An IMAP mailbox string.
+ * @param array $data The list of data to save. The keys are the
+ * UIDs, the values are an array of information
+ * to save. If empty, do a check to make sure
+ * the uidvalidity is still valid.
+ * @param integer $uidvalid The IMAP uidvalidity value of the mailbox.
+ */
+ public function set($mailbox, $data, $uidvalid)
+ {
+ $mailbox = strval($mailbox);
+
+ if (empty($data)) {
+ $this->_backend->getMetaData($mailbox, $uidvalid, array('uidvalid'));
+ } else {
+ $this->_backend->set($mailbox, $data, $uidvalid);
+
+ if ($this->_debug) {
+ $this->_debug->info('CACHE: Stored messages (mailbox: ' . $mailbox . '; UIDs: ' . $this->_baseob->getIdsOb(array_keys($data))->tostring_sort . ")");
+ }
+ }
+ }
+
+ /**
+ * Get metadata information for a mailbox.
+ *
+ * @param string $mailbox An IMAP mailbox string.
+ * @param integer $uidvalid The IMAP uidvalidity value of the mailbox.
+ * @param array $entries An array of entries to return. If empty,
+ * returns all metadata.
+ *
+ * @return array The requested metadata. Requested entries that do not
+ * exist will be undefined. The following entries are
+ * defaults and always present:
+ * - uidvalid: (integer) The UIDVALIDITY of the mailbox.
+ */
+ public function getMetaData($mailbox, $uidvalid = null,
+ array $entries = array())
+ {
+ return $this->_backend->getMetaData(strval($mailbox), $uidvalid, $entries);
+ }
+
+ /**
+ * Set metadata information for a mailbox.
+ *
+ * @param string $mailbox An IMAP mailbox string.
+ * @param integer $uidvalid The IMAP uidvalidity value of the mailbox.
+ * @param array $data The list of data to save. The keys are the
+ * metadata IDs, the values are the associated
+ * data. The following labels are reserved:
+ * 'uidvalid'.
+ */
+ public function setMetaData($mailbox, $uidvalid, array $data = array())
+ {
+ unset($data['uidvalid']);
+
+ if (!empty($data)) {
+ if (!empty($uidvalid)) {
+ $data['uidvalid'] = $uidvalid;
+ }
+ $mailbox = strval($mailbox);
+
+ $this->_backend->setMetaData($mailbox, $data);
+
+ if ($this->_debug) {
+ $this->_debug->info('CACHE: Stored metadata (mailbox: ' . $mailbox . '; keys: ' . implode(',', array_keys($data)) . ")");
+ }
+ }
+ }
+
+ /**
+ * Delete messages in the cache.
+ *
+ * @param string $mailbox An IMAP mailbox string.
+ * @param array $uids The list of message UIDs to delete.
+ */
+ public function deleteMsgs($mailbox, $uids)
+ {
+ if (empty($uids)) {
+ return;
+ }
+
+ $mailbox = strval($mailbox);
+
+ $this->_backend->deleteMsgs($mailbox, $uids);
+
+ if ($this->_debug) {
+ $this->_debug->info('CACHE: Deleted messages (mailbox: ' . $mailbox . '; UIDs: ' . $this->_baseob->getIdsOb($uids)->tostring_sort . ")");
+ }
+ }
+
+ /**
+ * Delete a mailbox from the cache.
+ *
+ * @param string $mbox The mailbox to delete.
+ */
+ public function deleteMailbox($mbox)
+ {
+ $mbox = strval($mbox);
+ $this->_backend->deleteMailbox($mbox);
+
+ if ($this->_debug) {
+ $this->_debug->info('CACHE: Deleted mailbox (mailbox: ' . $mbox . ")");
+ }
+ }
+
+ /**
+ * Clear the cache.
+ *
+ * @since 2.9.0
+ *
+ * @param integer $lifetime Only delete entries older than this (in
+ * seconds). If null, deletes all entries.
+ */
+ public function clear($lifetime = null)
+ {
+ $this->_backend->clear($lifetime);
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientDataAclphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Acl.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Acl.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Acl.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,156 @@
</span><ins>+<?php
+/**
+ * Copyright 2011-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2011-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * ACL rights for a mailbox (see RFC 2086/4314).
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2011-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+class Horde_Imap_Client_Data_Acl extends Horde_Imap_Client_Data_AclCommon implements ArrayAccess, IteratorAggregate, Serializable
+{
+ /**
+ * ACL rights.
+ *
+ * @var array
+ */
+ protected $_rights;
+
+ /**
+ * Constructor.
+ *
+ * @param string $rights The rights (see RFC 4314 [2.1]).
+ */
+ public function __construct($rights = '')
+ {
+ $this->_rights = str_split($rights);
+ $this->_normalize();
+ }
+
+ /**
+ * String representation of the ACL.
+ *
+ * @return string String representation (RFC 4314 compliant).
+ */
+ public function __toString()
+ {
+ return implode('', $this->_rights);
+ }
+
+ /**
+ * Computes the difference to another rights string.
+ * Virtual rights are ignored.
+ *
+ * @param string $rights The rights to compute against.
+ *
+ * @return array Two element array: added and removed.
+ */
+ public function diff($rights)
+ {
+ $rlist = array_diff(str_split($rights), array_keys($this->_virtual));
+
+ return array(
+ 'added' => implode('', array_diff($rlist, $this->_rights)),
+ 'removed' => implode('', array_diff($this->_rights, $rlist))
+ );
+ }
+
+ /**
+ * Normalize virtual rights (see RFC 4314 [2.1.1]).
+ */
+ protected function _normalize()
+ {
+ /* Clients conforming to RFC 4314 MUST ignore the virtual ACL_CREATE
+ * and ACL_DELETE rights. See RFC 4314 [2.1]. However, we still need
+ * to handle these rights when dealing with RFC 2086 servers since
+ * we are abstracting out use of ACL_CREATE/ACL_DELETE to their
+ * component RFC 4314 rights. */
+ foreach ($this->_virtual as $key => $val) {
+ if ($this[$key]) {
+ unset($this[$key]);
+ if (!$this[reset($val)]) {
+ $this->_rights = array_unique(array_merge($this->_rights, $val));
+ }
+ }
+ }
+ }
+
+ /* ArrayAccess methods. */
+
+ /**
+ */
+ public function offsetExists($offset)
+ {
+ return $this[$offset];
+ }
+
+ /**
+ */
+ public function offsetGet($offset)
+ {
+ return in_array($offset, $this->_rights);
+ }
+
+ /**
+ */
+ public function offsetSet($offset, $value)
+ {
+ if ($value) {
+ if (!$this[$offset]) {
+ $this->_rights[] = $offset;
+ $this->_normalize();
+ }
+ } elseif ($this[$offset]) {
+ if (isset($this->_virtual[$offset])) {
+ foreach ($this->_virtual[$offset] as $val) {
+ unset($this[$val]);
+ }
+ }
+ unset($this[$offset]);
+ }
+ }
+
+ /**
+ */
+ public function offsetUnset($offset)
+ {
+ $this->_rights = array_values(array_diff($this->_rights, array($offset)));
+ }
+
+ /* IteratorAggregate method. */
+
+ public function getIterator()
+ {
+ return new ArrayIterator($this->_rights);
+ }
+
+ /* Serializable methods. */
+
+ /**
+ */
+ public function serialize()
+ {
+ return json_encode($this->_rights);
+ }
+
+ /**
+ */
+ public function unserialize($data)
+ {
+ $this->_rights = json_decode($data);
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientDataAclCommonphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/AclCommon.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/AclCommon.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/AclCommon.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,72 @@
</span><ins>+<?php
+/**
+ * Copyright 2011-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2011-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * Provides common methods shared in all ACL classes (see RFC 2086/4314).
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2011-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+class Horde_Imap_Client_Data_AclCommon
+{
+ /* Constants for getString(). */
+ const RFC_2086 = 1;
+ const RFC_4314 = 2;
+
+ /**
+ * List of virtual rights (RFC 4314 [2.1.1]).
+ *
+ * @var array
+ */
+ protected $_virtual = array(
+ Horde_Imap_Client::ACL_CREATE => array(
+ Horde_Imap_Client::ACL_CREATEMBOX,
+ Horde_Imap_Client::ACL_DELETEMBOX
+ ),
+ Horde_Imap_Client::ACL_DELETE => array(
+ Horde_Imap_Client::ACL_DELETEMSGS,
+ // Don't put this first - we do checks on the existence of the
+ // first element in this array to determine the RFC type, and this
+ // is duplicate of right contained in ACL_CREATE.
+ Horde_Imap_Client::ACL_DELETEMBOX,
+ Horde_Imap_Client::ACL_EXPUNGE
+ )
+ );
+
+ /**
+ * Returns the raw string to use in IMAP server calls.
+ *
+ * @param integer $type The RFC type to use (RFC_* constant).
+ *
+ * @return string The string representation of the ACL.
+ */
+ public function getString($type = self::RFC_4314)
+ {
+ $acl = strval($this);
+
+ if ($type == self::RFC_2086) {
+ foreach ($this->_virtual as $key => $val) {
+ $acl = str_replace($val, '', $acl, $count);
+ if ($count) {
+ $acl .= $key;
+ }
+ }
+ }
+
+ return $acl;
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientDataAclNegativephp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/AclNegative.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/AclNegative.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/AclNegative.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,25 @@
</span><ins>+<?php
+/**
+ * Copyright 2011-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2011-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * ACL *negative* rights for a mailbox (see RFC 2086/4314 [2]).
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2011-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+class Horde_Imap_Client_Data_AclNegative extends Horde_Imap_Client_Data_Acl
+{
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientDataAclRightsphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/AclRights.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/AclRights.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/AclRights.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,208 @@
</span><ins>+<?php
+/**
+ * Copyright 2011-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2011-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * Available ACL rights for a mailbox/identifier (see RFC 2086/4314).
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2011-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+class Horde_Imap_Client_Data_AclRights extends Horde_Imap_Client_Data_AclCommon implements ArrayAccess, Iterator, Serializable
+{
+ /**
+ * ACL optional rights.
+ *
+ * @var array
+ */
+ protected $_optional = array();
+
+ /**
+ * ACL required rights.
+ *
+ * @var array
+ */
+ protected $_required = array();
+
+ /**
+ * Constructor.
+ *
+ * @param array $required The required rights (see RFC 4314 [2.1]).
+ * @param array $optional The optional rights (see RFC 4314 [2.1]).
+ */
+ public function __construct(array $required = array(),
+ array $optional = array())
+ {
+ $this->_required = $required;
+
+ foreach ($optional as $val) {
+ foreach (str_split($val) as $right) {
+ $this->_optional[$right] = $val;
+ }
+ }
+
+ $this->_normalize();
+ }
+
+ /**
+ * String representation of the ACL.
+ *
+ * @return string String representation (RFC 4314 compliant).
+ *
+ */
+ public function __toString()
+ {
+ return implode('', array_keys(array_flip(array_merge(array_values($this->_required), array_keys($this->_optional)))));
+ }
+
+ /**
+ * Normalize virtual rights (see RFC 4314 [2.1.1]).
+ */
+ protected function _normalize()
+ {
+ /* Clients conforming to RFC 4314 MUST ignore the virtual ACL_CREATE
+ * and ACL_DELETE rights. See RFC 4314 [2.1]. However, we still need
+ * to handle these rights when dealing with RFC 2086 servers since
+ * we are abstracting out use of ACL_CREATE/ACL_DELETE to their
+ * component RFC 4314 rights. */
+ foreach ($this->_virtual as $key => $val) {
+ if (isset($this->_optional[$key])) {
+ unset($this->_optional[$key]);
+ foreach ($val as $val2) {
+ $this->_optional[$val2] = implode('', $val);
+ }
+ } elseif (($pos = array_search($key, $this->_required)) !== false) {
+ unset($this->_required[$pos]);
+ $this->_required = array_unique(array_merge($this->_required, $val));
+ }
+ }
+ }
+
+ /* ArrayAccess methods. */
+
+ /**
+ */
+ public function offsetExists($offset)
+ {
+ return (bool)$this[$offset];
+ }
+
+ /**
+ */
+ public function offsetGet($offset)
+ {
+ if (isset($this->_optional[$offset])) {
+ return $this->_optional[$offset];
+ }
+
+ $pos = array_search($offset, $this->_required);
+
+ return ($pos === false)
+ ? null
+ : $this->_required[$pos];
+ }
+
+ /**
+ */
+ public function offsetSet($offset, $value)
+ {
+ $this->_optional[$offset] = $value;
+ $this->_normalize();
+ }
+
+ /**
+ */
+ public function offsetUnset($offset)
+ {
+ unset($this->_optional[$offset]);
+ $this->_required = array_values(array_diff($this->_required, array($offset)));
+
+ if (isset($this->_virtual[$offset])) {
+ foreach ($this->_virtual[$offset] as $val) {
+ unset($this[$val]);
+ }
+ }
+ }
+
+ /* Iterator methods. */
+
+ /**
+ */
+ public function current()
+ {
+ $val = current($this->_required);
+ return is_null($val)
+ ? current($this->_optional)
+ : $val;
+ }
+
+ /**
+ */
+ public function key()
+ {
+ $key = key($this->_required);
+ return is_null($key)
+ ? key($this->_optional)
+ : $key;
+ }
+
+ /**
+ */
+ public function next()
+ {
+ if (key($this->_required) === null) {
+ next($this->_optional);
+ } else {
+ next($this->_required);
+ }
+ }
+
+ /**
+ */
+ public function rewind()
+ {
+ reset($this->_required);
+ reset($this->_optional);
+ }
+
+ /**
+ */
+ public function valid()
+ {
+ return ((key($this->_required) !== null) ||
+ (key($this->_optional) !== null));
+
+ }
+
+ /* Serializable methods. */
+
+ /**
+ */
+ public function serialize()
+ {
+ return json_encode(array(
+ $this->_required,
+ $this->_optional
+ ));
+ }
+
+ /**
+ */
+ public function unserialize($data)
+ {
+ list($this->_required, $this->_optional) = json_decode($data);
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientDataBaseSubjectphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/BaseSubject.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/BaseSubject.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/BaseSubject.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,237 @@
</span><ins>+<?php
+/**
+ * Copyright 2008-2013 Horde LLC (http://www.horde.org/)
+ *
+ * getBaseSubject() code adapted from imap-base-subject.c (Dovecot 1.2)
+ * Original code released under the LGPL-2.1
+ * Copyright (c) 2002-2008 Timo Sirainen <tss@iki.fi>
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2002-2008 Timo Sirainen
+ * @copyright 2008-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * Determines the "base subject" of a string (RFC 5256 [2.1]).
+ *
+ * @author Timo Sirainen <tss@iki.fi>
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2002-2008 Timo Sirainen
+ * @copyright 2011-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+class Horde_Imap_Client_Data_BaseSubject
+{
+ /**
+ * The base subject.
+ *
+ * @var string
+ */
+ protected $_subject;
+
+ /**
+ * Constructor.
+ *
+ * @param string $str The subject string.
+ * @param array $opts Additional options:
+ * - keepblob: (boolean) Don't remove any "blob" information (i.e. text
+ * leading text between square brackets) from string.
+ *
+ * @return string The cleaned up subject string.
+ */
+ public function __construct($str, array $opts = array())
+ {
+ // Rule 1a: MIME decode.
+ $str = Horde_Mime::decode($str);
+
+ // Rule 1b: Remove superfluous whitespace.
+ $str = preg_replace("/[\t\r\n ]+/", ' ', $str);
+
+ if (!strlen($str)) {
+ $this->_subject = '';
+ }
+
+ do {
+ /* (2) Remove all trailing text of the subject that matches the
+ * the subj-trailer ABNF, repeat until no more matches are
+ * possible. */
+ $str = preg_replace("/(?:\s*\(fwd\)\s*)+$/i", '', $str);
+
+ do {
+ /* (3) Remove all prefix text of the subject that matches the
+ * subj-leader ABNF. */
+ $found = $this->_removeSubjLeader($str, !empty($opts['keepblob']));
+
+ /* (4) If there is prefix text of the subject that matches
+ * the subj-blob ABNF, and removing that prefix leaves a
+ * non-empty subj-base, then remove the prefix text. */
+ $found = (empty($opts['keepblob']) && $this->_removeBlobWhenNonempty($str)) || $found;
+
+ /* (5) Repeat (3) and (4) until no matches remain. */
+ } while ($found);
+
+ /* (6) If the resulting text begins with the subj-fwd-hdr ABNF and
+ * ends with the subj-fwd-trl ABNF, remove the subj-fwd-hdr and
+ * subj-fwd-trl and repeat from step (2). */
+ } while ($this->_removeSubjFwdHdr($str));
+
+ $this->_subject = $str;
+ }
+
+ /**
+ * Return the "base subject" defined in RFC 5256 [2.1].
+ *
+ * @return string The base subject.
+ */
+ public function __toString()
+ {
+ return $this->_subject;
+ }
+
+ /**
+ * Remove all prefix text of the subject that matches the subj-leader
+ * ABNF.
+ *
+ * @param string &$str The subject string.
+ * @param boolean $keepblob Remove blob information?
+ *
+ * @return boolean True if string was altered.
+ */
+ protected function _removeSubjLeader(&$str, $keepblob = false)
+ {
+ $ret = false;
+
+ if (!strlen($str)) {
+ return $ret;
+ }
+
+ if ($len = strspn($str, " \t")) {
+ $str = substr($str, $len);
+ $ret = true;
+ }
+
+ $i = 0;
+
+ if (!$keepblob) {
+ while (isset($str[$i]) && ($str[$i] == '[')) {
+ if (($i = $this->_removeBlob($str, $i)) === false) {
+ return $ret;
+ }
+ }
+ }
+
+ if (stripos($str, 're', $i) === 0) {
+ $i += 2;
+ } elseif (stripos($str, 'fwd', $i) === 0) {
+ $i += 3;
+ } elseif (stripos($str, 'fw', $i) === 0) {
+ $i += 2;
+ } else {
+ return $ret;
+ }
+
+ $i += strspn($str, " \t", $i);
+
+ if (!$keepblob) {
+ while (isset($str[$i]) && ($str[$i] == '[')) {
+ if (($i = $this->_removeBlob($str, $i)) === false) {
+ return $ret;
+ }
+ }
+ }
+
+ if (!isset($str[$i]) || ($str[$i] != ':')) {
+ return $ret;
+ }
+
+ $str = substr($str, ++$i);
+
+ return true;
+ }
+
+ /**
+ * Remove "[...]" text.
+ *
+ * @param string $str The subject string.
+ * @param integer $i Current position.
+ *
+ * @return boolean|integer False if blob was not found, otherwise the
+ * string position of the first non-blob char.
+ */
+ protected function _removeBlob($str, $i)
+ {
+ if ($str[$i] != '[') {
+ return false;
+ }
+
+ ++$i;
+
+ for ($cnt = strlen($str); $i < $cnt; ++$i) {
+ if ($str[$i] == ']') {
+ break;
+ }
+
+ if ($str[$i] == '[') {
+ return false;
+ }
+ }
+
+ if ($i == ($cnt - 1)) {
+ return false;
+ }
+
+ ++$i;
+
+ if ($str[$i] == ' ') {
+ ++$i;
+ }
+
+ return $i;
+ }
+
+ /**
+ * Remove "[...]" text if it doesn't result in the subject becoming
+ * empty.
+ *
+ * @param string &$str The subject string.
+ *
+ * @return boolean True if string was altered.
+ */
+ protected function _removeBlobWhenNonempty(&$str)
+ {
+ if ($str &&
+ ($str[0] == '[') &&
+ (($i = $this->_removeBlob($str, 0)) !== false) &&
+ ($i != strlen($str))) {
+ $str = substr($str, $i);
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Remove a "[fwd: ... ]" string.
+ *
+ * @param string &$str The subject string.
+ *
+ * @return boolean True if string was altered.
+ */
+ protected function _removeSubjFwdHdr(&$str)
+ {
+ if ((stripos($str, '[fwd:') !== 0) || (substr($str, -1) != ']')) {
+ return false;
+ }
+
+ $str = substr($str, 5, -1);
+ return true;
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientDataEnvelopephp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Envelope.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Envelope.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Envelope.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,212 @@
</span><ins>+<?php
+/**
+ * Copyright 2011-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2011-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * Envelope data as returned by the IMAP FETCH command (RFC 3501 [7.4.2]).
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2011-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ *
+ * @property Horde_Mail_Rfc822_List $bcc Bcc address(es).
+ * @property Horde_Mail_Rfc822_List $cc Cc address(es).
+ * @property Horde_Imap_Client_DateTime $date IMAP internal date.
+ * @property Horde_Mail_Rfc822_List $from From address(es).
+ * @property string $in_reply_to Message-ID of the message replied to.
+ * @property string $message_id Message-ID of the message.
+ * @property Horde_Mail_Rfc822_List $reply_to Reply-to address(es).
+ * @property Horde_Mail_Rfc822_List $sender Sender address.
+ * @property string $subject Subject.
+ * @property Horde_Mail_Rfc822_List $to To address(es).
+ */
+class Horde_Imap_Client_Data_Envelope implements Serializable
+{
+ /** Serializable version. */
+ const VERSION = 2;
+
+ /**
+ * Data object.
+ *
+ * @var Horde_Mime_Headers
+ */
+ protected $_data;
+
+ /**
+ * Constructor.
+ *
+ * @var array $data An array of property names (keys) and values to set
+ * in this object.
+ */
+ public function __construct(array $data = array())
+ {
+ $this->_data = new Horde_Mime_Headers();
+
+ foreach ($data as $key => $val) {
+ $this->$key = $val;
+ }
+ }
+
+ /**
+ */
+ public function __get($name)
+ {
+ switch ($name) {
+ case 'reply_to':
+ $name = 'reply-to';
+ // Fall-through
+
+ case 'bcc':
+ case 'cc':
+ case 'from':
+ case 'sender':
+ case 'to':
+ if (($ob = $this->_data->getOb($name)) !== null) {
+ return $ob;
+ }
+
+ if (in_array($name, array('sender', 'reply-to'))) {
+ return $this->from;
+ }
+ break;
+
+ case 'date':
+ if (($val = $this->_data->getValue($name)) !== null) {
+ return new Horde_Imap_Client_DateTime($val);
+ }
+ break;
+
+ case 'in_reply_to':
+ case 'message_id':
+ case 'subject':
+ if (($val = $this->_data->getValue($name)) !== null) {
+ return $val;
+ }
+ break;
+ }
+
+ // Default values.
+ switch ($name) {
+ case 'bcc':
+ case 'cc':
+ case 'from':
+ case 'to':
+ return new Horde_Mail_Rfc822_List();
+
+ case 'date':
+ return new Horde_Imap_Client_DateTime();
+
+ case 'in_reply_to':
+ case 'message_id':
+ case 'subject':
+ return '';
+ }
+
+ return null;
+ }
+
+ /**
+ */
+ public function __set($name, $value)
+ {
+ if (!strlen($value)) {
+ return;
+ }
+
+ switch ($name) {
+ case 'bcc':
+ case 'cc':
+ case 'date':
+ case 'from':
+ case 'in_reply_to':
+ case 'message_id':
+ case 'reply_to':
+ case 'sender':
+ case 'subject':
+ case 'to':
+ switch ($name) {
+ case 'from':
+ foreach (array('reply_to', 'sender') as $val) {
+ if ($this->$val->match($value)) {
+ $this->_data->removeHeader($val);
+ }
+ }
+ break;
+
+ case 'reply_to':
+ case 'sender':
+ if ($this->from->match($value)) {
+ $this->_data->removeHeader($name);
+ return;
+ }
+
+ /* Convert reply-to name. */
+ if ($name == 'reply_to') {
+ $name = 'reply-to';
+ }
+ break;
+ }
+
+ $this->_data->addHeader($name, $value, array(
+ 'sanity_check' => true
+ ));
+ break;
+ }
+ }
+
+ /**
+ */
+ public function __isset($name)
+ {
+ switch ($name) {
+ case 'reply_to':
+ $name = 'reply-to';
+ // Fall-through
+
+ case 'sender':
+ if ($this->_data->getValue($name) !== null) {
+ return true;
+ }
+ $name = 'from';
+ break;
+ }
+
+ return ($this->_data->getValue($name) !== null);
+ }
+
+ /* Serializable methods. */
+
+ /**
+ */
+ public function serialize()
+ {
+ return serialize(array(
+ 'd' => $this->_data,
+ 'v' => self::VERSION
+ ));
+ }
+
+ /**
+ */
+ public function unserialize($data)
+ {
+ $data = @unserialize($data);
+ if (empty($data['v']) || ($data['v'] != self::VERSION)) {
+ throw new Exception('Cache version change');
+ }
+
+ $this->_data = $data['d'];
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientDataFetchPop3php"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Fetch/Pop3.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Fetch/Pop3.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Fetch/Pop3.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,36 @@
</span><ins>+<?php
+/**
+ * Copyright 2011-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2011-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * Object containg POP3 fetch data.
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2011-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+class Horde_Imap_Client_Data_Fetch_Pop3 extends Horde_Imap_Client_Data_Fetch
+{
+ /**
+ * Set UID.
+ *
+ * @param string $uid The message UID. Unlike IMAP, this UID does not
+ * have to be an integer.
+ */
+ public function setUid($uid)
+ {
+ $this->_data[Horde_Imap_Client::FETCH_UID] = strval($uid);
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientDataFetchphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Fetch.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Fetch.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Fetch.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,567 @@
</span><ins>+<?php
+/**
+ * Copyright 2011-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2011-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * Object containing data returned by the Horde_Imap_Client_Base#fetch()
+ * command.
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2011-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+class Horde_Imap_Client_Data_Fetch
+{
+ /* Constants. */
+ const HEADER_PARSE = 1;
+ const HEADER_STREAM = 2;
+
+ /**
+ * Internal data array.
+ *
+ * @var array
+ */
+ protected $_data = array();
+
+ /**
+ * Set the full message property.
+ *
+ * @param mixed $msg The full message text, as either a string or stream
+ * resource.
+ */
+ public function setFullMsg($msg)
+ {
+ $this->_data[Horde_Imap_Client::FETCH_FULLMSG] = $msg;
+ }
+
+ /**
+ * Returns the full message.
+ *
+ * @param boolean $stream Return as a stream?
+ *
+ * @return mixed The full text of the entire message.
+ */
+ public function getFullMsg($stream = false)
+ {
+ return $this->_msgText($stream, isset($this->_data[Horde_Imap_Client::FETCH_FULLMSG]) ? $this->_data[Horde_Imap_Client::FETCH_FULLMSG] : null);
+ }
+
+ /**
+ * Set the message structure.
+ *
+ * @param Horde_Mime_Part $structure The base MIME part of the message.
+ */
+ public function setStructure(Horde_Mime_Part $structure)
+ {
+ $this->_data[Horde_Imap_Client::FETCH_STRUCTURE] = $structure;
+ }
+
+ /**
+ * Get the message structure.
+ *
+ * @return Horde_Mime_Part $structure The base MIME part of the message.
+ */
+ public function getStructure()
+ {
+ return isset($this->_data[Horde_Imap_Client::FETCH_STRUCTURE])
+ ? clone $this->_data[Horde_Imap_Client::FETCH_STRUCTURE]
+ : new Horde_Mime_Part();
+ }
+
+ /**
+ * Set a header entry.
+ *
+ * @param string $label The search label.
+ * @param mixed $data Either a Horde_Mime_Headers object or the raw
+ * header text.
+ */
+ public function setHeaders($label, $data)
+ {
+ $this->_data[Horde_Imap_Client::FETCH_HEADERS][$label] = $data;
+ }
+
+ /**
+ * Get a header entry.
+ *
+ * @param string $label The search label.
+ * @param integer $format The return format. If self::HEADER_PARSE,
+ * returns a Horde_Mime_Headers object. If
+ * self::HEADER_STREAM, returns a stream.
+ * Otherwise, returns header text.
+ *
+ * @return mixed See $format.
+ */
+ public function getHeaders($label, $format = 0)
+ {
+ return $this->_getHeaders($label, $format, Horde_Imap_Client::FETCH_HEADERS);
+ }
+
+ /**
+ * Set a header text entry.
+ *
+ * @param string $id The MIME ID.
+ * @param string $text The header text.
+ */
+ public function setHeaderText($id, $text)
+ {
+ $this->_data[Horde_Imap_Client::FETCH_HEADERTEXT][$id] = $text;
+ }
+
+ /**
+ * Get a header text entry.
+ *
+ * @param string $id The MIME ID.
+ * @param integer $format The return format. If self::HEADER_PARSE,
+ * returns a Horde_Mime_Headers object. If
+ * self::HEADER_STREAM, returns a stream.
+ * Otherwise, returns header text.
+ *
+ * @return mixed See $format.
+ */
+ public function getHeaderText($id = 0, $format = 0)
+ {
+ return $this->_getHeaders($id, $format, Horde_Imap_Client::FETCH_HEADERTEXT);
+ }
+
+ /**
+ * Set a MIME header entry.
+ *
+ * @param string $id The MIME ID.
+ * @param string $text The header text.
+ */
+ public function setMimeHeader($id, $text)
+ {
+ $this->_data[Horde_Imap_Client::FETCH_MIMEHEADER][$id] = $text;
+ }
+
+ /**
+ * Get a MIME header entry.
+ *
+ * @param string $id The MIME ID.
+ * @param integer $format The return format. If self::HEADER_PARSE,
+ * returns a Horde_Mime_Headers object. If
+ * self::HEADER_STREAM, returns a stream.
+ * Otherwise, returns header text.
+ *
+ * @return mixed See $format.
+ */
+ public function getMimeHeader($id, $format = 0)
+ {
+ return $this->_getHeaders($id, $format, Horde_Imap_Client::FETCH_MIMEHEADER);
+ }
+
+ /**
+ * Set a body part entry.
+ *
+ * @param string $id The MIME ID.
+ * @param mixed $text The body part text, as either a string or stream
+ * resource.
+ * @param string $decode Either '8bit', 'binary', or null.
+ */
+ public function setBodyPart($id, $text, $decode = null)
+ {
+ $this->_data[Horde_Imap_Client::FETCH_BODYPART][$id] = array(
+ 'd' => $decode,
+ 't' => $text
+ );
+ }
+
+ /**
+ * Set the body part size for a body part.
+ *
+ * @param string $id The MIME ID.
+ * @param integer $size The size (in bytes).
+ */
+ public function setBodyPartSize($id, $size)
+ {
+ $this->_data[Horde_Imap_Client::FETCH_BODYPARTSIZE][$id] = intval($size);
+ }
+
+ /**
+ * Get a body part entry.
+ *
+ * @param string $id The MIME ID.
+ * @param boolean $stream Return as a stream?
+ *
+ * @return mixed The full text of the body part.
+ */
+ public function getBodyPart($id, $stream = false)
+ {
+ return $this->_msgText($stream, isset($this->_data[Horde_Imap_Client::FETCH_BODYPART][$id]) ? $this->_data[Horde_Imap_Client::FETCH_BODYPART][$id]['t'] : null);
+ }
+
+ /**
+ * Determines if/how a body part was MIME decoded on the server.
+ *
+ * @param string $id The MIME ID.
+ *
+ * @return string Either '8bit', 'binary', or null.
+ */
+ public function getBodyPartDecode($id)
+ {
+ return isset($this->_data[Horde_Imap_Client::FETCH_BODYPART][$id])
+ ? $this->_data[Horde_Imap_Client::FETCH_BODYPART][$id]['d']
+ : null;
+ }
+
+ /**
+ * Returns the body part size, if returned by the server.
+ *
+ * @param string $id The MIME ID.
+ *
+ * @return integer The body part size, in bytes.
+ */
+ public function getBodyPartSize($id)
+ {
+ return isset($this->_data[Horde_Imap_Client::FETCH_BODYPARTSIZE][$id])
+ ? $this->_data[Horde_Imap_Client::FETCH_BODYPARTSIZE][$id]
+ : null;
+ }
+
+ /**
+ * Set a body text entry.
+ *
+ * @param string $id The MIME ID.
+ * @param mixed $text The body part text, as either a string or stream
+ * resource.
+ */
+ public function setBodyText($id, $text)
+ {
+ $this->_data[Horde_Imap_Client::FETCH_BODYTEXT][$id] = $text;
+ }
+
+ /**
+ * Get a body text entry.
+ *
+ * @param string $id The MIME ID.
+ * @param boolean $stream Return as a stream?
+ *
+ * @return mixed The full text of the body text.
+ */
+ public function getBodyText($id = 0, $stream = false)
+ {
+ return $this->_msgText($stream, isset($this->_data[Horde_Imap_Client::FETCH_BODYTEXT][$id]) ? $this->_data[Horde_Imap_Client::FETCH_BODYTEXT][$id] : null);
+ }
+
+ /**
+ * Set envelope data.
+ *
+ * @param array $data The envelope data to pass to the Envelope object
+ * constructor, or an Envelope object.
+ */
+ public function setEnvelope($data)
+ {
+ $this->_data[Horde_Imap_Client::FETCH_ENVELOPE] = is_array($data)
+ ? new Horde_Imap_Client_Data_Envelope($data)
+ : $data;
+ }
+
+ /**
+ * Get envelope data.
+ *
+ * @return Horde_Imap_Client_Data_Envelope An envelope object.
+ */
+ public function getEnvelope()
+ {
+ return isset($this->_data[Horde_Imap_Client::FETCH_ENVELOPE])
+ ? clone $this->_data[Horde_Imap_Client::FETCH_ENVELOPE]
+ : new Horde_Imap_Client_Data_Envelope();
+ }
+
+ /**
+ * Set IMAP flags.
+ *
+ * @param array $flags An array of IMAP flags.
+ */
+ public function setFlags(array $flags)
+ {
+ $this->_data[Horde_Imap_Client::FETCH_FLAGS] = array_map('strtolower', $flags);
+ }
+
+ /**
+ * Get IMAP flags.
+ *
+ * @return array An array of IMAP flags (all flags in lowercase).
+ */
+ public function getFlags()
+ {
+ return isset($this->_data[Horde_Imap_Client::FETCH_FLAGS])
+ ? $this->_data[Horde_Imap_Client::FETCH_FLAGS]
+ : array();
+ }
+
+ /**
+ * Set IMAP internal date.
+ *
+ * @param mixed $date Either a Horde_Imap_Client_DateTime object or a
+ * date string.
+ */
+ public function setImapDate($date)
+ {
+ $this->_data[Horde_Imap_Client::FETCH_IMAPDATE] = is_object($date)
+ ? $date
+ : new Horde_Imap_Client_DateTime($date);
+ }
+
+ /**
+ * Get internal IMAP date.
+ *
+ * @return Horde_Imap_Client_DateTime A date object.
+ */
+ public function getImapDate()
+ {
+ return isset($this->_data[Horde_Imap_Client::FETCH_IMAPDATE])
+ ? clone $this->_data[Horde_Imap_Client::FETCH_IMAPDATE]
+ : new Horde_Imap_Client_DateTime();
+ }
+
+ /**
+ * Set message size.
+ *
+ * @param integer $size The size of the message, in bytes.
+ */
+ public function setSize($size)
+ {
+ $this->_data[Horde_Imap_Client::FETCH_SIZE] = intval($size);
+ }
+
+ /**
+ * Get message size.
+ *
+ * @return integer The size of the message, in bytes.
+ */
+ public function getSize()
+ {
+ return isset($this->_data[Horde_Imap_Client::FETCH_SIZE])
+ ? $this->_data[Horde_Imap_Client::FETCH_SIZE]
+ : 0;
+ }
+
+ /**
+ * Set UID.
+ *
+ * @param integer $uid The message UID.
+ */
+ public function setUid($uid)
+ {
+ $this->_data[Horde_Imap_Client::FETCH_UID] = intval($uid);
+ }
+
+ /**
+ * Get UID.
+ *
+ * @return integer The message UID.
+ */
+ public function getUid()
+ {
+ return isset($this->_data[Horde_Imap_Client::FETCH_UID])
+ ? $this->_data[Horde_Imap_Client::FETCH_UID]
+ : null;
+ }
+
+ /**
+ * Set message sequence number.
+ *
+ * @param integer $seq The message sequence number.
+ */
+ public function setSeq($seq)
+ {
+ $this->_data[Horde_Imap_Client::FETCH_SEQ] = intval($seq);
+ }
+
+ /**
+ * Get message sequence number.
+ *
+ * @return integer The message sequence number.
+ */
+ public function getSeq()
+ {
+ return isset($this->_data[Horde_Imap_Client::FETCH_SEQ])
+ ? $this->_data[Horde_Imap_Client::FETCH_SEQ]
+ : null;
+ }
+
+ /**
+ * Set the modified sequence value for the message.
+ *
+ * @param integer $modseq The modseq value.
+ */
+ public function setModSeq($modseq)
+ {
+ $this->_data[Horde_Imap_Client::FETCH_MODSEQ] = intval($modseq);
+ }
+
+ /**
+ * Get the modified sequence value for the message.
+ *
+ * @return integer The modseq value.
+ */
+ public function getModSeq()
+ {
+ return isset($this->_data[Horde_Imap_Client::FETCH_MODSEQ])
+ ? $this->_data[Horde_Imap_Client::FETCH_MODSEQ]
+ : null;
+ }
+
+ /**
+ * Set the internationalized downgraded status for the message.
+ *
+ * @since 2.11.0
+ *
+ * @param boolean $downgraded True if at least one message component has
+ * been downgraded.
+ */
+ public function setDowngraded($downgraded)
+ {
+ if ($downgraded) {
+ $this->_data[Horde_Imap_Client::FETCH_DOWNGRADED] = true;
+ } else {
+ unset($this->_data[Horde_Imap_Client::FETCH_DOWNGRADED]);
+ }
+ }
+
+ /**
+ * Does the message contain internationalized downgraded data (i.e. it
+ * is a "surrogate" message)?
+ *
+ * @since 2.11.0
+ *
+ * @return boolean True if at least one message components has been
+ * downgraded.
+ */
+ public function isDowngraded()
+ {
+ return !empty($this->_data[Horde_Imap_Client::FETCH_DOWNGRADED]);
+ }
+
+ /**
+ * Return the internal representation of the data.
+ *
+ * @return array The data array.
+ */
+ public function getRawData()
+ {
+ return $this->_data;
+ }
+
+ /**
+ * Merge a fetch object into this one.
+ *
+ * @param Horde_Imap_Client_Data_Fetch $data A fetch object.
+ */
+ public function merge(Horde_Imap_Client_Data_Fetch $data)
+ {
+ $this->_data = array_replace_recursive($this->_data, $data->getRawData());
+ }
+
+ /**
+ * Does this object containing cacheable data of the given type?
+ *
+ * @param integer $type The type to query.
+ *
+ * @return boolean True if the type is cacheable.
+ */
+ public function exists($type)
+ {
+ return isset($this->_data[$type]);
+ }
+
+ /**
+ * Does this object contain only default values for all fields?
+ *
+ * @return boolean True if object contains default data.
+ */
+ public function isDefault()
+ {
+ return empty($this->_data);
+ }
+
+ /**
+ * Return text representation of a field.
+ *
+ * @param boolean $stream Return as a stream?
+ * @param mixed $data The field data (string or resource) or null if
+ * field does not exist.
+ *
+ * @return mixed Requested text representation.
+ */
+ protected function _msgText($stream, $data)
+ {
+ if ($stream) {
+ if (is_resource($data)) {
+ rewind($data);
+ return $data;
+ }
+
+ $tmp = fopen('php://temp', 'w+');
+
+ if (!is_null($data)) {
+ fwrite($tmp, $data);
+ rewind($tmp);
+ }
+
+ return $tmp;
+ }
+
+ if (is_resource($data)) {
+ rewind($data);
+ return stream_get_contents($data);
+ }
+
+ return strval($data);
+ }
+
+ /**
+ * Return representation of a header field.
+ *
+ * @param string $id The header id.
+ * @param integer $format The return format. If self::HEADER_PARSE,
+ * returns a Horde_Mime_Headers object. If
+ * self::HEADER_STREAM, returns a stream.
+ * Otherwise, returns header text.
+ * @param integer $key The array key where the data is stored in the
+ * internal array.
+ *
+ * @return mixed The data in the format specified by $format.
+ */
+ protected function _getHeaders($id, $format, $key)
+ {
+ switch ($format) {
+ case self::HEADER_STREAM:
+ if (!isset($this->_data[$key][$id])) {
+ return $this->_msgText(true, null);
+ } elseif (is_object($this->_data[$key][$id])) {
+ return $this->_getHeaders($id, 0, $key);
+ }
+ return $this->_msgText(true, $this->_data[$key][$id]);
+
+ case self::HEADER_PARSE:
+ if (!isset($this->_data[$key][$id])) {
+ return new Horde_Mime_Headers();
+ } elseif (is_object($this->_data[$key][$id])) {
+ return clone $this->_data[$key][$id];
+ }
+ return Horde_Mime_Headers::parseHeaders($this->_getHeaders($id, 0, $key));
+ }
+
+ if (!isset($this->_data[$key][$id])) {
+ return '';
+ }
+
+ return is_object($this->_data[$key][$id])
+ ? $this->_data[$key][$id]->toString(array('nowrap' => true))
+ : $this->_msgText(false, $this->_data[$key][$id]);
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientDataFormatAstringphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Format/Astring.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Format/Astring.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Format/Astring.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,32 @@
</span><ins>+<?php
+/**
+ * Copyright 2012-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * Object representation of an IMAP astring (atom or string) (RFC 3501 [4.3]).
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+class Horde_Imap_Client_Data_Format_Astring extends Horde_Imap_Client_Data_Format_String
+{
+ /**
+ */
+ public function quoted()
+ {
+ return $this->_filter->quoted || !$this->_data->length();
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientDataFormatAtomphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Format/Atom.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Format/Atom.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Format/Atom.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,53 @@
</span><ins>+<?php
+/**
+ * Copyright 2012-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * Object representation of an IMAP atom (RFC 3501 [4.1]).
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+class Horde_Imap_Client_Data_Format_Atom extends Horde_Imap_Client_Data_Format
+{
+ /**
+ */
+ public function escape()
+ {
+ return strlen($this->_data)
+ ? parent::escape()
+ : '""';
+ }
+
+ /**
+ */
+ public function verify()
+ {
+ if (strlen($this->_data) != strlen($this->stripNonAtomCharacters())) {
+ throw new Horde_Imap_Client_Data_Format_Exception('Illegal character in IMAP atom.');
+ }
+ }
+
+ /**
+ * Strip out any characters that are not allowed in an IMAP atom.
+ *
+ * @return string The atom data disallowed characters removed.
+ */
+ public function stripNonAtomCharacters()
+ {
+ return str_replace(array('(', ')', '{', ' ', '%', '*', '"', '\\', ']'), '', preg_replace('/[\x00-\x1f\x7f]/', '', $this->_data));
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientDataFormatDatephp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Format/Date.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Format/Date.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Format/Date.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,49 @@
</span><ins>+<?php
+/**
+ * Copyright 2012-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * Object representation of an IMAP date string (RFC 3501 [9]).
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+class Horde_Imap_Client_Data_Format_Date extends Horde_Imap_Client_Data_Format
+{
+ /**
+ * Constructor.
+ *
+ * @param mixed $data Either a DateTime object, or a date format that
+ * can be converted to a DateTime object.
+ *
+ * @throws Exception
+ */
+ public function __construct($data)
+ {
+ if (!($data instanceof DateTime)) {
+ $data = new Horde_Imap_Client_DateTime($data);
+ }
+
+ parent::__construct($data);
+ }
+
+ /**
+ */
+ public function __toString()
+ {
+ return $this->_data->format('j-M-Y');
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientDataFormatDateTimephp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Format/DateTime.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Format/DateTime.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Format/DateTime.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,39 @@
</span><ins>+<?php
+/**
+ * Copyright 2012-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * Object representation of an IMAP date-time string (RFC 3501 [9]).
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+class Horde_Imap_Client_Data_Format_DateTime extends Horde_Imap_Client_Data_Format_Date
+{
+ /**
+ */
+ public function __toString()
+ {
+ return $this->_data->format('j-M-Y H:i:s O');
+ }
+
+ /**
+ */
+ public function escape()
+ {
+ return '"' . strval($this) . '"';
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientDataFormatExceptionphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Format/Exception.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Format/Exception.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Format/Exception.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,25 @@
</span><ins>+<?php
+/**
+ * Copyright 2012-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * Exception object for IMAP data format errors.
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+class Horde_Imap_Client_Data_Format_Exception extends Horde_Exception_Wrapped
+{
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientDataFormatFilterQuotephp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Format/Filter/Quote.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Format/Filter/Quote.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Format/Filter/Quote.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,43 @@
</span><ins>+<?php
+/**
+ * Copyright 2012-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * Stream filter to output a quoted IMAP string.
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+class Horde_Imap_Client_Data_Format_Filter_Quote extends php_user_filter
+{
+ /**
+ * @see stream_filter_register()
+ */
+ public function filter($in, $out, &$consumed, $closing)
+ {
+ stream_bucket_append($out, stream_bucket_new($this->stream, '"'));
+
+ while ($bucket = stream_bucket_make_writeable($in)) {
+ $consumed += $bucket->datalen;
+ $bucket->data = addcslashes($bucket->data, '"\\');
+ stream_bucket_append($out, $bucket);
+ }
+
+ stream_bucket_append($out, stream_bucket_new($this->stream, '"'));
+
+ return PSFS_PASS_ON;
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientDataFormatFilterStringphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Format/Filter/String.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Format/Filter/String.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Format/Filter/String.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,112 @@
</span><ins>+<?php
+/**
+ * Copyright 2012-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * Stream filter to analyze an IMAP string to determine how to send to the
+ * server.
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+class Horde_Imap_Client_Data_Format_Filter_String extends php_user_filter
+{
+ /**
+ * @see stream_filter_register()
+ */
+ public function onCreate()
+ {
+ $this->params->binary = false;
+ $this->params->literal = false;
+ // no_quote_list is used below as a config option
+ $this->params->quoted = false;
+
+ return true;
+ }
+
+ /**
+ * @see stream_filter_register()
+ */
+ public function filter($in, $out, &$consumed, $closing)
+ {
+ $p = $this->params;
+ $skip = false;
+
+ while ($bucket = stream_bucket_make_writeable($in)) {
+ if (!$skip) {
+ $len = $bucket->datalen;
+ $str = $bucket->data;
+
+ for ($i = 0; $i < $len; ++$i) {
+ $chr = ord($str[$i]);
+
+ switch ($chr) {
+ case 0: // null
+ $p->binary = true;
+ $p->literal = true;
+
+ // No need to scan input anymore.
+ $skip = true;
+ break 2;
+
+ case 10: // LF
+ case 13: // CR
+ $p->literal = true;
+ break;
+
+ case 32: // SPACE
+ case 34: // "
+ case 40: // (
+ case 41: // )
+ case 92: // \
+ case 123: // {
+ case 127: // DEL
+ // These are all invalid ATOM characters.
+ $p->quoted = true;
+ break;
+
+ case 37: // %
+ case 42: // *
+ // These are not quoted if being used as wildcards.
+ if (empty($p->no_quote_list)) {
+ $p->quoted = true;
+ }
+ break;
+
+ default:
+ if ($chr < 32) {
+ // CTL characters must be, at a minimum, quoted.
+ $p->quoted = true;
+ } elseif ($chr > 127) {
+ // 8-bit chars must be in a literal.
+ $p->literal = true;
+ }
+ break;
+ }
+ }
+ }
+
+ $consumed += $bucket->datalen;
+ stream_bucket_append($out, $bucket);
+ }
+
+ if ($p->literal) {
+ $p->quoted = false;
+ }
+
+ return PSFS_PASS_ON;
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientDataFormatListphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Format/List.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Format/List.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Format/List.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,105 @@
</span><ins>+<?php
+/**
+ * Copyright 2012-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * Object representation of an IMAP parenthesized list (RFC 3501 [4.4]).
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+class Horde_Imap_Client_Data_Format_List extends Horde_Imap_Client_Data_Format implements Countable, IteratorAggregate
+{
+ /**
+ * @see add()
+ */
+ public function __construct($data = null)
+ {
+ parent::__construct(array());
+
+ if (!is_null($data)) {
+ $this->add($data);
+ }
+ }
+
+ /**
+ * Add an element to the list.
+ *
+ * @param mixed $data The data element(s) to add. Either a
+ * Horde_Imap_Client_Data_Format object, a string
+ * value that will be treated as an IMAP atom, or
+ * an array (or iterable object) of objects to add.
+ * @param boolean $merge Merge the contents of any container objects,
+ * instead of adding the objects themselves?
+ *
+ * @return Horde_Imap_Client_Data_Format_List This object to allow for
+ * chainable calls (since
+ * 2.10.0).
+ */
+ public function add($data, $merge = false)
+ {
+ if (is_array($data) || ($merge && ($data instanceof Traversable))) {
+ foreach ($data as $val) {
+ $this->add($val);
+ }
+ } elseif (is_object($data)) {
+ $this->_data[] = $data;
+ } elseif (!is_null($data)) {
+ $this->_data[] = new Horde_Imap_Client_Data_Format_Atom($data);
+ }
+
+ return $this;
+ }
+
+ /**
+ */
+ public function __toString()
+ {
+ $out = '';
+
+ foreach ($this as $val) {
+ if ($val instanceof $this) {
+ $out .= '(' . $val->escape() . ') ';
+ } elseif (($val instanceof Horde_Imap_Client_Data_Format_String) &&
+ $val->literal()) {
+ throw new Horde_Imap_Client_Data_Format_Exception('Requires literal output.');
+ } else {
+ $out .= $val->escape() . ' ';
+ }
+ }
+
+ return rtrim($out);
+ }
+
+ /* Countable methods. */
+
+ /**
+ */
+ public function count()
+ {
+ return count($this->_data);
+ }
+
+ /* IteratorAggregate method. */
+
+ /**
+ * Iterator loops through the data elements contained in this list.
+ */
+ public function getIterator()
+ {
+ return new ArrayIterator($this->_data);
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientDataFormatListMailboxphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Format/ListMailbox.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Format/ListMailbox.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Format/ListMailbox.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,37 @@
</span><ins>+<?php
+/**
+ * Copyright 2012-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * Object representation of an IMAP mailbox string used in a LIST command.
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+class Horde_Imap_Client_Data_Format_ListMailbox extends Horde_Imap_Client_Data_Format_Mailbox
+{
+ /**
+ */
+ protected function _filterParams()
+ {
+ $ob = parent::_filterParams();
+
+ /* Don't quote % or * characters. */
+ $ob->no_quote_list = true;
+
+ return $ob;
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientDataFormatMailboxphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Format/Mailbox.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Format/Mailbox.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Format/Mailbox.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,91 @@
</span><ins>+<?php
+/**
+ * Copyright 2012-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * Object representation of an IMAP mailbox string (RFC 3501 [9]).
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+class Horde_Imap_Client_Data_Format_Mailbox extends Horde_Imap_Client_Data_Format_Astring
+{
+ /**
+ * Mailbox object.
+ *
+ * @var Horde_Imap_Client_Mailbox
+ */
+ protected $_mailbox;
+
+ /**
+ * @param mixed $data Either a mailbox object or a UTF-8 mailbox name.
+ */
+ public function __construct($data)
+ {
+ $this->_mailbox = Horde_Imap_Client_Mailbox::get($data);
+
+ parent::__construct($this->_mailbox->utf7imap);
+ }
+
+ /**
+ */
+ public function __toString()
+ {
+ return strval($this->_mailbox);
+ }
+
+ /**
+ */
+ public function getData()
+ {
+ return $this->_mailbox;
+ }
+
+ /**
+ * @throws Horde_Imap_Client_Exception
+ */
+ public function binary()
+ {
+ if (parent::binary()) {
+ // Mailbox data can NEVER be sent as binary.
+ /* @todo: Disable until Horde_Imap_Client 3.0 */
+ // throw new Horde_Imap_Client_Exception(
+ // 'Client error: can not send mailbox to IMAP server as binary data.'
+ // );
+
+ // Temporary fix: send a blank mailbox string.
+ $this->_mailbox = Horde_Imap_Client_Mailbox::get('');
+ }
+
+ return false;
+ }
+
+ /**
+ */
+ public function length()
+ {
+ return strlen($this->_mailbox->utf7imap);
+ }
+
+ /**
+ */
+ public function getStream()
+ {
+ $stream = new Horde_Stream_Temp();
+ $stream->add($this->_mailbox->utf7imap);
+ return $stream;
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientDataFormatNilphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Format/Nil.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Format/Nil.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Format/Nil.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,46 @@
</span><ins>+<?php
+/**
+ * Copyright 2012-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * Object representation of an IMAP NIL (RFC 3501 [4.5]).
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+class Horde_Imap_Client_Data_Format_Nil extends Horde_Imap_Client_Data_Format
+{
+ /**
+ */
+ public function __construct($data = null)
+ {
+ // Don't store any data in object.
+ }
+
+ /**
+ */
+ public function __toString()
+ {
+ return '';
+ }
+
+ /**
+ */
+ public function escape()
+ {
+ return 'NIL';
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientDataFormatNstringphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Format/Nstring.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Format/Nstring.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Format/Nstring.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,82 @@
</span><ins>+<?php
+/**
+ * Copyright 2012-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * Object representation of an IMAP nstring (NIL or string) (RFC 3501 [4.5]).
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+class Horde_Imap_Client_Data_Format_Nstring extends Horde_Imap_Client_Data_Format_String
+{
+ /**
+ */
+ public function __construct($data = null)
+ {
+ /* Data can be null (NIL) here. */
+ if (is_null($data)) {
+ $this->_data = null;
+ } else {
+ parent::__construct($data);
+ }
+ }
+
+ /**
+ */
+ public function __toString()
+ {
+ return is_null($this->_data)
+ ? ''
+ : parent::__toString();
+ }
+
+ /**
+ */
+ public function escape()
+ {
+ return is_null($this->_data)
+ ? 'NIL'
+ : parent::escape();
+ }
+
+ /**
+ */
+ public function quoted()
+ {
+ return is_null($this->_data)
+ ? false
+ : parent::quoted();
+ }
+
+ /**
+ */
+ public function length()
+ {
+ return is_null($this->_data)
+ ? 0
+ : parent::length();
+ }
+
+ /**
+ */
+ public function getStream()
+ {
+ return is_null($this->_data)
+ ? new Horde_Stream_Temp()
+ : parent::length();
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientDataFormatNumberphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Format/Number.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Format/Number.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Format/Number.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,41 @@
</span><ins>+<?php
+/**
+ * Copyright 2012-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * Object representation of an IMAP number (RFC 3501 [4.2]).
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+class Horde_Imap_Client_Data_Format_Number extends Horde_Imap_Client_Data_Format
+{
+ /**
+ */
+ public function __toString()
+ {
+ return strval(intval($this->_data));
+ }
+
+ /**
+ */
+ public function verify()
+ {
+ if (!is_numeric($this->_data)) {
+ throw new Horde_Imap_Client_Data_Format_Exception('Illegal character in IMAP number.');
+ }
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientDataFormatStringphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Format/String.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Format/String.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Format/String.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,209 @@
</span><ins>+<?php
+/**
+ * Copyright 2012-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * Object representation of an IMAP string (RFC 3501 [4.3]).
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+class Horde_Imap_Client_Data_Format_String extends Horde_Imap_Client_Data_Format
+{
+ /**
+ * String filter parameters.
+ *
+ * @var string
+ */
+ protected $_filter;
+
+ /**
+ * @param array $opts Additional options:
+ * - eol: (boolean) If true, normalize EOLs in input. @since 2.2.0
+ * - skipscan: (boolean) If true, don't scan input for
+ * binary/literal/quoted data. @since 2.2.0
+ */
+ public function __construct($data, array $opts = array())
+ {
+ /* String data is stored in a stream. */
+ $this->_data = new Horde_Stream_Temp();
+
+ $this->_filter = $this->_filterParams();
+
+ if (empty($opts['skipscan'])) {
+ stream_filter_register('horde_imap_client_string', 'Horde_Imap_Client_Data_Format_Filter_String');
+ $res = stream_filter_append($this->_data->stream, 'horde_imap_client_string', STREAM_FILTER_WRITE, $this->_filter);
+ } else {
+ $res = null;
+ }
+
+ if (empty($opts['eol'])) {
+ $res2 = null;
+ } else {
+ stream_filter_register('horde_eol', 'Horde_Stream_Filter_Eol');
+ $res2 = stream_filter_append($this->_data->stream, 'horde_eol', STREAM_FILTER_WRITE);
+ }
+
+ $this->_data->add($data);
+
+ if (!is_null($res)) {
+ stream_filter_remove($res);
+ }
+ if (!is_null($res2)) {
+ stream_filter_remove($res2);
+ }
+ }
+
+ /**
+ * Return the base string filter parameters.
+ *
+ * @return object Filter parameters.
+ */
+ protected function _filterParams()
+ {
+ return new stdClass;
+ }
+
+ /**
+ */
+ public function __toString()
+ {
+ return $this->_data->getString(0);
+ }
+
+ /**
+ */
+ public function escape()
+ {
+ if ($this->literal()) {
+ throw new Horde_Imap_Client_Data_Format_Exception('String requires literal to output.');
+ }
+
+ return $this->quoted()
+ ? stream_get_contents($this->escapeStream())
+ : $this->_data->getString(0);
+ }
+
+ /**
+ * Return the escaped string as a stream.
+ *
+ * @return resource The IMAP escaped stream.
+ */
+ public function escapeStream()
+ {
+ if ($this->literal()) {
+ throw new Horde_Imap_Client_Data_Format_Exception('String requires literal to output.');
+ }
+
+ rewind($this->_data->stream);
+
+ $stream = new Horde_Stream_Temp();
+ $stream->add($this->_data, true);
+
+ stream_filter_register('horde_imap_client_string_quote', 'Horde_Imap_Client_Data_Format_Filter_Quote');
+ stream_filter_append($stream->stream, 'horde_imap_client_string_quote', STREAM_FILTER_READ);
+
+ return $stream->stream;
+ }
+
+ /**
+ * Does this data item require quoted string output?
+ *
+ * @return boolean True if quoted output is required.
+ */
+ public function quoted()
+ {
+ /* IMAP strings MUST be quoted if they are not a literal. */
+ return (!isset($this->_filter) || !$this->_filter->literal);
+ }
+
+ /**
+ * Force item to be output quoted.
+ */
+ public function forceQuoted()
+ {
+ $this->_filter = $this->_filterParams();
+ $this->_filter->binary = false;
+ $this->_filter->literal = false;
+ $this->_filter->quoted = true;
+ }
+
+ /**
+ * Does this data item require literal string output?
+ *
+ * @return boolean True if literal output is required.
+ */
+ public function literal()
+ {
+ return (isset($this->_filter) && $this->_filter->literal);
+ }
+
+ /**
+ * Force item to be output as a literal.
+ */
+ public function forceLiteral()
+ {
+ $this->_filter = $this->_filterParams();
+ // Keep binary status, if set
+ $this->_filter->literal = true;
+ $this->_filter->quoted = false;
+ }
+
+ /**
+ * If literal output, is the data binary?
+ *
+ * @return boolean True if the literal output is binary.
+ */
+ public function binary()
+ {
+ return (isset($this->_filter) && !empty($this->_filter->binary));
+ }
+
+ /**
+ * Force item to be output as a binary literal.
+ */
+ public function forceBinary()
+ {
+ $this->_filter = $this->_filterParams();
+ $this->_filter->binary = true;
+ $this->_filter->literal = true;
+ $this->_filter->quoted = false;
+ }
+
+ /**
+ * Return the length of the data.
+ *
+ * @since 2.2.0
+ *
+ * @return integer Data length.
+ */
+ public function length()
+ {
+ return $this->_data->length();
+ }
+
+ /**
+ * Return the contents of the string as a stream object.
+ *
+ * @since 2.3.0
+ *
+ * @return Horde_Stream The stream object.
+ */
+ public function getStream()
+ {
+ return $this->_data;
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientDataFormatphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Format.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Format.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Format.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,83 @@
</span><ins>+<?php
+/**
+ * Copyright 2012-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * Object representation of an IMAP data format (RFC 3501 [4]).
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+class Horde_Imap_Client_Data_Format
+{
+ /**
+ * Data.
+ *
+ * @var mixed
+ */
+ protected $_data;
+
+ /**
+ * Constructor.
+ *
+ * @param mixed $data Data.
+ */
+ public function __construct($data)
+ {
+ $this->_data = is_resource($data)
+ ? stream_get_contents($data, -1, 0)
+ : $data;
+ }
+
+ /**
+ * Returns the string value of the raw data.
+ *
+ * @return string String value.
+ */
+ public function __toString()
+ {
+ return strval($this->_data);
+ }
+
+ /**
+ * Returns the raw data.
+ *
+ * @return mixed Raw data.
+ */
+ public function getData()
+ {
+ return $this->_data;
+ }
+
+ /**
+ * Returns the data formatted for output to the IMAP server.
+ *
+ * @return string IMAP escaped string.
+ */
+ public function escape()
+ {
+ return strval($this);
+ }
+
+ /**
+ * Verify the data.
+ *
+ * @throws Horde_Imap_Client_Data_Format_Exception
+ */
+ public function verify()
+ {
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientDataSyncphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Sync.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Sync.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Sync.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,268 @@
</span><ins>+<?php
+/**
+ * Copyright 2012-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * Mailbox synchronization results.
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ * @since 2.2.0
+ *
+ * @property-read Horde_Imap_Client_Ids $flagsuids List of messages with flag
+ * changes.
+ * @property-read Horde_Imap_Client_Ids $newmsgsuids List of new messages.
+ * @property-read Horde_Imap_Client_Ids $vanisheduids List of messages that
+ * have vanished.
+ */
+class Horde_Imap_Client_Data_Sync
+{
+ /**
+ * Mappings of status() values to sync keys.
+ *
+ * @since 2.8.0
+ *
+ * @var array
+ */
+ static public $map = array(
+ 'H' => 'highestmodseq',
+ 'M' => 'messages',
+ 'U' => 'uidnext',
+ 'V' => 'uidvalidity'
+ );
+
+ /**
+ * Are there messages that have had flag changes?
+ *
+ * @var Horde_Imap_Client_Ids
+ */
+ public $flags = null;
+
+ /**
+ * The previous value of HIGHESTMODSEQ.
+ *
+ * @since 2.8.0
+ *
+ * @var integer
+ */
+ public $highestmodseq = null;
+
+ /**
+ * The synchronized mailbox.
+ *
+ * @var Horde_Imap_Client_Mailbox
+ */
+ public $mailbox;
+
+ /**
+ * The previous number of messages in the mailbox.
+ *
+ * @since 2.8.0
+ *
+ * @var integer
+ */
+ public $messages = null;
+
+ /**
+ * Are there new messages?
+ *
+ * @var boolean
+ */
+ public $newmsgs = null;
+
+ /**
+ * The previous value of UIDNEXT.
+ *
+ * @since 2.8.0
+ *
+ * @var integer
+ */
+ public $uidnext = null;
+
+ /**
+ * The previous value of UIDVALIDITY.
+ *
+ * @since 2.8.0
+ *
+ * @var integer
+ */
+ public $uidvalidity = null;
+
+ /**
+ * The UIDs of messages that are guaranteed to have vanished. This list is
+ * only guaranteed to be available if the server supports QRESYNC or a
+ * list of known UIDs is passed to the sync() method.
+ *
+ * @var Horde_Imap_Client_Ids
+ */
+ public $vanished = null;
+
+ /**
+ * UIDs of messages that have had flag changes.
+ *
+ * @var Horde_Imap_Client_Ids
+ */
+ protected $_flagsuids;
+
+ /**
+ * UIDs of new messages.
+ *
+ * @var Horde_Imap_Client_Ids
+ */
+ protected $_newmsgsuids;
+
+ /**
+ * UIDs of messages that have vanished.
+ *
+ * @var Horde_Imap_Client_Ids
+ */
+ protected $_vanisheduids;
+
+ /**
+ * Constructor.
+ *
+ * @param Horde_Imap_Client_Base $base_ob Base driver object.
+ * @param mixed $mailbox Mailbox to sync.
+ * @param array $sync Token sync data.
+ * @param array $curr Current sync data.
+ * @param integer $criteria Mask of criteria to return.
+ * @param Horde_Imap_Client_Ids $ids List of known UIDs.
+ *
+ * @throws Horde_Imap_Client_Exception
+ * @throws Horde_Imap_Client_Exception_Sync
+ */
+ public function __construct(Horde_Imap_Client_Base $base_ob, $mailbox,
+ $sync, $curr, $criteria, $ids)
+ {
+ foreach (self::$map as $key => $val) {
+ if (isset($sync[$key])) {
+ $this->$val = $sync[$key];
+ }
+ }
+
+ /* Check uidvalidity. */
+ if (!$this->uidvalidity || ($curr['V'] != $this->uidvalidity)) {
+ throw new Horde_Imap_Client_Exception_Sync('UIDs in cached mailbox have changed.', Horde_Imap_Client_Exception_Sync::UIDVALIDITY_CHANGED);
+ }
+
+ $this->mailbox = $mailbox;
+
+ /* This was a UIDVALIDITY check only. */
+ if (!$criteria) {
+ return;
+ }
+
+ $sync_all = ($criteria & Horde_Imap_Client::SYNC_ALL);
+
+ /* New messages. */
+ if ($sync_all ||
+ ($criteria & Horde_Imap_Client::SYNC_NEWMSGS) ||
+ ($criteria & Horde_Imap_Client::SYNC_NEWMSGSUIDS)) {
+ $this->newmsgs = empty($this->uidnext)
+ ? !empty($curr['U'])
+ : (!empty($curr['U']) && ($curr['U'] > $this->uidnext));
+
+ if ($this->newmsgs &&
+ ($sync_all ||
+ ($criteria & Horde_Imap_Client::SYNC_NEWMSGSUIDS))) {
+ $new_ids = empty($this->uidnext)
+ ? Horde_Imap_Client_Ids::ALL
+ : ($this->uidnext . ':' . $curr['U']);
+
+ $squery = new Horde_Imap_Client_Search_Query();
+ $squery->ids($new_ids);
+ $sres = $base_ob->search($mailbox, $squery);
+
+ $this->_newmsgsuids = $sres['match'];
+ }
+ }
+
+ /* Do single status call to get all necessary data. */
+ if ($this->highestmodseq &&
+ ($sync_all ||
+ ($criteria & Horde_Imap_Client::SYNC_FLAGS) ||
+ ($criteria & Horde_Imap_Client::SYNC_FLAGSUIDS) ||
+ ($criteria & Horde_Imap_Client::SYNC_VANISHED) ||
+ ($criteria & Horde_Imap_Client::SYNC_VANISHEDUIDS))) {
+ $status_sync = $base_ob->status($mailbox, Horde_Imap_Client::STATUS_SYNCMODSEQ | Horde_Imap_Client::STATUS_SYNCFLAGUIDS | Horde_Imap_Client::STATUS_SYNCVANISHED);
+
+ if (!is_null($ids)) {
+ $ids = $base_ob->resolveIds($mailbox, $ids);
+ }
+ }
+
+ /* Flag changes. */
+ if ($sync_all || ($criteria & Horde_Imap_Client::SYNC_FLAGS)) {
+ $this->flags = $this->highestmodseq
+ ? ($this->highestmodseq != $curr['H'])
+ : true;
+ }
+
+ if ($sync_all || ($criteria & Horde_Imap_Client::SYNC_FLAGSUIDS)) {
+ if ($this->highestmodseq) {
+ if ($this->highestmodseq == $status_sync['syncmodseq']) {
+ $this->_flagsuids = is_null($ids)
+ ? $status_sync['syncflaguids']
+ : $base_ob->getIdsOb(array_intersect($ids->ids, $status_sync['syncflaguids']->ids));
+ } else {
+ $squery = new Horde_Imap_Client_Search_Query();
+ $squery->modseq($this->highestmodseq + 1);
+ $sres = $base_ob->search($mailbox, $squery, array(
+ 'ids' => $ids
+ ));
+ $this->_flagsuids = $sres['match'];
+ }
+ } else {
+ /* Without MODSEQ, need to mark all FLAGS as changed. */
+ $this->_flagsuids = $base_ob->resolveIds($mailbox, is_null($ids) ? $base_ob->getIdsOb(Horde_Imap_Client_Ids::ALL) : $ids);
+ }
+ }
+
+ /* Vanished messages. */
+ if ($sync_all ||
+ ($criteria & Horde_Imap_Client::SYNC_VANISHED) ||
+ ($criteria & Horde_Imap_Client::SYNC_VANISHEDUIDS)) {
+ if ($this->highestmodseq &&
+ ($this->highestmodseq == $status_sync['syncmodseq'])) {
+ $vanished = is_null($ids)
+ ? $status_sync['syncvanisheduids']
+ : $base_ob->getIdsOb(array_intersect($ids->ids, $status_sync['syncvanisheduids']->ids));
+ } else {
+ $vanished = $base_ob->vanished($mailbox, $this->highestmodseq ? $this->highestmodseq : 1, array(
+ 'ids' => $ids
+ ));
+ }
+
+ $this->vanished = (bool)count($vanished);
+ $this->_vanisheduids = $vanished;
+ }
+ }
+
+ /**
+ */
+ public function __get($name)
+ {
+ switch ($name) {
+ case 'flagsuids':
+ case 'newmsgsuids':
+ case 'vanisheduids':
+ $varname = '_' . $name;
+ return empty($this->$varname)
+ ? new Horde_Imap_Client_Ids()
+ : $this->$varname;
+ }
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientDataThreadphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Thread.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Thread.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Data/Thread.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,169 @@
</span><ins>+<?php
+/**
+ * Copyright 2008-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2008-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * Object representing the threaded sort results from
+ * Horde_Imap_Client_Base#thread().
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2008-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+class Horde_Imap_Client_Data_Thread implements Countable, Serializable
+{
+ /**
+ * Internal thread data structure. Keys are base values, values are arrays
+ * with keys as the ID and values as the level.
+ *
+ * @var array
+ */
+ protected $_thread = array();
+
+ /**
+ * The index type.
+ *
+ * @var string
+ */
+ protected $_type;
+
+ /**
+ * Constructor.
+ *
+ * @param array $data See $_thread.
+ * @param string $type Either 'sequence' or 'uid'.
+ */
+ public function __construct($data, $type)
+ {
+ $this->_thread = $data;
+ $this->_type = $type;
+ }
+
+ /**
+ * Return the ID type.
+ *
+ * @return string Either 'sequence' or 'uid'.
+ */
+ public function getType()
+ {
+ return $this->_type;
+ }
+
+ /**
+ * Return the sorted list of messages indices.
+ *
+ * @return Horde_Imap_Client_Ids The sorted list of messages.
+ */
+ public function messageList()
+ {
+ return new Horde_Imap_Client_Ids($this->_getAllIndices(), $this->getType() == 'sequence');
+ }
+
+ /**
+ * Returns the list of messages in a thread.
+ *
+ * @param integer $index An index contained in the thread.
+ *
+ * @return array Keys are indices, values are objects with the following
+ * properties:
+ * - base: (integer) Base ID of the thread. If null, thread is a single
+ * message.
+ * - last: (boolean) If true, this is the last index in the sublevel.
+ * - level: (integer) The sublevel of the index.
+ */
+ public function getThread($index)
+ {
+ reset($this->_thread);
+ while (list(,$v) = each($this->_thread)) {
+ if (isset($v[$index])) {
+ reset($v);
+
+ $ob = new stdClass;
+ $ob->base = (count($v) > 1) ? key($v) : null;
+ $ob->last = false;
+
+ $levels = $out = array();
+ $last = 0;
+
+ while (list($k2, $v2) = each($v)) {
+ $ob2 = clone $ob;
+ $ob2->level = $v2;
+ $out[$k2] = $ob2;
+
+ if (($last < $v2) && isset($levels[$v2])) {
+ $out[$levels[$v2]]->last = true;
+ }
+ $levels[$v2] = $k2;
+ $last = $v2;
+ }
+
+ foreach ($levels as $v) {
+ $out[$v]->last = true;
+ }
+
+ return $out;
+ }
+ }
+
+ return array();
+ }
+
+ /* Countable methods. */
+
+ /**
+ */
+ public function count()
+ {
+ return count($this->_getAllIndices());
+ }
+
+ /* Serializable methods. */
+
+ /**
+ */
+ public function serialize()
+ {
+ return json_encode(array(
+ $this->_thread,
+ $this->_type
+ ));
+ }
+
+ /**
+ */
+ public function unserialize($data)
+ {
+ list($this->_thread, $this->_type) = json_decode($data, true);
+ }
+
+ /* Protected methods. */
+
+ /**
+ * Return all indices.
+ *
+ * @return array An array of indices.
+ */
+ protected function _getAllIndices()
+ {
+ $out = array();
+
+ reset($this->_thread);
+ while (list(,$v) = each($this->_thread)) {
+ $out = array_merge($out, array_keys($v));
+ }
+
+ return $out;
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientDateTimephp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/DateTime.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/DateTime.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/DateTime.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,79 @@
</span><ins>+<?php
+/**
+ * Copyright 2012-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * A wrapper around PHP's native DateTime class that handles improperly
+ * formatted dates and adds a few features missing from the base object
+ * (string representation; doesn't fail on bad date input).
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+class Horde_Imap_Client_DateTime extends DateTime
+{
+ /**
+ */
+ public function __construct($time = null)
+ {
+ $tz = new DateTimeZone('UTC');
+
+ try {
+ parent::__construct($time, $tz);
+ return;
+ } catch (Exception $e) {}
+
+ /* Bug #5717 - Check for UT vs. UTC. */
+ if (substr(rtrim($time), -3) == ' UT') {
+ try {
+ parent::__construct($time . 'C', $tz);
+ return;
+ } catch (Exception $e) {}
+ }
+
+ /* Bug #9847 - Catch paranthesized timezone information at end of date
+ * string. */
+ $date = preg_replace("/\s*\([^\)]+\)\s*$/", '', $time, -1, $i);
+ if ($i) {
+ try {
+ parent::__construct($date, $tz);
+ return;
+ } catch (Exception $e) {}
+ }
+
+ parent::__construct('@-1', $tz);
+ }
+
+ /**
+ * String representation: UNIX timestamp.
+ */
+ public function __toString()
+ {
+ return $this->error()
+ ? '0'
+ : $this->format('U');
+ }
+
+ /**
+ * Was this an unparseable date?
+ *
+ * @return boolean True if unparseable.
+ */
+ public function error()
+ {
+ return ($this->format('U') == -1);
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientExceptionNoSupportExtensionphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Exception/NoSupportExtension.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Exception/NoSupportExtension.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Exception/NoSupportExtension.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,50 @@
</span><ins>+<?php
+/**
+ * Copyright 2012-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * Exception thrown for non-supported server extensions.
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+class Horde_Imap_Client_Exception_NoSupportExtension extends Horde_Imap_Client_Exception
+{
+ /**
+ * The extension not supported on the server.
+ *
+ * @var string
+ */
+ public $extension;
+
+ /**
+ * Constructor.
+ *
+ * @param string $extension The extension not supported on the server.
+ * @param string $msg A non-standard error message to use instead
+ * of the default.
+ */
+ public function __construct($extension, $msg = null)
+ {
+ $this->extension = $extension;
+
+ if (is_null($msg)) {
+ $msg = sprintf(Horde_Imap_Client_Translation::t("The server does not support the %s extension."), $extension);
+ }
+
+ parent::__construct($msg, self::NOT_SUPPORTED);
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientExceptionNoSupportPop3php"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Exception/NoSupportPop3.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Exception/NoSupportPop3.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Exception/NoSupportPop3.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,38 @@
</span><ins>+<?php
+/**
+ * Copyright 2012-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * Exception thrown for non-supported IMAP features on POP3 servers.
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+class Horde_Imap_Client_Exception_NoSupportPop3 extends Horde_Imap_Client_Exception
+{
+ /**
+ * Constructor.
+ *
+ * @param string $feature The feature not supported in POP3.
+ */
+ public function __construct($feature)
+ {
+ parent::__construct(
+ sprintf(Horde_Imap_Client_Translation::t("%s not supported on POP3 servers."), $feature),
+ self::NOT_SUPPORTED
+ );
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientExceptionSearchCharsetphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Exception/SearchCharset.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Exception/SearchCharset.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Exception/SearchCharset.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,49 @@
</span><ins>+<?php
+/**
+ * Copyright 2012-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * Exception thrown if search query text cannot be converted to different
+ * charset.
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+class Horde_Imap_Client_Exception_SearchCharset extends Horde_Imap_Client_Exception
+{
+ /**
+ * Charset that was attempted to be converted to.
+ *
+ * @var string
+ */
+ public $charset;
+
+ /**
+ * Constructor.
+ *
+ * @param string $charset The charset that was attempted to be converted
+ * to.
+ */
+ public function __construct($charset)
+ {
+ $this->charset = $charset;
+
+ parent::__construct(
+ Horde_Imap_Client_Translation::t("Cannot convert search query text to new charset"),
+ self::BADCHARSET
+ );
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientExceptionServerResponsephp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Exception/ServerResponse.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Exception/ServerResponse.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Exception/ServerResponse.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,88 @@
</span><ins>+<?php
+/**
+ * Copyright 2012-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * Exception thrown for server error responses.
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ *
+ * @property-read string $command The command that caused the BAD/NO error
+ * status.
+ * @property-read array $resp_data The response data array.
+ * @property-read integer $status Server error status.
+ */
+class Horde_Imap_Client_Exception_ServerResponse extends Horde_Imap_Client_Exception
+{
+ /**
+ * Pipeline object.
+ *
+ * @var Horde_Imap_Client_Interaction_Pipeline
+ */
+ protected $_pipeline;
+
+ /**
+ * Server response object.
+ *
+ * @var Horde_Imap_Client_Interaction_Server
+ */
+ protected $_server;
+
+ /**
+ * Constructor.
+ *
+ * @param string $msg Error message.
+ * @param integer $code Error code.
+ * @param Horde_Imap_Client_Interaction_Server $server Server ob.
+ * @param Horde_Imap_Client_Interaction_Pipeline $pipeline Pipeline ob.
+ */
+ public function __construct(
+ $msg = null,
+ $code = 0,
+ Horde_Imap_Client_Interaction_Server $server,
+ Horde_Imap_Client_Interaction_Pipeline $pipeline
+ )
+ {
+ $this->details = strval($server->token);
+
+ $this->_pipeline = $pipeline;
+ $this->_server = $server;
+
+ parent::__construct($msg, $code);
+ }
+
+ /**
+ */
+ public function __get($name)
+ {
+ switch ($name) {
+ case 'command':
+ return ($this->_server instanceof Horde_Imap_Client_Interaction_Server_Tagged)
+ ? $this->_pipeline->getCmd($this->_server->tag)->getCommand()
+ : null;
+
+ case 'resp_data':
+ return $this->_pipeline->data;
+
+ case 'status':
+ return $this->_server->status;
+
+ default:
+ return parent::__get($name);
+ }
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientExceptionSyncphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Exception/Sync.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Exception/Sync.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Exception/Sync.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,33 @@
</span><ins>+<?php
+/**
+ * Copyright 2012-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * Exception thrown for mailbox synchronization errors.
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+class Horde_Imap_Client_Exception_Sync extends Horde_Exception_Wrapped
+{
+ /* Error message codes. */
+
+ // Token could not be parsed.
+ const BAD_TOKEN = 1;
+
+ // UIDVALIDITY of the mailbox changed.
+ const UIDVALIDITY_CHANGED = 2;
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientExceptionphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Exception.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Exception.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Exception.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,184 @@
</span><ins>+<?php
+/**
+ * Copyright 2008-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2008-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * Exception handler for the Horde_Imap_Client package.
+ *
+ * Additional server debug information MAY be found in the $details
+ * property.
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2008-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+class Horde_Imap_Client_Exception extends Horde_Exception_Wrapped
+{
+ /* Error message codes. */
+
+ // Unspecified error (default)
+ const UNSPECIFIED = 0;
+
+ // There was an unrecoverable error in UTF7IMAP -> UTF8 conversion.
+ const UTF7IMAP_CONVERSION = 3;
+
+ // The server ended the connection.
+ const DISCONNECT = 4;
+
+ // The charset used in the search query is not supported on the server.
+ const BADCHARSET = 5;
+
+ // There were errors parsing the MIME/RFC 2822 header of the part.
+ const PARSEERROR = 6;
+
+ // The server could not decode the MIME part (see RFC 3516)
+ const UNKNOWNCTE = 7;
+
+ // The comparator specified by setComparator() was not recognized by the
+ // IMAP server
+ const BADCOMPARATOR = 9;
+
+ // RFC 4551 [3.1.2] - All mailboxes are not required to support
+ // mod-sequences.
+ const MBOXNOMODSEQ = 10;
+
+ // Thrown if server denies the network connection.
+ const SERVER_CONNECT = 11;
+
+ // Thrown if read error for server response.
+ const SERVER_READERROR = 12;
+
+ // Thrown if write error in server interaction.
+ const SERVER_WRITEERROR = 16;
+
+ // Thrown on CATENATE if the URL is invalid.
+ const CATENATE_BADURL = 13;
+
+ // Thrown on CATENATE if the message was too big.
+ const CATENATE_TOOBIG = 14;
+
+ // Thrown on CREATE if special-use attribute is not supported.
+ const USEATTR = 15;
+
+ // The user did not have permissions to carry out the operation.
+ const NOPERM = 17;
+
+ // The operation was not successful because another user is holding
+ // a necessary resource. The operation may succeed if attempted later.
+ const INUSE = 18;
+
+ // The operation failed because data on the server was corrupt.
+ const CORRUPTION = 19;
+
+ // The operation failed because it exceeded some limit on the server.
+ const LIMIT = 20;
+
+ // The operation failed because the user is over their quota.
+ const OVERQUOTA = 21;
+
+ // The operation failed because the requested creation object already
+ // exists.
+ const ALREADYEXISTS = 22;
+
+ // The operation failed because the requested deletion object did not
+ // exist.
+ const NONEXISTENT = 23;
+
+ // Setting metadata failed because the size of its value is too large.
+ // The maximum octet count the server is willing to accept will be
+ // in the exception message string.
+ const METADATA_MAXSIZE = 24;
+
+ // Setting metadata failed because the maximum number of allowed
+ // annotations has already been reached.
+ const METADATA_TOOMANY = 25;
+
+ // Setting metadata failed because the server does not support private
+ // annotations on one of the specified mailboxes.
+ const METADATA_NOPRIVATE = 26;
+
+ // Invalid metadata entry.
+ const METADATA_INVALID = 27;
+
+
+ // Login failures
+
+ // Could not start mandatory TLS connection.
+ const LOGIN_TLSFAILURE = 100;
+
+ // Could not find an available authentication method.
+ const LOGIN_NOAUTHMETHOD = 101;
+
+ // Generic authentication failure.
+ const LOGIN_AUTHENTICATIONFAILED = 102;
+
+ // Remote server is unavailable.
+ const LOGIN_UNAVAILABLE = 103;
+
+ // Authentication succeeded, but authorization failed.
+ const LOGIN_AUTHORIZATIONFAILED = 104;
+
+ // Authentication is no longer permitted with this passphrase.
+ const LOGIN_EXPIRED = 105;
+
+ // Login requires privacy.
+ const LOGIN_PRIVACYREQUIRED = 106;
+
+
+ // Mailbox access failures
+
+ // Could not open/access mailbox
+ const MAILBOX_NOOPEN = 200;
+
+ // Could not complete the command because the mailbox is read-only
+ const MAILBOX_READONLY = 201;
+
+
+ // POP3 specific error codes
+
+ // Temporary issue. Generally, there is no need to alarm the user for
+ // errors of this type.
+ const POP3_TEMP_ERROR = 300;
+
+ // Permanent error indicated by server.
+ const POP3_PERM_ERROR = 301;
+
+
+ // Unsupported feature error codes
+
+ // Function/feature is not supported on this server.
+ const NOT_SUPPORTED = 400;
+
+
+ /**
+ * Allow the error message to be altered.
+ *
+ * @param string $msg Error message.
+ */
+ public function setMessage($msg)
+ {
+ $this->message = strval($msg);
+ }
+
+ /**
+ * Allow the error code to be altered.
+ *
+ * @param integer $code Error code.
+ */
+ public function setCode($code)
+ {
+ $this->code = intval($code);
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientFetchQueryphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Fetch/Query.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Fetch/Query.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Fetch/Query.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,380 @@
</span><ins>+<?php
+/**
+ * Copyright 2011-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2011-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * Fetch query object for use with Horde_Imap_Client_Base#fetch().
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2011-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+class Horde_Imap_Client_Fetch_Query implements ArrayAccess, Countable, Iterator
+{
+ /**
+ * Internal data array.
+ *
+ * @var array
+ */
+ protected $_data = array();
+
+ /**
+ * Get the full text of the message.
+ *
+ * @param array $opts The following options are available:
+ * - length: (integer) The length of the substring to return.
+ * DEFAULT: The entire text is returned.
+ * - peek: (boolean) If set, does not set the '\Seen' flag on the
+ * message.
+ * DEFAULT: The seen flag is set.
+ * - start: (integer) If a portion of the full text is desired to be
+ * returned, the starting position is identified here.
+ * DEFAULT: The entire text is returned.
+ */
+ public function fullText(array $opts = array())
+ {
+ $this->_data[Horde_Imap_Client::FETCH_FULLMSG] = $opts;
+ }
+
+ /**
+ * Return header text.
+ *
+ * Header text is defined only for the base RFC 2822 message or
+ * message/rfc822 parts.
+ *
+ * @param array $opts The following options are available:
+ * - id: (string) The MIME ID to obtain the header text for.
+ * DEFAULT: The header text for the base message will be
+ * returned.
+ * - length: (integer) The length of the substring to return.
+ * DEFAULT: The entire text is returned.
+ * - peek: (boolean) If set, does not set the '\Seen' flag on the
+ * message.
+ * DEFAULT: The seen flag is set.
+ * - start: (integer) If a portion of the full text is desired to be
+ * returned, the starting position is identified here.
+ * DEFAULT: The entire text is returned.
+ */
+ public function headerText(array $opts = array())
+ {
+ $id = isset($opts['id'])
+ ? $opts['id']
+ : 0;
+ $this->_data[Horde_Imap_Client::FETCH_HEADERTEXT][$id] = $opts;
+ }
+
+ /**
+ * Return body text.
+ *
+ * Body text is defined only for the base RFC 2822 message or
+ * message/rfc822 parts.
+ *
+ * @param array $opts The following options are available:
+ * - id: (string) The MIME ID to obtain the body text for.
+ * DEFAULT: The body text for the entire message will be
+ * returned.
+ * - length: (integer) The length of the substring to return.
+ * DEFAULT: The entire text is returned.
+ * - peek: (boolean) If set, does not set the '\Seen' flag on the
+ * message.
+ * DEFAULT: The seen flag is set.
+ * - start: (integer) If a portion of the full text is desired to be
+ * returned, the starting position is identified here.
+ * DEFAULT: The entire text is returned.
+ */
+ public function bodyText(array $opts = array())
+ {
+ $id = isset($opts['id'])
+ ? $opts['id']
+ : 0;
+ $this->_data[Horde_Imap_Client::FETCH_BODYTEXT][$id] = $opts;
+ }
+
+ /**
+ * Return MIME header text.
+ *
+ * MIME header text is defined only for non-RFC 2822 messages and
+ * non-message/rfc822 parts.
+ *
+ * @param string $id The MIME ID to obtain the MIME header text for.
+ * @param array $opts The following options are available:
+ * - length: (integer) The length of the substring to return.
+ * DEFAULT: The entire text is returned.
+ * - peek: (boolean) If set, does not set the '\Seen' flag on the
+ * message.
+ * DEFAULT: The seen flag is set.
+ * - start: (integer) If a portion of the full text is desired to be
+ * returned, the starting position is identified here.
+ * DEFAULT: The entire text is returned.
+ */
+ public function mimeHeader($id, array $opts = array())
+ {
+ $this->_data[Horde_Imap_Client::FETCH_MIMEHEADER][$id] = $opts;
+ }
+
+ /**
+ * Return the body part data for a MIME ID.
+ *
+ * @param string $id The MIME ID to obtain the body part text for.
+ * @param array $opts The following options are available:
+ * - decode: (boolean) Attempt to server-side decode the bodypart data
+ * if it is MIME transfer encoded.
+ * DEFAULT: false
+ * - length: (integer) The length of the substring to return.
+ * DEFAULT: The entire text is returned.
+ * - peek: (boolean) If set, does not set the '\Seen' flag on the
+ * message.
+ * DEFAULT: The seen flag is set.
+ * - start: (integer) If a portion of the full text is desired to be
+ * returned, the starting position is identified here.
+ * DEFAULT: The entire text is returned.
+ */
+ public function bodyPart($id, array $opts = array())
+ {
+ $this->_data[Horde_Imap_Client::FETCH_BODYPART][$id] = $opts;
+ }
+
+ /**
+ * Returns the decoded body part size for a MIME ID.
+ *
+ * @param string $id The MIME ID to obtain the decoded body part size
+ * for.
+ */
+ public function bodyPartSize($id)
+ {
+ $this->_data[Horde_Imap_Client::FETCH_BODYPARTSIZE][$id] = true;
+ }
+
+ /**
+ * Returns RFC 2822 header text that matches a search string.
+ *
+ * This header search work only with the base RFC 2822 message or
+ * message/rfc822 parts.
+ *
+ * @param string $label A unique label associated with this particular
+ * search. This is how the results are stored.
+ * @param array $search The search string(s) (case-insensitive).
+ * @param array $opts The following options are available:
+ * - cache: (boolean) If true, and 'peek' is also true, will cache
+ * the result of this call.
+ * DEFAULT: false
+ * - id: (string) The MIME ID to search.
+ * DEFAULT: The base message part
+ * - length: (integer) The length of the substring to return.
+ * DEFAULT: The entire text is returned.
+ * - notsearch: (boolean) Do a 'NOT' search on the headers.
+ * DEFAULT: false
+ * - peek: (boolean) If set, does not set the '\Seen' flag on the
+ * message.
+ * DEFAULT: The seen flag is set.
+ * - start: (integer) If a portion of the full text is desired to be
+ * returned, the starting position is identified here.
+ * DEFAULT: The entire text is returned.
+ */
+ public function headers($label, $search, array $opts = array())
+ {
+ $this->_data[Horde_Imap_Client::FETCH_HEADERS][$label] = array_merge($opts, array(
+ 'headers' => $search
+ ));
+ }
+
+ /**
+ * Return MIME structure information.
+ */
+ public function structure()
+ {
+ $this->_data[Horde_Imap_Client::FETCH_STRUCTURE] = true;
+ }
+
+ /**
+ * Return envelope header data.
+ */
+ public function envelope()
+ {
+ $this->_data[Horde_Imap_Client::FETCH_ENVELOPE] = true;
+ }
+
+ /**
+ * Return flags set for the message.
+ */
+ public function flags()
+ {
+ $this->_data[Horde_Imap_Client::FETCH_FLAGS] = true;
+ }
+
+ /**
+ * Return the internal (IMAP) date of the message.
+ */
+ public function imapDate()
+ {
+ $this->_data[Horde_Imap_Client::FETCH_IMAPDATE] = true;
+ }
+
+ /**
+ * Return the size (in bytes) of the message.
+ */
+ public function size()
+ {
+ $this->_data[Horde_Imap_Client::FETCH_SIZE] = true;
+ }
+
+ /**
+ * Return the unique ID of the message.
+ */
+ public function uid()
+ {
+ $this->_data[Horde_Imap_Client::FETCH_UID] = true;
+ }
+
+ /**
+ * Return the sequence number of the message.
+ */
+ public function seq()
+ {
+ $this->_data[Horde_Imap_Client::FETCH_SEQ] = true;
+ }
+
+ /**
+ * Return the mod-sequence value for the message.
+ *
+ * The server must support the CONDSTORE IMAP extension, and the mailbox
+ * must support mod-sequences.
+ */
+ public function modseq()
+ {
+ $this->_data[Horde_Imap_Client::FETCH_MODSEQ] = true;
+ }
+
+ /**
+ * Does the query contain the given criteria?
+ *
+ * @param integer $criteria The criteria to remove.
+ *
+ * @return boolean True if the query contains the given criteria.
+ */
+ public function contains($criteria)
+ {
+ return isset($this->_data[$criteria]);
+ }
+
+ /**
+ * Remove an entry under a given criteria.
+ *
+ * @param integer $criteria Criteria ID.
+ * @param string $key The key to remove.
+ */
+ public function remove($criteria, $key)
+ {
+ if (isset($this->_data[$criteria]) &&
+ is_array($this->_data[$criteria])) {
+ unset($this->_data[$criteria][$key]);
+ if (empty($this->_data[$criteria])) {
+ unset($this->_data[$criteria]);
+ }
+ }
+ }
+
+ /**
+ * Returns a MD5 hash of the current query object.
+ *
+ * @return string MD5 hash.
+ */
+ public function hash()
+ {
+ return hash('md5', serialize($this));
+ }
+
+ /* ArrayAccess methods. */
+
+ /**
+ */
+ public function offsetExists($offset)
+ {
+ return isset($this->_data[$offset]);
+ }
+
+ /**
+ */
+ public function offsetGet($offset)
+ {
+ return isset($this->_data[$offset])
+ ? $this->_data[$offset]
+ : null;
+ }
+
+ /**
+ */
+ public function offsetSet($offset, $value)
+ {
+ $this->_data[$offset] = $value;
+ }
+
+ /**
+ */
+ public function offsetUnset($offset)
+ {
+ unset($this->_data[$offset]);
+ }
+
+ /* Countable methods. */
+
+ /**
+ */
+ public function count()
+ {
+ return count($this->_data);
+ }
+
+ /* Iterator methods. */
+
+ /**
+ */
+ public function current()
+ {
+ $opts = current($this->_data);
+
+ return (!empty($opts) && ($this->key() == Horde_Imap_Client::FETCH_BODYPARTSIZE))
+ ? array_keys($opts)
+ : $opts;
+ }
+
+ /**
+ */
+ public function key()
+ {
+ return key($this->_data);
+ }
+
+ /**
+ */
+ public function next()
+ {
+ next($this->_data);
+ }
+
+ /**
+ */
+ public function rewind()
+ {
+ reset($this->_data);
+ }
+
+ /**
+ */
+ public function valid()
+ {
+ return !is_null($this->key());
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientFetchResultsphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Fetch/Results.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Fetch/Results.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Fetch/Results.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,179 @@
</span><ins>+<?php
+/**
+ * Copyright 2012-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * Fetch results object for use with Horde_Imap_Client_Base#fetch().
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ *
+ * @property-read integer $key_type The key type (sequence or UID).
+ */
+class Horde_Imap_Client_Fetch_Results implements ArrayAccess, Countable, IteratorAggregate
+{
+ /* Key type constants. */
+ const SEQUENCE = 1;
+ const UID = 2;
+
+ /**
+ * Internal data array.
+ *
+ * @var array
+ */
+ protected $_data = array();
+
+ /**
+ * Key type.
+ *
+ * @var integer
+ */
+ protected $_keyType;
+
+ /**
+ * Class to use when creating a new fetch object.
+ *
+ * @var string
+ */
+ protected $_obClass;
+
+ /**
+ * Constructor.
+ *
+ * @param string $ob_class Class to use when creating a new fetch
+ * object.
+ * @param integer $key_type Key type.
+ */
+ public function __construct($ob_class = 'Horde_Imap_Client_Data_Fetch',
+ $key_type = self::UID)
+ {
+ $this->_obClass = $ob_class;
+ $this->_keyType = $key_type;
+ }
+
+ /**
+ */
+ public function __get($name)
+ {
+ switch ($name) {
+ case 'key_type':
+ return $this->_keyType;
+ }
+ }
+
+ /**
+ * Return a fetch object, creating and storing an empty object in the
+ * results set if it doesn't currently exist.
+ *
+ * @param string $key The key to retrieve.
+ *
+ * @return Horde_Imap_Client_Data_Fetch The fetch object.
+ */
+ public function get($key)
+ {
+ if (!isset($this->_data[$key])) {
+ $this->_data[$key] = new $this->_obClass();
+ }
+
+ return $this->_data[$key];
+ }
+
+ /**
+ * Return the list of IDs.
+ *
+ * @return array ID list.
+ */
+ public function ids()
+ {
+ ksort($this->_data);
+ return array_keys($this->_data);
+ }
+
+ /**
+ * Return the first fetch object in the results, if there is only one
+ * object.
+ *
+ * @return null|Horde_Imap_Client_Data_Fetch The fetch object if there is
+ * only one object, or null.
+ */
+ public function first()
+ {
+ return (count($this->_data) == 1)
+ ? reset($this->_data)
+ : null;
+ }
+
+ /**
+ * Clears all fetch results.
+ *
+ * @since 2.6.0
+ */
+ public function clear()
+ {
+ $this->_data = array();
+ }
+
+ /* ArrayAccess methods. */
+
+ /**
+ */
+ public function offsetExists($offset)
+ {
+ return isset($this->_data[$offset]);
+ }
+
+ /**
+ */
+ public function offsetGet($offset)
+ {
+ return isset($this->_data[$offset])
+ ? $this->_data[$offset]
+ : null;
+ }
+
+ /**
+ */
+ public function offsetSet($offset, $value)
+ {
+ $this->_data[$offset] = $value;
+ }
+
+ /**
+ */
+ public function offsetUnset($offset)
+ {
+ unset($this->_data[$offset]);
+ }
+
+ /* Countable methods. */
+
+ /**
+ */
+ public function count()
+ {
+ return count($this->_data);
+ }
+
+ /* IteratorAggregate methods. */
+
+ /**
+ */
+ public function getIterator()
+ {
+ ksort($this->_data);
+ return new ArrayIterator($this->_data);
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientIdsMapphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Ids/Map.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Ids/Map.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Ids/Map.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,236 @@
</span><ins>+<?php
+/**
+ * Copyright 2012-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * An object implementing lookups between UIDs and message sequence numbers.
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ * @since 2.1.0
+ *
+ * @property-read array $map The raw ID mapping data.
+ * @property-read Horde_Imap_Client_Ids $seq The sorted sequence values.
+ * @property-read Horde_Imap_Client_Ids $uids The sorted UIDs.
+ */
+class Horde_Imap_Client_Ids_Map implements Countable, IteratorAggregate, Serializable
+{
+ /**
+ * Sequence -> UID mapping.
+ *
+ * @var array
+ */
+ protected $_ids = array();
+
+ /**
+ * Is the array sorted?
+ *
+ * @var boolean
+ */
+ protected $_sorted = true;
+
+ /**
+ * Constructor.
+ *
+ * @param array $ids Array of sequence -> UID mapping.
+ */
+ public function __construct(array $ids = array())
+ {
+ $this->update($ids);
+ }
+
+ /**
+ */
+ public function __get($name)
+ {
+ switch ($name) {
+ case 'map':
+ return $this->_ids;
+
+ case 'seq':
+ $this->sort();
+ return new Horde_Imap_Client_Ids(array_keys($this->_ids), true);
+
+ case 'uids':
+ $this->sort();
+ return new Horde_Imap_Client_Ids($this->_ids);
+ }
+ }
+
+ /**
+ * Updates the mapping.
+ *
+ * @param array $ids Array of sequence -> UID mapping.
+ *
+ * @return boolean True if the mapping changed.
+ */
+ public function update($ids)
+ {
+ if (empty($ids)) {
+ return false;
+ } elseif (empty($this->_ids)) {
+ $this->_ids = $ids;
+ $change = true;
+ } else {
+ $change = false;
+ foreach ($ids as $k => $v) {
+ if (!isset($this->_ids[$k]) || ($this->_ids[$k] != $v)) {
+ $this->_ids[$k] = $v;
+ $change = true;
+ }
+ }
+ }
+
+ if ($change) {
+ $this->_sorted = false;
+ }
+
+ return $change;
+ }
+
+ /**
+ * Create a Sequence <-> UID lookup table.
+ *
+ * @param Horde_Imap_Client_Ids $ids IDs to lookup.
+ *
+ * @return array Keys are sequence numbers, values are UIDs.
+ */
+ public function lookup(Horde_Imap_Client_Ids $ids)
+ {
+ if ($ids->all) {
+ return $this->_ids;
+ } elseif ($ids->sequence) {
+ return array_intersect_key($this->_ids, array_flip($ids->ids));
+ }
+
+ return array_intersect($this->_ids, $ids->ids);
+ }
+
+ /**
+ * Removes messages from the ID mapping.
+ *
+ * @param Horde_Imap_Client_Ids $ids IDs to remove.
+ */
+ public function remove(Horde_Imap_Client_Ids $ids)
+ {
+ /* For sequence numbers, we need to reindex anytime we have an index
+ * that appears equal to or after a previously seen index. If an IMAP
+ * server is smart, it will expunge in reverse order instead. */
+ if ($ids->sequence) {
+ $remove = $ids->ids;
+ } else {
+ $ids->sort();
+ $remove = array_reverse(array_keys($this->lookup($ids)));
+ }
+
+ if (empty($remove)) {
+ return;
+ }
+
+ $this->sort();
+
+ /* Find the minimum sequence number to remove. We know entries before
+ * this are untouched so no need to process them multiple times. */
+ $first = min($remove);
+ $edit = $newids = array();
+ foreach (array_keys($this->_ids) as $i => $seq) {
+ if ($seq >= $first) {
+ $i += (($seq == $first) ? 0 : 1);
+ $newids = array_slice($this->_ids, 0, $i, true);
+ $edit = array_slice($this->_ids, $i + (($seq == $first) ? 0 : 1), null, true);
+ break;
+ }
+ }
+
+ if (!empty($edit)) {
+ foreach ($remove as $val) {
+ $found = false;
+ $tmp = array();
+
+ foreach (array_keys($edit) as $i => $seq) {
+ if ($found) {
+ $tmp[$seq - 1] = $edit[$seq];
+ } elseif ($seq >= $val) {
+ $tmp = array_slice($edit, 0, ($seq == $val) ? $i : $i + 1, true);
+ $found = true;
+ }
+ }
+
+ $edit = $tmp;
+ }
+ }
+
+ $this->_ids = $newids + $edit;
+ }
+
+ /**
+ * Sort the map.
+ */
+ public function sort()
+ {
+ if (!$this->_sorted) {
+ ksort($this->_ids, SORT_NUMERIC);
+ $this->_sorted = true;
+ }
+ }
+
+ /* Countable methods. */
+
+ /**
+ */
+ public function count()
+ {
+ return count($this->_ids);
+ }
+
+ /* IteratorAggregate method. */
+
+ /**
+ */
+ public function getIterator()
+ {
+ return new ArrayIterator($this->_ids);
+ }
+
+ /* Serializable methods. */
+
+ /**
+ */
+ public function serialize()
+ {
+ /* Sort before storing; provides more compressible representation. */
+ $this->sort();
+
+ return json_encode(array(
+ strval(new Horde_Imap_Client_Ids(array_keys($this->_ids))),
+ strval(new Horde_Imap_Client_Ids(array_values($this->_ids)))
+ ));
+ }
+
+ /**
+ */
+ public function unserialize($data)
+ {
+ $data = json_decode($data, true);
+
+ $keys = new Horde_Imap_Client_Ids($data[0]);
+ $vals = new Horde_Imap_Client_Ids($data[1]);
+ $this->_ids = array_combine($keys->ids, $vals->ids);
+
+ /* Guaranteed to be sorted if unserializing. */
+ $this->_sorted = true;
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientIdsPop3php"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Ids/Pop3.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Ids/Pop3.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Ids/Pop3.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,53 @@
</span><ins>+<?php
+/**
+ * Copyright 2011-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2011-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * Wrapper around Ids object that correctly handles POP3 UID strings.
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2011-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+class Horde_Imap_Client_Ids_Pop3 extends Horde_Imap_Client_Ids
+{
+ /**
+ * Create a POP3 message sequence string.
+ *
+ * Index Format: UID1[SPACE]UID2...
+ *
+ * @param boolean $sort Not used in this class.
+ *
+ * @return string The POP3 message sequence string.
+ */
+ protected function _toSequenceString($sort = true)
+ {
+ /* Use space as delimiter as it is the only printable ASCII character
+ * that is not allowed as part of the UID (RFC 1939 [7]). */
+ return implode(' ', count($this->_ids) > 25000 ? array_unique($this->_ids) : array_keys(array_flip($this->_ids)));
+ }
+
+ /**
+ * Parse a POP3 message sequence string into a list of indices.
+ *
+ * @param string $str The POP3 message sequence string.
+ *
+ * @return array An array of UIDs.
+ */
+ protected function _fromSequenceString($str)
+ {
+ return explode(' ', trim($str));
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientIdsphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Ids.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Ids.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Ids.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,449 @@
</span><ins>+<?php
+/**
+ * Copyright 2011-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2011-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * An object that provides a way to identify a list of IMAP indices.
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2011-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ *
+ * @property-read boolean $all Does this represent an ALL message set?
+ * @property-read array $ids The list of IDs.
+ * @property-read boolean $largest Does this represent the largest ID in use?
+ * @property-read string $range_string Generates a range string consisting of
+ * all messages between begin and end of
+ * ID list.
+ * @property-read boolean $search_res Does this represent a search result?
+ * @property-read boolean $sequence Are these sequence IDs? If false, these
+ * are UIDs.
+ * @property-read boolean $special True if this is a "special" ID
+ * representation.
+ * @property-read string $tostring Return the non-sorted string
+ * representation.
+ * @property-read string $tostring_sort Return the sorted string
+ * representation.
+ */
+class Horde_Imap_Client_Ids implements Countable, Iterator, Serializable
+{
+ /* "Special" representation constants. */
+ const ALL = "\01";
+ const SEARCH_RES = "\02";
+ const LARGEST = "\03";
+
+ /**
+ * Allow duplicate IDs?
+ *
+ * @var boolean
+ */
+ public $duplicates = false;
+
+ /**
+ * List of IDs.
+ *
+ * @var mixed
+ */
+ protected $_ids = array();
+
+ /**
+ * Are IDs message sequence numbers?
+ *
+ * @var boolean
+ */
+ protected $_sequence = false;
+
+ /**
+ * Are IDs sorted?
+ *
+ * @var boolean
+ */
+ protected $_sorted = false;
+
+ /**
+ * Constructor.
+ *
+ * @param mixed $ids See self::add().
+ * @param boolean $sequence Are $ids message sequence numbers?
+ */
+ public function __construct($ids = null, $sequence = false)
+ {
+ $this->add($ids);
+ $this->_sequence = $sequence;
+ }
+
+ /**
+ */
+ public function __get($name)
+ {
+ switch ($name) {
+ case 'all':
+ return ($this->_ids === self::ALL);
+
+ case 'ids':
+ return is_array($this->_ids)
+ ? $this->_ids
+ : array();
+
+ case 'largest':
+ return ($this->_ids === self::LARGEST);
+
+ case 'range_string':
+ if (!count($this)) {
+ return '';
+ }
+
+ $this->sort();
+ $min = reset($this->_ids);
+ $max = end($this->_ids);
+
+ return ($min == $max)
+ ? $min
+ : $min . ':' . $max;
+
+ case 'search_res':
+ return ($this->_ids === self::SEARCH_RES);
+
+ case 'sequence':
+ return (bool)$this->_sequence;
+
+ case 'special':
+ return is_string($this->_ids);
+
+ case 'tostring':
+ case 'tostring_sort':
+ if ($this->all) {
+ return '1:*';
+ } elseif ($this->largest) {
+ return '*';
+ } elseif ($this->search_res) {
+ return '$';
+ }
+ return strval($this->_toSequenceString($name == 'tostring_sort'));
+ }
+ }
+
+ /**
+ */
+ public function __toString()
+ {
+ return $this->tostring;
+ }
+
+ /**
+ * Add IDs to the current object.
+ *
+ * @param mixed $ids Either self::ALL, self::SEARCH_RES, self::LARGEST,
+ * Horde_Imap_Client_Ids object, array, or sequence
+ * string.
+ */
+ public function add($ids)
+ {
+ if (!is_null($ids)) {
+ $add = array();
+
+ if (is_string($ids) &&
+ in_array($ids, array(self::ALL, self::SEARCH_RES, self::LARGEST))) {
+ $this->_ids = $ids;
+ $this->_sorted = false;
+ return;
+ }
+
+ if ($ids instanceof Horde_Imap_Client_Ids) {
+ $add = $ids->ids;
+ } elseif (is_array($ids)) {
+ $add = $ids;
+ } elseif (is_string($ids) || is_integer($ids)) {
+ if (is_numeric($ids)) {
+ $add = array($ids);
+ } else {
+ $add = $this->_fromSequenceString($ids);
+ }
+ }
+
+ if (!empty($add)) {
+ $this->_ids = is_array($this->_ids)
+ ? array_merge($this->_ids, $add)
+ : $add;
+ if (!$this->duplicates) {
+ $this->_ids = (count($this->_ids) > 25000)
+ ? array_unique($this->_ids)
+ : array_keys(array_flip($this->_ids));
+ }
+ $this->_sorted = false;
+ }
+ }
+ }
+
+ /**
+ * Is this object empty (i.e. does not contain IDs)?
+ *
+ * @return boolean True if object is empty.
+ */
+ public function isEmpty()
+ {
+ return (is_array($this->_ids) && !count($this->_ids));
+ }
+
+ /**
+ * Reverses the order of the IDs.
+ */
+ public function reverse()
+ {
+ if (is_array($this->_ids)) {
+ $this->_ids = array_reverse($this->_ids);
+ }
+ }
+
+ /**
+ * Sorts the IDs numerically.
+ */
+ public function sort()
+ {
+ if (!$this->_sorted && is_array($this->_ids)) {
+ sort($this->_ids, SORT_NUMERIC);
+ $this->_sorted = true;
+ }
+ }
+
+ /**
+ * Split the sequence string at an approximate length.
+ *
+ * @since 2.7.0
+ *
+ * @param integer $length Length to split.
+ *
+ * @return array A list containing individual sequence strings.
+ */
+ public function split($length)
+ {
+ $id = new Horde_Stream_Temp();
+ $id->add($this->tostring_sort, true);
+
+ $out = array();
+
+ do {
+ $out[] = stream_get_contents($id->stream, $length) . $id->getToChar(',');
+ } while (!feof($id->stream));
+
+ return $out;
+ }
+
+ /**
+ * Create an IMAP message sequence string from a list of indices.
+ *
+ * Index Format: range_start:range_end,uid,uid2,...
+ *
+ * @param boolean $sort Numerically sort the IDs before creating the
+ * range?
+ *
+ * @return string The IMAP message sequence string.
+ */
+ protected function _toSequenceString($sort = true)
+ {
+ if (empty($this->_ids)) {
+ return '';
+ }
+
+ $in = $this->_ids;
+
+ if ($sort) {
+ sort($in, SORT_NUMERIC);
+ }
+
+ $first = $last = array_shift($in);
+ $i = count($in) - 1;
+ $out = array();
+
+ reset($in);
+ while (list($key, $val) = each($in)) {
+ if (($last + 1) == $val) {
+ $last = $val;
+ }
+
+ if (($i == $key) || ($last != $val)) {
+ if ($last == $first) {
+ $out[] = $first;
+ if ($i == $key) {
+ $out[] = $val;
+ }
+ } else {
+ $out[] = $first . ':' . $last;
+ if (($i == $key) && ($last != $val)) {
+ $out[] = $val;
+ }
+ }
+ $first = $last = $val;
+ }
+ }
+
+ return empty($out)
+ ? $first
+ : implode(',', $out);
+ }
+
+ /**
+ * Parse an IMAP message sequence string into a list of indices.
+ *
+ * @see _toSequenceString()
+ *
+ * @param string $str The IMAP message sequence string.
+ *
+ * @return array An array of indices.
+ */
+ protected function _fromSequenceString($str)
+ {
+ $ids = array();
+ $str = trim($str);
+
+ if (!strlen($str)) {
+ return $ids;
+ }
+
+ $idarray = explode(',', $str);
+
+ reset($idarray);
+ while (list(,$val) = each($idarray)) {
+ $range = explode(':', $val);
+ if (isset($range[1])) {
+ for ($i = min($range), $j = max($range); $i <= $j; ++$i) {
+ $ids[] = $i;
+ }
+ } else {
+ $ids[] = $val;
+ }
+ }
+
+ return $ids;
+ }
+
+ /* Countable methods. */
+
+ /**
+ */
+ public function count()
+ {
+ return is_array($this->_ids)
+ ? count($this->_ids)
+ : 0;
+ }
+
+ /* Iterator methods. */
+
+ /**
+ */
+ public function current()
+ {
+ return is_array($this->_ids)
+ ? current($this->_ids)
+ : null;
+ }
+
+ /**
+ */
+ public function key()
+ {
+ return is_array($this->_ids)
+ ? key($this->_ids)
+ : null;
+ }
+
+ /**
+ */
+ public function next()
+ {
+ if (is_array($this->_ids)) {
+ next($this->_ids);
+ }
+ }
+
+ /**
+ */
+ public function rewind()
+ {
+ if (is_array($this->_ids)) {
+ reset($this->_ids);
+ }
+ }
+
+ /**
+ */
+ public function valid()
+ {
+ return !is_null($this->key());
+ }
+
+ /* Serializable methods. */
+
+ /**
+ */
+ public function serialize()
+ {
+ $save = array();
+
+ if ($this->duplicates) {
+ $save['d'] = 1;
+ }
+
+ if ($this->_sequence) {
+ $save['s'] = 1;
+ }
+
+ if ($this->_sorted) {
+ $save['is'] = 1;
+ }
+
+ switch ($this->_ids) {
+ case self::ALL:
+ $save['a'] = true;
+ break;
+
+ case self::LARGEST:
+ $save['l'] = true;
+ break;
+
+ case self::SEARCH_RES:
+ $save['sr'] = true;
+ break;
+
+ default:
+ $save['i'] = strval($this);
+ break;
+ }
+
+ return serialize($save);
+ }
+
+ /**
+ */
+ public function unserialize($data)
+ {
+ $save = @unserialize($data);
+
+ $this->duplicates = !empty($save['d']);
+ $this->_sequence = !empty($save['s']);
+ $this->_sorted = !empty($save['is']);
+
+ if (isset($save['a'])) {
+ $this->_ids = self::ALL;
+ } elseif (isset($save['l'])) {
+ $this->_ids = self::LARGEST;
+ } elseif (isset($save['sr'])) {
+ $this->_ids = self::SEARCH_RES;
+ } elseif (isset($save['i'])) {
+ $this->add($save['i']);
+ }
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientInteractionClientphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Interaction/Client.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Interaction/Client.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Interaction/Client.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,61 @@
</span><ins>+<?php
+/**
+ * Copyright 2012-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * An object representing an IMAP client command interaction (RFC 3501
+ * [2.2.1]).
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @deprecated
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+class Horde_Imap_Client_Interaction_Client extends Horde_Imap_Client_Data_Format_List
+{
+ /**
+ * The command tag.
+ *
+ * @var string
+ */
+ public $tag;
+
+ /**
+ * Constructor.
+ *
+ * @param string $tag The tag to use. If not set, will be automatically
+ * generated.
+ */
+ public function __construct($tag = null)
+ {
+ $this->tag = is_null($tag)
+ ? substr(strval(new Horde_Support_Randomid()), 0, 10)
+ : strval($tag);
+
+ parent::__construct($this->tag);
+ }
+
+ /**
+ * Get the command.
+ *
+ * @return string The command.
+ */
+ public function getCommand()
+ {
+ return isset($this->_data[1])
+ ? $this->_data[1]
+ : null;
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientInteractionCommandContinuationphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Interaction/Command/Continuation.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Interaction/Command/Continuation.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Interaction/Command/Continuation.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,66 @@
</span><ins>+<?php
+/**
+ * Copyright 2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * An object representing a portion of an IMAP command that requires data
+ * sent in a continuation response (RFC 3501 [2.2.1]).
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ * @since 2.10.0
+ */
+class Horde_Imap_Client_Interaction_Command_Continuation
+{
+ /**
+ * Closure function to run after continuation response.
+ *
+ * @var Closure
+ */
+ protected $_closure;
+
+ /**
+ * Constructor.
+ *
+ * @param Closure $closure A function to run after the continuation
+ * response is received. It receives one
+ * argument - a Continuation object - and should
+ * return a list of arguments to send to the
+ * server (via a
+ * Horde_Imap_Client_Data_Format_List object).
+ */
+ public function __construct($closure)
+ {
+ $this->_closure = $closure;
+ }
+
+ /**
+ * Calls the closure object.
+ *
+ * @param Horde_Imap_Client_Interaction_Server_Continuation $ob Continuation
+ * object.
+ *
+ * @return Horde_Imap_Client_Data_Format_List Further commands to issue
+ * to the server.
+ */
+ public function getCommands(
+ Horde_Imap_Client_Interaction_Server_Continuation $ob
+ )
+ {
+ $closure = $this->_closure;
+ return $closure($ob);
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientInteractionCommandphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Interaction/Command.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Interaction/Command.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Interaction/Command.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,110 @@
</span><ins>+<?php
+/**
+ * Copyright 2012-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * An object representing an IMAP command (RFC 3501 [2.2.1]).
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ * @since 2.10.0
+ *
+ * @property-read boolean $continuation True if the command requires a server
+ * continuation response.
+ */
+class Horde_Imap_Client_Interaction_Command extends Horde_Imap_Client_Data_Format_List
+{
+ /**
+ * Debug string to use instead of command text.
+ *
+ * @var string
+ */
+ public $debug = null;
+
+ /**
+ * Use LITERAL+ if available
+ *
+ * @var boolean
+ */
+ public $literalplus = true;
+
+ /**
+ * Are literal8's available?
+ *
+ * @var boolean
+ */
+ public $literal8 = false;
+
+ /**
+ * Server response.
+ *
+ * @var Horde_Imap_Client_Interaction_Server
+ */
+ public $response;
+
+ /**
+ * The command tag.
+ *
+ * @var string
+ */
+ public $tag;
+
+ /**
+ * Constructor.
+ *
+ * @param string $cmd The IMAP command.
+ * @param string $tag The tag to use. If not set, will be automatically
+ * generated.
+ */
+ public function __construct($cmd, $tag = null)
+ {
+ $this->tag = is_null($tag)
+ ? substr(new Horde_Support_Randomid(), 0, 10)
+ : strval($tag);
+
+ parent::__construct($this->tag);
+
+ $this->add($cmd);
+ }
+
+ /**
+ */
+ public function __get($name)
+ {
+ switch ($name) {
+ case 'continuation':
+ foreach ($this as $val) {
+ if (($val instanceof Horde_Imap_Client_Interaction_Command_Continuation) ||
+ (($val instanceof Horde_Imap_Client_Data_Format_String) &&
+ $val->literal())) {
+
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Get the command.
+ *
+ * @return string The command.
+ */
+ public function getCommand()
+ {
+ return $this->_data[1];
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientInteractionPipelinephp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Interaction/Pipeline.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Interaction/Pipeline.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Interaction/Pipeline.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,145 @@
</span><ins>+<?php
+/**
+ * Copyright 2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * An object representing a series of IMAP client commands (RFC 3501 [2.2.1])
+ * to be processed at the same time.
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ * @since 2.10.0
+ *
+ * @property-read boolean $finished True if all commands have finished.
+ */
+class Horde_Imap_Client_Interaction_Pipeline implements Countable, IteratorAggregate
+{
+ /**
+ * Data storage from server responses.
+ *
+ * @var array
+ */
+ public $data = array(
+ 'modseqs' => array(),
+ 'modseqs_nouid' => array()
+ );
+
+ /**
+ * Fetch results.
+ *
+ * @var Horde_Imap_Client_Fetch_Results
+ */
+ public $fetch;
+
+ /**
+ * The list of commands.
+ *
+ * @var array
+ */
+ protected $_commands = array();
+
+ /**
+ * The list of commands to complete.
+ *
+ * @var array
+ */
+ protected $_todo = array();
+
+ /**
+ * Constructor.
+ *
+ * @param Horde_Imap_Client_Fetch_Results $fetch Fetch results object.
+ */
+ public function __construct(Horde_Imap_Client_Fetch_Results $fetch)
+ {
+ $this->fetch = $fetch;
+ }
+
+ /**
+ */
+ public function __get($name)
+ {
+ switch ($name) {
+ case 'finished':
+ return empty($this->_todo);
+ }
+ }
+
+ /**
+ * Add a command to the pipeline.
+ *
+ * @param Horde_Imap_Client_Interaction_Command $cmd Command object.
+ * @param boolean $top Add command to top
+ * of queue?
+ */
+ public function add(Horde_Imap_Client_Interaction_Command $cmd,
+ $top = false)
+ {
+ if ($top) {
+ // This won't re-index keys, which may be numerical.
+ $this->_commands = array($cmd->tag => $cmd) + $this->_commands;
+ } else {
+ $this->_commands[$cmd->tag] = $cmd;
+ }
+ $this->_todo[$cmd->tag] = true;
+ }
+
+ /**
+ * Mark a command as completed.
+ *
+ * @param Horde_Imap_Client_Interaction_Server_Tagged $resp Tagged server
+ * response.
+ */
+ public function complete(Horde_Imap_Client_Interaction_Server_Tagged $resp)
+ {
+ $this->_commands[$resp->tag]->response = $resp;
+ unset($this->_todo[$resp->tag]);
+ }
+
+ /**
+ * Return the command for a given tag.
+ *
+ * @param string $tag The command tag.
+ *
+ * @return Horde_Imap_Client_Interaction_Command A command object (or
+ * null if the tag does
+ * not exist).
+ */
+ public function getCmd($tag)
+ {
+ return isset($this->_commands[$tag])
+ ? $this->_commands[$tag]
+ : null;
+ }
+
+ /* Countable methods. */
+
+ /**
+ */
+ public function count()
+ {
+ return count($this->_commands);
+ }
+
+ /* IteratorAggregate methods. */
+
+ /**
+ */
+ public function getIterator()
+ {
+ return new ArrayIterator($this->_commands);
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientInteractionServerContinuationphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Interaction/Server/Continuation.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Interaction/Server/Continuation.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Interaction/Server/Continuation.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,25 @@
</span><ins>+<?php
+/**
+ * Copyright 2012-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * An object representing an IMAP continuation response (RFC 3501 [2.2.2]).
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+class Horde_Imap_Client_Interaction_Server_Continuation extends Horde_Imap_Client_Interaction_Server
+{
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientInteractionServerTaggedphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Interaction/Server/Tagged.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Interaction/Server/Tagged.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Interaction/Server/Tagged.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,46 @@
</span><ins>+<?php
+/**
+ * Copyright 2012-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * An object representing an IMAP tagged response (RFC 3501 [2.2.2]).
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+class Horde_Imap_Client_Interaction_Server_Tagged extends Horde_Imap_Client_Interaction_Server
+{
+ /**
+ * Tag.
+ *
+ * @var string
+ */
+ public $tag;
+
+ /**
+ * @param string $tag Response tag.
+ */
+ public function __construct(Horde_Imap_Client_Tokenize $token, $tag)
+ {
+ $this->tag = $tag;
+
+ parent::__construct($token);
+
+ if (is_null($this->status)) {
+ throw new Horde_Imap_Client_Exception(Horde_Imap_Client_Translation::t("Bad tagged response."));
+ }
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientInteractionServerUntaggedphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Interaction/Server/Untagged.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Interaction/Server/Untagged.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Interaction/Server/Untagged.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,25 @@
</span><ins>+<?php
+/**
+ * Copyright 2012-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * An object representing an IMAP untagged response (RFC 3501 [2.2.2]).
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+class Horde_Imap_Client_Interaction_Server_Untagged extends Horde_Imap_Client_Interaction_Server
+{
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientInteractionServerphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Interaction/Server.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Interaction/Server.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Interaction/Server.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,142 @@
</span><ins>+<?php
+/**
+ * Copyright 2012-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * An object representing an IMAP server command interaction (RFC 3501
+ * [2.2.2]).
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+class Horde_Imap_Client_Interaction_Server
+{
+ /* Response codes (RFC 3501 [7.1]). */
+ const BAD = 1;
+ const BYE = 2;
+ const NO = 3;
+ const OK = 4;
+ const PREAUTH = 5;
+
+ /**
+ * Check for status response?
+ *
+ * @var boolean
+ */
+ protected $_checkStatus = true;
+
+ /**
+ * Response code (RFC 3501 [7.1]). Properties:
+ * - code: (string) Response code.
+ * - data: (array) Data associated with response.
+ *
+ * @var object
+ */
+ public $responseCode = null;
+
+ /**
+ * Status response from the server.
+ *
+ * @var string
+ */
+ public $status = null;
+
+ /**
+ * IMAP server data.
+ *
+ * @var Horde_Imap_Client_Tokenize
+ */
+ public $token;
+
+ /**
+ * Auto-scan an incoming line to determine the response type.
+ *
+ * @param Horde_Imap_Client_Tokenize $t Tokenized data returned from the
+ * server.
+ *
+ * @return Horde_Imap_Client_Interaction_Server A server response object.
+ */
+ static public function create(Horde_Imap_Client_Tokenize $t)
+ {
+ $t->rewind();
+ $tag = $t->next();
+ $t->next();
+
+ switch ($tag) {
+ case '+':
+ return new Horde_Imap_Client_Interaction_Server_Continuation($t);
+
+ case '*':
+ return new Horde_Imap_Client_Interaction_Server_Untagged($t);
+
+ default:
+ return new Horde_Imap_Client_Interaction_Server_Tagged($t, $tag);
+ }
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param Horde_Imap_Client_Tokenize $token Tokenized data returned from
+ * the server.
+ */
+ public function __construct(Horde_Imap_Client_Tokenize $token)
+ {
+ $this->token = $token;
+
+ /* Check for response status. */
+ $status = $token->current();
+ $valid = array('BAD', 'BYE', 'NO', 'OK', 'PREAUTH');
+
+ if (in_array($status, $valid)) {
+ $this->status = constant(__CLASS__ . '::' . $status);
+ $resp_text = $token->next();
+
+ /* Check for response code. Only occurs if there is a response
+ * status. */
+ if (is_string($resp_text) && ($resp_text[0] == '[')) {
+ $resp = new stdClass;
+ $resp->data = array();
+
+ if ($resp_text[strlen($resp_text) - 1] == ']') {
+ $resp->code = substr($resp_text, 1, -1);
+ } else {
+ $resp->code = substr($resp_text, 1);
+
+ while (($elt = $token->next()) !== false) {
+ if (is_string($elt) && $elt[strlen($elt) - 1] == ']') {
+ $resp->data[] = substr($elt, 0, -1);
+ break;
+ }
+ $resp->data[] = is_string($elt)
+ ? $elt
+ : $token->flushIterator();
+ }
+ }
+
+ $token->next();
+ $this->responseCode = $resp;
+ }
+ }
+ }
+
+ /**
+ */
+ public function __toString()
+ {
+ return strval($this->token);
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientMailboxListphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Mailbox/List.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Mailbox/List.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Mailbox/List.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,158 @@
</span><ins>+<?php
+/**
+ * Copyright 2004-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2004-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * Container of IMAP mailboxes.
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2004-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+class Horde_Imap_Client_Mailbox_List implements Countable, IteratorAggregate
+{
+ /**
+ * The delimiter character to use.
+ *
+ * @var string
+ */
+ protected $_delimiter;
+
+ /**
+ * Mailbox list.
+ *
+ * @var array
+ */
+ protected $_mboxes = array();
+
+ /**
+ * Should we sort with INBOX at the front of the list?
+ *
+ * @var boolean
+ */
+ protected $_sortinbox;
+
+ /**
+ * Constructor.
+ *
+ * @param mixed $mboxes A mailbox or list of mailboxes.
+ */
+ public function __construct($mboxes)
+ {
+ $this->_mboxes = is_array($mboxes)
+ ? $mboxes
+ : array($mboxes);
+ }
+
+ /**
+ * Sort the list of mailboxes.
+ *
+ * @param array $opts Options:
+ * - delimiter: (string) The delimiter to use.
+ * DEFAULT: '.'
+ * - inbox: (boolean) Always put INBOX at the head of the list?
+ * DEFAULT: Yes
+ * - noupdate: (boolean) Do not update the object's mailbox list?
+ * DEFAULT: true
+ *
+ * @return array List of sorted mailboxes (index association is kept).
+ */
+ public function sort(array $opts = array())
+ {
+ $this->_delimiter = isset($opts['delimiter'])
+ ? $opts['delimiter']
+ : '.';
+ $this->_sortinbox = (!isset($opts['inbox']) || !empty($opts['inbox']));
+
+ if (empty($opts['noupdate'])) {
+ $mboxes = &$this->_mboxes;
+ } else {
+ $mboxes = $this->_mboxes;
+ }
+
+ uasort($mboxes, array($this, '_mboxCompare'));
+
+ return $mboxes;
+ }
+
+ /**
+ * Hierarchical folder sorting function (used with usort()).
+ *
+ * @param string $a Comparison item 1.
+ * @param string $b Comparison item 2.
+ *
+ * @return integer See usort().
+ */
+ protected final function _mboxCompare($a, $b)
+ {
+ /* Always return INBOX as "smaller". */
+ if ($this->_sortinbox) {
+ if (strcasecmp($a, 'INBOX') == 0) {
+ return -1;
+ } elseif (strcasecmp($b, 'INBOX') == 0) {
+ return 1;
+ }
+ }
+
+ $a_parts = explode($this->_delimiter, $a);
+ $b_parts = explode($this->_delimiter, $b);
+
+ $a_count = count($a_parts);
+ $b_count = count($b_parts);
+
+ for ($i = 0, $iMax = min($a_count, $b_count); $i < $iMax; ++$i) {
+ if ($a_parts[$i] != $b_parts[$i]) {
+ /* If only one of the folders is under INBOX, return it as
+ * "smaller". */
+ if ($this->_sortinbox && ($i == 0)) {
+ $a_base = (strcasecmp($a_parts[0], 'INBOX') == 0);
+ $b_base = (strcasecmp($b_parts[0], 'INBOX') == 0);
+ if ($a_base && !$b_base) {
+ return -1;
+ } elseif (!$a_base && $b_base) {
+ return 1;
+ }
+ }
+
+ $cmp = strnatcasecmp($a_parts[$i], $b_parts[$i]);
+ return ($cmp == 0)
+ ? strcmp($a_parts[$i], $b_parts[$i])
+ : $cmp;
+ } elseif ($a_parts[$i] !== $b_parts[$i]) {
+ return strlen($a_parts[$i]) - strlen($b_parts[$i]);
+ }
+ }
+
+ return ($a_count - $b_count);
+ }
+
+ /* Countable methods. */
+
+ /**
+ */
+ public function count()
+ {
+ return count($this->_mboxes);
+ }
+
+ /* IteratorAggregate methods. */
+
+ /**
+ */
+ public function getIterator()
+ {
+ return new ArrayIterator($this->_mboxes);
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientMailboxphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Mailbox.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Mailbox.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Mailbox.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,142 @@
</span><ins>+<?php
+/**
+ * Copyright 2011-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2011-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * An object that provides a way to switch between UTF7-IMAP and
+ * human-readable representations of a mailbox name.
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2011-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ *
+ * @property-read string $list_escape Escapes mailbox for use in LIST
+ * command (UTF-8).
+ * @property-read string $utf7imap Mailbox in UTF7-IMAP.
+ * @property-read string $utf8 Mailbox in UTF-8.
+ */
+class Horde_Imap_Client_Mailbox implements Serializable
+{
+ /**
+ * UTF7-IMAP representation of mailbox.
+ * If boolean true, it is identical to UTF-8 representation.
+ *
+ * @var mixed
+ */
+ protected $_utf7imap;
+
+ /**
+ * UTF8 representation of mailbox.
+ *
+ * @var string
+ */
+ protected $_utf8;
+
+ /**
+ * Shortcut to obtaining mailbox object.
+ *
+ * @param string $mbox The mailbox name.
+ * @param boolean $utf7imap Is mailbox UTF7-IMAP encoded? Otherwise,
+ * mailbox is assumed to be UTF-8.
+ *
+ * @return Horde_Imap_Client_Mailbox A mailbox object.
+ */
+ static public function get($mbox, $utf7imap = false)
+ {
+ return ($mbox instanceof Horde_Imap_Client_Mailbox)
+ ? $mbox
+ : new Horde_Imap_Client_Mailbox($mbox, $utf7imap);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param string $mbox The mailbox name.
+ * @param mixed $utf7imap Is mailbox UTF7-IMAP encoded (true). Otherwise,
+ * mailbox is assumed to be UTF-8 encoded.
+ */
+ public function __construct($mbox, $utf7imap = false)
+ {
+ if ($utf7imap) {
+ $this->_utf7imap = $mbox;
+ } else {
+ $this->_utf8 = $mbox;
+ }
+ }
+
+ /**
+ */
+ public function __get($name)
+ {
+ switch ($name) {
+ case 'list_escape':
+ return preg_replace("/\*+/", '%', $this->utf8);
+
+ case 'utf7imap':
+ if (!isset($this->_utf7imap)) {
+ $n = Horde_Imap_Client_Utf7imap::Utf8ToUtf7Imap($this->_utf8);
+ $this->_utf7imap = ($n == $this->_utf8)
+ ? true
+ : $n;
+ }
+
+ return ($this->_utf7imap === true)
+ ? $this->_utf8
+ : $this->_utf7imap;
+
+ case 'utf8':
+ if (!isset($this->_utf8)) {
+ $this->_utf8 = Horde_Imap_Client_Utf7imap::Utf7ImapToUtf8($this->_utf7imap);
+ if ($this->_utf8 == $this->_utf7imap) {
+ $this->_utf7imap = true;
+ }
+ }
+ return $this->_utf8;
+ }
+ }
+
+ /**
+ */
+ public function __toString()
+ {
+ return $this->utf8;
+ }
+
+ /**
+ * Compares this mailbox to another mailbox string.
+ *
+ * @return boolean True if the items are equal.
+ */
+ public function equals($mbox)
+ {
+ return ($this->utf8 == $mbox);
+ }
+
+ /* Serializable methods. */
+
+ /**
+ */
+ public function serialize()
+ {
+ return json_encode(array($this->_utf7imap, $this->_utf8));
+ }
+
+ /**
+ */
+ public function unserialize($data)
+ {
+ list($this->_utf7imap, $this->_utf8) = json_decode($data, true);
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientSearchQueryphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Search/Query.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Search/Query.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Search/Query.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,745 @@
</span><ins>+<?php
+/**
+ * Copyright 2008-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2008-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * Abstraction of the IMAP4rev1 search criteria (see RFC 3501 [6.4.4]).
+ * Allows translation between abstracted search criteria and a generated IMAP
+ * search criteria string suitable for sending to a remote IMAP server.
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2008-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+class Horde_Imap_Client_Search_Query implements Serializable
+{
+ /* Serialized version. */
+ const VERSION = 3;
+
+ /* Constants for dateSearch() */
+ const DATE_BEFORE = 'BEFORE';
+ const DATE_ON = 'ON';
+ const DATE_SINCE = 'SINCE';
+
+ /* Constants for intervalSearch() */
+ const INTERVAL_OLDER = 'OLDER';
+ const INTERVAL_YOUNGER = 'YOUNGER';
+
+ /**
+ * The charset of the search strings. All text strings must be in
+ * this charset. By default, this is 'US-ASCII' (see RFC 3501 [6.4.4]).
+ *
+ * @var string
+ */
+ protected $_charset = null;
+
+ /**
+ * The list of search params.
+ *
+ * @var array
+ */
+ protected $_search = array();
+
+ /**
+ * String representation: The IMAP search string.
+ */
+ public function __toString()
+ {
+ $res = $this->build(null);
+ return $res['query']->escape();
+ }
+
+ /**
+ * Sets the charset of the search text.
+ *
+ * @param string $charset The charset to use for the search.
+ * @param boolean $convert Convert existing text values?
+ *
+ * @throws Horde_Imap_Client_Exception_SearchCharset
+ */
+ public function charset($charset, $convert = true)
+ {
+ $oldcharset = $this->_charset;
+ $this->_charset = strtoupper($charset);
+
+ if (!$convert || ($oldcharset == $this->_charset)) {
+ return;
+ }
+
+ foreach (array('header', 'text') as $item) {
+ if (isset($this->_search[$item])) {
+ foreach ($this->_search[$item] as $key => $val) {
+ $new_val = Horde_String::convertCharset($val['text'], $oldcharset, $this->_charset);
+ if (Horde_String::convertCharset($new_val, $this->_charset, $oldcharset) != $val['text']) {
+ throw new Horde_Imap_Client_Exception_SearchCharset($this->_charset);
+ }
+ $this->_search[$item][$key]['text'] = $new_val;
+ }
+ }
+ }
+ }
+
+ /**
+ * Builds an IMAP4rev1 compliant search string.
+ *
+ * @param array $exts The list of extensions supported by the server.
+ * This determines whether certain criteria can be
+ * used, and determines whether workarounds are used
+ * for other criteria. In the format returned by
+ * Horde_Imap_Client_Base::capability(). If this value
+ * is null, all extensions are assumed to be
+ * available.
+ *
+ * @return array An array with these elements:
+ * - charset: (string) The charset of the search string. If null, no
+ * text strings appear in query.
+ * - exts: (array) The list of IMAP extensions used to create the
+ * string.
+ * - query: (Horde_Imap_Client_Data_Format_List) The IMAP search
+ * command.
+ *
+ * @throws Horde_Imap_Client_Exception_NoSupportExtension
+ */
+ public function build($exts = array())
+ {
+ $temp = array(
+ 'cmds' => new Horde_Imap_Client_Data_Format_List(),
+ 'exts' => $exts,
+ 'exts_used' => array()
+ );
+ $cmds = &$temp['cmds'];
+ $charset = null;
+ $exts_used = &$temp['exts_used'];
+ $ptr = &$this->_search;
+
+ if (isset($ptr['new'])) {
+ $this->_addFuzzy(!empty($ptr['newfuzzy']), $temp);
+ if ($ptr['new']) {
+ $cmds->add('NEW');
+ unset($ptr['flag']['UNSEEN']);
+ } else {
+ $cmds->add('OLD');
+ }
+ unset($ptr['flag']['RECENT']);
+ }
+
+ if (!empty($ptr['flag'])) {
+ foreach ($ptr['flag'] as $key => $val) {
+ $this->_addFuzzy(!empty($val['fuzzy']), $temp);
+
+ $tmp = '';
+ if (empty($val['set'])) {
+ // This is a 'NOT' search. All system flags but \Recent
+ // have 'UN' equivalents.
+ if ($key == 'RECENT') {
+ $cmds->add('NOT');
+ } else {
+ $tmp = 'UN';
+ }
+ }
+
+ if ($val['type'] == 'keyword') {
+ $cmds->add(array(
+ $tmp . 'KEYWORD',
+ $key
+ ));
+ } else {
+ $cmds->add($tmp . $key);
+ }
+ }
+ }
+
+ if (!empty($ptr['header'])) {
+ /* The list of 'system' headers that have a specific search
+ * query. */
+ $systemheaders = array(
+ 'BCC', 'CC', 'FROM', 'SUBJECT', 'TO'
+ );
+
+ foreach ($ptr['header'] as $val) {
+ $this->_addFuzzy(!empty($val['fuzzy']), $temp);
+
+ if (!empty($val['not'])) {
+ $cmds->add('NOT');
+ }
+
+ if (in_array($val['header'], $systemheaders)) {
+ $cmds->add($val['header']);
+ } else {
+ $cmds->add(array(
+ 'HEADER',
+ new Horde_Imap_Client_Data_Format_Astring($val['header'])
+ ));
+ }
+ $cmds->add(new Horde_Imap_Client_Data_Format_Astring(isset($val['text']) ? $val['text'] : ''));
+ $charset = is_null($this->_charset)
+ ? 'US-ASCII'
+ : $this->_charset;
+ }
+ }
+
+ if (!empty($ptr['text'])) {
+ foreach ($ptr['text'] as $val) {
+ $this->_addFuzzy(!empty($val['fuzzy']), $temp);
+
+ if (!empty($val['not'])) {
+ $cmds->add('NOT');
+ }
+ $cmds->add(array(
+ $val['type'],
+ new Horde_Imap_Client_Data_Format_Astring($val['text'])
+ ));
+ if (is_null($charset)) {
+ $charset = is_null($this->_charset)
+ ? 'US-ASCII'
+ : $this->_charset;
+ }
+ }
+ }
+
+ if (!empty($ptr['size'])) {
+ foreach ($ptr['size'] as $key => $val) {
+ $this->_addFuzzy(!empty($val['fuzzy']), $temp);
+ if (!empty($val['not'])) {
+ $cmds->add('NOT');
+ }
+ $cmds->add(array(
+ $key,
+ new Horde_Imap_Client_Data_Format_Number($val['size'])
+ ));
+ }
+ }
+
+ if (isset($ptr['ids']) &&
+ (count($ptr['ids']['ids']) || $ptr['ids']['ids']->special)) {
+ $this->_addFuzzy(!empty($val['fuzzy']), $temp);
+ if (!empty($ptr['ids']['not'])) {
+ $cmds->add('NOT');
+ }
+ if (!$ptr['ids']['ids']->sequence) {
+ $cmds->add('UID');
+ }
+ $cmds->add(strval($ptr['ids']['ids']));
+ }
+
+ if (!empty($ptr['date'])) {
+ foreach ($ptr['date'] as $val) {
+ $this->_addFuzzy(!empty($val['fuzzy']), $temp);
+
+ if (!empty($val['not'])) {
+ $cmds->add('NOT');
+ }
+
+ if (empty($val['header'])) {
+ $cmds->add($val['range']);
+ } else {
+ $cmds->add('SENT' . $val['range']);
+ }
+ $cmds->add($val['date']);
+ }
+ }
+
+ if (!empty($ptr['within'])) {
+ if (is_null($exts) || isset($exts['WITHIN'])) {
+ $exts_used[] = 'WITHIN';
+ }
+
+ foreach ($ptr['within'] as $key => $val) {
+ $this->_addFuzzy(!empty($val['fuzzy']), $temp);
+ if (!empty($val['not'])) {
+ $cmds->add('NOT');
+ }
+
+ if (is_null($exts) || isset($exts['WITHIN'])) {
+ $cmds->add(array(
+ $key,
+ new Horde_Imap_Client_Data_Format_Number($val['interval'])
+ ));
+ } else {
+ // This workaround is only accurate to within 1 day, due
+ // to limitations with the IMAP4rev1 search commands.
+ $cmds->add(array(
+ ($key == self::INTERVAL_OLDER) ? self::DATE_BEFORE : self::DATE_SINCE,
+ new Horde_Imap_Client_Data_Format_Date('now -' . $val['interval'] . ' seconds')
+ ));
+ }
+ }
+ }
+
+ if (!empty($ptr['modseq'])) {
+ if (!is_null($exts) && !isset($exts['CONDSTORE'])) {
+ throw new Horde_Imap_Client_Exception_NoSupportExtension('IMAP Server does not support CONDSTORE.');
+ }
+
+ $exts_used[] = 'CONDSTORE';
+
+ $this->_addFuzzy(!empty($ptr['modseq']['fuzzy']), $temp);
+
+ if (!empty($ptr['modseq']['not'])) {
+ $cmds->add('NOT');
+ }
+ $cmds->add('MODSEQ');
+ if (isset($ptr['modseq']['name'])) {
+ $cmds->add(array(
+ new Horde_Imap_Client_Data_Format_String($ptr['modseq']['name']),
+ $ptr['modseq']['type']
+ ));
+ }
+ $cmds->add(new Horde_Imap_Client_Data_Format_Number($ptr['modseq']['value']));
+ }
+
+ if (isset($ptr['prevsearch'])) {
+ if (!is_null($exts) && !isset($exts['SEARCHRES'])) {
+ throw new Horde_Imap_Client_Exception_NoSupportExtension('IMAP Server does not support SEARCHRES.');
+ }
+
+ $exts_used[] = 'SEARCHRES';
+
+ $this->_addFuzzy(!empty($ptr['prevsearchfuzzy']), $temp);
+
+ if (!$ptr['prevsearch']) {
+ $cmds->add('NOT');
+ }
+ $cmds->add('$');
+ }
+
+ // Add AND'ed queries
+ if (!empty($ptr['and'])) {
+ foreach ($ptr['and'] as $val) {
+ $ret = $val->build();
+ if ($ret['charset'] != 'US-ASCII') {
+ $charset = $ret['charset'];
+ }
+ $exts_used = array_merge($exts_used, $ret['exts']);
+ $cmds->add($ret['query'], true);
+ }
+ }
+
+ // Add OR'ed queries
+ if (!empty($ptr['or'])) {
+ foreach ($ptr['or'] as $val) {
+ $ret = $val->build();
+
+ if ($ret['charset'] != 'US-ASCII') {
+ $charset = $ret['charset'];
+ }
+ $exts_used = array_merge($exts_used, $ret['exts']);
+
+ // First OR'd query
+ if (count($cmds)) {
+ $new_cmds = new Horde_Imap_Client_Data_Format_List();
+ $new_cmds->add(array(
+ 'OR',
+ $ret['query'],
+ $cmds
+ ));
+ $cmds = $new_cmds;
+ } else {
+ $cmds = $ret['query'];
+ }
+ }
+ }
+
+ // Default search is 'ALL'
+ if (!count($cmds)) {
+ $cmds->add('ALL');
+ }
+
+ return array(
+ 'charset' => $charset,
+ 'exts' => array_keys(array_flip($exts_used)),
+ 'query' => $cmds
+ );
+ }
+
+ /**
+ * Adds fuzzy modifier to search keys.
+ *
+ * @param boolean $add Add the fuzzy modifier?
+ * @param array $temp Temporary build data.
+ *
+ * @throws Horde_Imap_Client_Exception_NoSupport_Extension
+ */
+ protected function _addFuzzy($add, &$temp)
+ {
+ if ($add) {
+ if (!isset($temp['exts']['SEARCH']) ||
+ !in_array('FUZZY', $temp['exts']['SEARCH'])) {
+ throw new Horde_Imap_Client_Exception_NoSupportExtension('IMAP Server does not support SEARCH=FUZZY.');
+ }
+ $temp['cmds']->add('FUZZY');
+ $temp['exts_used'][] = 'SEARCH=FUZZY';
+ }
+ }
+
+ /**
+ * Search for a flag/keywords.
+ *
+ * @param string $name The flag or keyword name.
+ * @param boolean $set If true, search for messages that have the flag
+ * set. If false, search for messages that do not
+ * have the flag set.
+ * @param array $opts Additional options:
+ * - fuzzy: (boolean) If true, perform a fuzzy search. The IMAP server
+ * MUST support RFC 6203.
+ */
+ public function flag($name, $set = true, array $opts = array())
+ {
+ $name = strtoupper(ltrim($name, '\\'));
+ if (!isset($this->_search['flag'])) {
+ $this->_search['flag'] = array();
+ }
+
+ /* The list of defined system flags (see RFC 3501 [2.3.2]). */
+ $systemflags = array(
+ 'ANSWERED', 'DELETED', 'DRAFT', 'FLAGGED', 'RECENT', 'SEEN'
+ );
+
+ $this->_search['flag'][$name] = array_filter(array(
+ 'fuzzy' => !empty($opts['fuzzy']),
+ 'set' => $set,
+ 'type' => in_array($name, $systemflags) ? 'flag' : 'keyword'
+ ));
+ }
+
+ /**
+ * Determines if flags are a part of the search.
+ *
+ * @return boolean True if search query involves flags.
+ */
+ public function flagSearch()
+ {
+ return !empty($this->_search['flag']);
+ }
+
+ /**
+ * Search for either new messages (messages that have the '\Recent' flag
+ * but not the '\Seen' flag) or old messages (messages that do not have
+ * the '\Recent' flag). If new messages are searched, this will clear
+ * any '\Recent' or '\Unseen' flag searches. If old messages are searched,
+ * this will clear any '\Recent' flag search.
+ *
+ * @param boolean $newmsgs If true, searches for new messages. Else,
+ * search for old messages.
+ * @param array $opts Additional options:
+ * - fuzzy: (boolean) If true, perform a fuzzy search. The IMAP server
+ * MUST support RFC 6203.
+ */
+ public function newMsgs($newmsgs = true, array $opts = array())
+ {
+ $this->_search['new'] = $newmsgs;
+ if (!empty($opts['fuzzy'])) {
+ $this->_search['newfuzzy'] = true;
+ }
+ }
+
+ /**
+ * Search for text in the header of a message.
+ *
+ * @param string $header The header field.
+ * @param string $text The search text.
+ * @param boolean $not If true, do a 'NOT' search of $text.
+ * @param array $opts Additional options:
+ * - fuzzy: (boolean) If true, perform a fuzzy search. The IMAP server
+ * MUST support RFC 6203.
+ */
+ public function headerText($header, $text, $not = false,
+ array $opts = array())
+ {
+ if (!isset($this->_search['header'])) {
+ $this->_search['header'] = array();
+ }
+ $this->_search['header'][] = array_filter(array(
+ 'fuzzy' => !empty($opts['fuzzy']),
+ 'header' => strtoupper($header),
+ 'text' => $text,
+ 'not' => $not
+ ));
+ }
+
+ /**
+ * Search for text in either the entire message, or just the body.
+ *
+ * @param string $text The search text.
+ * @param string $bodyonly If true, only search in the body of the
+ * message. If false, also search in the headers.
+ * @param boolean $not If true, do a 'NOT' search of $text.
+ * @param array $opts Additional options:
+ * - fuzzy: (boolean) If true, perform a fuzzy search. The IMAP server
+ * MUST support RFC 6203.
+ */
+ public function text($text, $bodyonly = true, $not = false,
+ array $opts = array())
+ {
+ if (!isset($this->_search['text'])) {
+ $this->_search['text'] = array();
+ }
+
+ $this->_search['text'][] = array_filter(array(
+ 'fuzzy' => !empty($opts['fuzzy']),
+ 'not' => $not,
+ 'text' => $text,
+ 'type' => $bodyonly ? 'BODY' : 'TEXT'
+ ));
+ }
+
+ /**
+ * Search for messages smaller/larger than a certain size.
+ *
+ * @param integer $size The size (in bytes).
+ * @param boolean $larger Search for messages larger than $size?
+ * @param boolean $not If true, do a 'NOT' search of $text.
+ * @param array $opts Additional options:
+ * - fuzzy: (boolean) If true, perform a fuzzy search. The IMAP server
+ * MUST support RFC 6203.
+ */
+ public function size($size, $larger = false, $not = false,
+ array $opts = array())
+ {
+ if (!isset($this->_search['size'])) {
+ $this->_search['size'] = array();
+ }
+ $this->_search['size'][$larger ? 'LARGER' : 'SMALLER'] = array_filter(array(
+ 'fuzzy' => !empty($opts['fuzzy']),
+ 'not' => $not,
+ 'size' => (float)$size
+ ));
+ }
+
+ /**
+ * Search for messages within a given ID sequence range. Only one message
+ * range can be specified per query.
+ *
+ * @param Horde_Imap_Client_Ids $ids The list of IDs to search.
+ * @param boolean $not If true, do a 'NOT' search of the
+ * IDs.
+ * @param array $opts Additional options:
+ * - fuzzy: (boolean) If true, perform a fuzzy search. The IMAP server
+ * MUST support RFC 6203.
+ */
+ public function ids(Horde_Imap_Client_Ids $ids, $not = false,
+ array $opts = array())
+ {
+ if (!$ids->isEmpty()) {
+ $this->_search['ids'] = array_filter(array(
+ 'fuzzy' => !empty($opts['fuzzy']),
+ 'ids' => $ids,
+ 'not' => $not
+ ));
+ }
+ }
+
+ /**
+ * Search for messages within a date range.
+ *
+ * @param mixed $date DateTime or Horde_Date object.
+ * @param string $range Either:
+ * - Horde_Imap_Client_Search_Query::DATE_BEFORE
+ * - Horde_Imap_Client_Search_Query::DATE_ON
+ * - Horde_Imap_Client_Search_Query::DATE_SINCE
+ * @param boolean $header If true, search using the date in the message
+ * headers. If false, search using the internal
+ * IMAP date (usually arrival time).
+ * @param boolean $not If true, do a 'NOT' search of the range.
+ * @param array $opts Additional options:
+ * - fuzzy: (boolean) If true, perform a fuzzy search. The IMAP server
+ * MUST support RFC 6203.
+ */
+ public function dateSearch($date, $range, $header = true, $not = false,
+ array $opts = array())
+ {
+ if (!isset($this->_search['date'])) {
+ $this->_search['date'] = array();
+ }
+
+ // We should really be storing the raw DateTime object as data,
+ // but all versions of the query object have converted at this stage.
+ $ob = new Horde_Imap_Client_Data_Format_Date($date);
+
+ $this->_search['date'][] = array_filter(array(
+ 'date' => $ob->escape(),
+ 'fuzzy' => !empty($opts['fuzzy']),
+ 'header' => $header,
+ 'range' => $range,
+ 'not' => $not
+ ));
+ }
+
+ /**
+ * Search for messages within a given interval. Only one interval of each
+ * type can be specified per search query. If the IMAP server supports
+ * the WITHIN extension (RFC 5032), it will be used. Otherwise, the
+ * search query will be dynamically created using IMAP4rev1 search
+ * terms.
+ *
+ * @param integer $interval Seconds from the present.
+ * @param string $range Either:
+ * - Horde_Imap_Client_Search_Query::INTERVAL_OLDER
+ * - Horde_Imap_Client_Search_Query::INTERVAL_YOUNGER
+ * @param boolean $not If true, do a 'NOT' search.
+ * @param array $opts Additional options:
+ * - fuzzy: (boolean) If true, perform a fuzzy search. The IMAP server
+ * MUST support RFC 6203.
+ */
+ public function intervalSearch($interval, $range, $not = false,
+ array $opts = array())
+ {
+ if (!isset($this->_search['within'])) {
+ $this->_search['within'] = array();
+ }
+ $this->_search['within'][$range] = array(
+ 'fuzzy' => !empty($opts['fuzzy']),
+ 'interval' => $interval,
+ 'not' => $not
+ );
+ }
+
+ /**
+ * AND queries - the contents of this query will be AND'ed (in its
+ * entirety) with the contents of EACH of the queries passed in. All
+ * AND'd queries must share the same charset as this query.
+ *
+ * @param mixed $queries A query, or an array of queries, to AND with the
+ * current query.
+ */
+ public function andSearch($queries)
+ {
+ if (!isset($this->_search['and'])) {
+ $this->_search['and'] = array();
+ }
+
+ if ($queries instanceof Horde_Imap_Client_Search_Query) {
+ $queries = array($queries);
+ }
+
+ $this->_search['and'] = array_merge($this->_search['and'], $queries);
+ }
+
+ /**
+ * OR a query - the contents of this query will be OR'ed (in its entirety)
+ * with the contents of EACH of the queries passed in. All OR'd queries
+ * must share the same charset as this query. All contents of any single
+ * query will be AND'ed together.
+ *
+ * @param mixed $queries A query, or an array of queries, to OR with the
+ * current query.
+ */
+ public function orSearch($queries)
+ {
+ if (!isset($this->_search['or'])) {
+ $this->_search['or'] = array();
+ }
+
+ if ($queries instanceof Horde_Imap_Client_Search_Query) {
+ $queries = array($queries);
+ }
+
+ $this->_search['or'] = array_merge($this->_search['or'], $queries);
+ }
+
+ /**
+ * Search for messages modified since a specific moment. The IMAP server
+ * must support the CONDSTORE extension (RFC 4551) for this query to be
+ * used.
+ *
+ * @param integer $value The mod-sequence value.
+ * @param string $name The entry-name string.
+ * @param string $type Either 'shared', 'priv', or 'all'. Defaults to
+ * 'all'
+ * @param boolean $not If true, do a 'NOT' search.
+ * @param array $opts Additional options:
+ * - fuzzy: (boolean) If true, perform a fuzzy search. The IMAP server
+ * MUST support RFC 6203.
+ */
+ public function modseq($value, $name = null, $type = null, $not = false,
+ array $opts = array())
+ {
+ if (!is_null($type)) {
+ $type = strtolower($type);
+ if (!in_array($type, array('shared', 'priv', 'all'))) {
+ $type = 'all';
+ }
+ }
+
+ $this->_search['modseq'] = array_filter(array(
+ 'fuzzy' => !empty($opts['fuzzy']),
+ 'name' => $name,
+ 'not' => $not,
+ 'type' => (!is_null($name) && is_null($type)) ? 'all' : $type,
+ 'value' => $value
+ ));
+ }
+
+ /**
+ * Use the results from the previous SEARCH command. The IMAP server must
+ * support the SEARCHRES extension (RFC 5182) for this query to be used.
+ *
+ * @param boolean $not If true, don't match the previous query.
+ * @param array $opts Additional options:
+ * - fuzzy: (boolean) If true, perform a fuzzy search. The IMAP server
+ * MUST support RFC 6203.
+ */
+ public function previousSearch($not = false, array $opts = array())
+ {
+ $this->_search['prevsearch'] = $not;
+ if (!empty($opts['fuzzy'])) {
+ $this->_search['prevsearchfuzzy'] = true;
+ }
+ }
+
+ /* Serializable methods. */
+
+ /**
+ * Serialization.
+ *
+ * @return string Serialized data.
+ */
+ public function serialize()
+ {
+ $data = array(
+ // Serialized data ID.
+ self::VERSION,
+ $this->_search
+ );
+
+ if (!is_null($this->_charset)) {
+ $data[] = $this->_charset;
+ }
+
+ return serialize($data);
+ }
+
+ /**
+ * Unserialization.
+ *
+ * @param string $data Serialized data.
+ *
+ * @throws Exception
+ */
+ public function unserialize($data)
+ {
+ $data = @unserialize($data);
+ if (!is_array($data) ||
+ !isset($data[0]) ||
+ ($data[0] != self::VERSION)) {
+ throw new Exception('Cache version change');
+ }
+
+ $this->_search = $data[1];
+ if (isset($data[2])) {
+ $this->_charset = $data[2];
+ }
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientSocketCatenatephp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Socket/Catenate.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Socket/Catenate.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Socket/Catenate.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,167 @@
</span><ins>+<?php
+/**
+ * Copyright 2012-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * Methods for the Socket driver used for a CATENATE command.
+ *
+ * NOTE: This class is NOT intended to be accessed outside of a Base object.
+ * There is NO guarantees that the API of this class will not change across
+ * versions.
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @internal
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+class Horde_Imap_Client_Socket_Catenate
+{
+ /**
+ * Socket object.
+ *
+ * @var Horde_Imap_Client_Socket
+ */
+ protected $_socket;
+
+ /**
+ * Constructor.
+ *
+ * @param Horde_Imap_Client_Socket $socket Socket object.
+ */
+ public function __construct(Horde_Imap_Client_Socket $socket)
+ {
+ $this->_socket = $socket;
+ }
+
+ /**
+ * Given an IMAP URL, fetches the corresponding part.
+ *
+ * @param Horde_Imap_Client_Url $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 $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;
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientSocketClientSortphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Socket/ClientSort.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Socket/ClientSort.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Socket/ClientSort.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,317 @@
</span><ins>+<?php
+/**
+ * Copyright 2012-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * Client sorting methods for the Socket driver.
+ *
+ * NOTE: This class is NOT intended to be accessed outside of a Base object.
+ * There is NO guarantees that the API of this class will not change across
+ * versions.
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @internal
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+class Horde_Imap_Client_Socket_ClientSort
+{
+ /**
+ * Socket object.
+ *
+ * @var Horde_Imap_Client_Socket
+ */
+ protected $_socket;
+
+ /**
+ * Constructor.
+ *
+ * @param Horde_Imap_Client_Socket $socket Socket object.
+ */
+ public function __construct(Horde_Imap_Client_Socket $socket)
+ {
+ $this->_socket = $socket;
+ }
+
+ /**
+ * 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']);
+ }
+
+ /**
+ * If server does not support the THREAD IMAP extension (RFC 5256), do
+ * ORDEREDSUBJECT threading on the client side.
+ *
+ * @param Horde_Imap_Client_Fetch_Results $data Fetch results.
+ * @param boolean $uids Are IDs UIDs?
+ *
+ * @return array The thread sort results.
+ */
+ public function threadOrderedSubject(Horde_Imap_Client_Fetch_Results $data,
+ $uids)
+ {
+ $dates = $this->_getSentDates($data, $data->ids());
+ $out = $sorted = $tsort = array();
+
+ foreach ($data as $k => $v) {
+ $subject = strval(new Horde_Imap_Client_Data_BaseSubject($v->getEnvelope()->subject));
+ $sorted[$subject][$k] = $dates[$k];
+ }
+
+ /* Step 1: Sort by base subject (already done).
+ * Step 2: Sort by sent date within each thread. */
+ foreach (array_keys($sorted) as $key) {
+ asort($sorted[$key], SORT_NUMERIC);
+ $tsort[$key] = reset($sorted[$key]);
+ }
+
+ /* Step 3: Sort by the sent date of the first message in the
+ * thread. */
+ asort($tsort, SORT_NUMERIC);
+
+ /* Now, $tsort contains the order of the threads, and each thread
+ * is sorted in $sorted. */
+ foreach (array_keys($tsort) as $key) {
+ $keys = array_keys($sorted[$key]);
+ $out[$keys[0]] = array(
+ $keys[0] => 0
+ ) + array_fill_keys(array_slice($keys, 1) , 1);
+ }
+
+ return new Horde_Imap_Client_Data_Thread($out, $uids ? 'uid' : 'sequence');
+ }
+
+ /**
+ */
+ protected function _clientSortProcess($res, $fetch_res, $sort)
+ {
+ /* The initial sort is on the entire set. */
+ $slices = array(0 => $res);
+ $reverse = false;
+
+ foreach ($sort as $val) {
+ if ($val == Horde_Imap_Client::SORT_REVERSE) {
+ $reverse = true;
+ continue;
+ }
+
+ $slices_list = $slices;
+ $slices = array();
+
+ foreach ($slices_list as $slice_start => $slice) {
+ $sorted = array();
+
+ if ($reverse) {
+ $slice = array_reverse($slice);
+ }
+
+ switch ($val) {
+ case Horde_Imap_Client::SORT_SEQUENCE:
+ /* There is no requirement that IDs be returned in
+ * sequence order (see RFC 4549 [4.3.1]). So we must sort
+ * ourselves. */
+ $sorted = array_flip($slice);
+ ksort($sorted, SORT_NUMERIC);
+ break;
+
+ case Horde_Imap_Client::SORT_SIZE:
+ foreach ($slice as $num) {
+ $sorted[$num] = $fetch_res[$num]->getSize();
+ }
+ asort($sorted, SORT_NUMERIC);
+ break;
+
+ case Horde_Imap_Client::SORT_DISPLAYFROM:
+ case Horde_Imap_Client::SORT_DISPLAYTO:
+ $field = ($val == Horde_Imap_Client::SORT_DISPLAYFROM)
+ ? 'from'
+ : 'to';
+
+ foreach ($slice as $num) {
+ $env = $fetch_res[$num]->getEnvelope();
+
+ if (empty($env->$field)) {
+ $sorted[$num] = null;
+ } else {
+ $addr_ob = reset($env->$field);
+ if (is_null($sorted[$num] = $addr_ob->personal)) {
+ $sorted[$num] = $addr_ob->mailbox;
+ }
+ }
+ }
+
+ asort($sorted, SORT_LOCALE_STRING);
+ break;
+
+ case Horde_Imap_Client::SORT_CC:
+ case Horde_Imap_Client::SORT_FROM:
+ case Horde_Imap_Client::SORT_TO:
+ if ($val == Horde_Imap_Client::SORT_CC) {
+ $field = 'cc';
+ } elseif ($val == Horde_Imap_Client::SORT_FROM) {
+ $field = 'from';
+ } else {
+ $field = 'to';
+ }
+
+ foreach ($slice as $num) {
+ $tmp = $fetch_res[$num]->getEnvelope()->$field;
+ $sorted[$num] = count($tmp)
+ ? $tmp[0]->mailbox
+ : null;
+ }
+ asort($sorted, SORT_LOCALE_STRING);
+ break;
+
+ case Horde_Imap_Client::SORT_ARRIVAL:
+ $sorted = $this->_getSentDates($fetch_res, $slice, true);
+ asort($sorted, SORT_NUMERIC);
+ break;
+
+ case Horde_Imap_Client::SORT_DATE:
+ // Date sorting rules in RFC 5256 [2.2]
+ $sorted = $this->_getSentDates($fetch_res, $slice);
+ asort($sorted, SORT_NUMERIC);
+ break;
+
+ case Horde_Imap_Client::SORT_SUBJECT:
+ // Subject sorting rules in RFC 5256 [2.1]
+ foreach ($slice as $num) {
+ $sorted[$num] = strval(new Horde_Imap_Client_Data_BaseSubject($fetch_res[$num]->getEnvelope()->subject));
+ }
+ asort($sorted, SORT_LOCALE_STRING);
+ break;
+ }
+
+ // At this point, keys of $sorted are sequence/UID and values
+ // are the sort strings
+ if (!empty($sorted)) {
+ if (count($sorted) == count($res)) {
+ $res = array_keys($sorted);
+ } else {
+ array_splice($res, $slice_start, count($slice), array_keys($sorted));
+ }
+
+ // Check for ties.
+ $last = $start = null;
+ $i = 0;
+ reset($sorted);
+ while (list($k, $v) = each($sorted)) {
+ if (is_null($last) || ($last != $v)) {
+ if ($i) {
+ $slices[array_search($start, $res)] = array_slice($sorted, array_search($start, $sorted), $i + 1);
+ $i = 0;
+ }
+ $last = $v;
+ $start = $k;
+ } else {
+ ++$i;
+ }
+ }
+ if ($i) {
+ $slices[array_search($start, $res)] = array_slice($sorted, array_search($start, $sorted), $i + 1);
+ }
+ }
+ }
+
+ $reverse = false;
+ }
+
+ return $res;
+ }
+
+ /**
+ * Get the sent dates for purposes of SORT/THREAD sorting under RFC 5256
+ * [2.2].
+ *
+ * @param Horde_Imap_Client_Fetch_Results $data Data returned from
+ * fetch() that includes
+ * both date and envelope
+ * items.
+ * @param array $ids The IDs to process.
+ * @param boolean $internal Only use internal date?
+ *
+ * @return array A mapping of IDs -> UNIX timestamps.
+ */
+ protected function _getSentDates(Horde_Imap_Client_Fetch_Results $data,
+ $ids, $internal = false)
+ {
+ $dates = array();
+
+ foreach ($ids as $num) {
+ $dt = ($internal || !isset($data[$num]->getEnvelope()->date))
+ // RFC 5256 [3] & 3501 [6.4.4]: disregard timezone when
+ // using internaldate.
+ ? $data[$num]->getImapDate()
+ : $data[$num]->getEnvelope()->date;
+ $dates[$num] = $dt->format('U');
+ }
+
+ return $dates;
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientSocketConnectionPop3php"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Socket/Connection/Pop3.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Socket/Connection/Pop3.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Socket/Connection/Pop3.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,81 @@
</span><ins>+<?php
+/**
+ * Copyright 2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * PHP stream connection to the POP3 server.
+ *
+ * NOTE: This class is NOT intended to be accessed outside of the package.
+ * There is NO guarantees that the API of this class will not change across
+ * versions.
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2013 Horde LLC
+ * @internal
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+class Horde_Imap_Client_Socket_Connection_Pop3
+extends Horde_Imap_Client_Socket_Connection
+{
+ /**
+ * Writes data to the POP3 output stream.
+ *
+ * @param string $data String data.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ public function write($data)
+ {
+ if (fwrite($this->_stream, $data . "\r\n") === false) {
+ throw new Horde_Imap_Client_Exception(
+ Horde_Imap_Client_Translation::t("Server write error."),
+ Horde_Imap_Client_Exception::SERVER_WRITEERROR
+ );
+ }
+
+ $this->_debug->client($data);
+ }
+
+ /**
+ * Read data from incoming POP3 stream.
+ *
+ * @return string Line of data.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ public function read()
+ {
+ if (feof($this->_stream)) {
+ $this->close();
+ $this->_debug->info("ERROR: Server closed the connection.");
+ throw new Horde_Imap_Client_Exception(
+ Horde_Imap_Client_Translation::t("POP3 Server closed the connection unexpectedly."),
+ Horde_Imap_Client_Exception::DISCONNECT
+ );
+ }
+
+ if (($read = fgets($this->_stream)) === false) {
+ $this->_debug->info("ERROR: IMAP read/timeout error.");
+ throw new Horde_Imap_Client_Exception(
+ Horde_Imap_Client_Translation::t("Error when communicating with the mail server."),
+ Horde_Imap_Client_Exception::SERVER_READERROR
+ );
+ }
+
+ $this->_debug->server(rtrim($read, "\r\n"));
+
+ return $read;
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientSocketConnectionSocketphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Socket/Connection/Socket.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Socket/Connection/Socket.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Socket/Connection/Socket.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,210 @@
</span><ins>+<?php
+/**
+ * Copyright 2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * PHP stream connection to the IMAP server.
+ *
+ * NOTE: This class is NOT intended to be accessed outside of the package.
+ * There is NO guarantees that the API of this class will not change across
+ * versions.
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2013 Horde LLC
+ * @internal
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+class Horde_Imap_Client_Socket_Connection_Socket
+extends Horde_Imap_Client_Socket_Connection
+{
+ /**
+ * Sending buffer.
+ *
+ * @var string
+ */
+ protected $_buffer = '';
+
+ /**
+ * Output full data for literals?
+ *
+ * @var boolean
+ */
+ protected $_debugliteral;
+
+ /**
+ * Constructor.
+ *
+ * @param Horde_Imap_Client_Base $base The base client object.
+ * @param object $debug The debug handler.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ public function __construct(Horde_Imap_Client_Base $base, $debug)
+ {
+ parent::__construct($base, $debug);
+
+ $this->_debugliteral = $base->getParam('debug_literal');
+ }
+
+ /**
+ * Writes data to the IMAP output stream.
+ *
+ * @param string $data String data.
+ * @param boolean $eol Append EOL?
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ public function write($data, $eol = false)
+ {
+ if ($eol) {
+ $buffer = $this->_buffer;
+ $this->_buffer = '';
+
+ if (fwrite($this->_stream, $buffer . $data . ($eol ? "\r\n" : '')) === false) {
+ throw new Horde_Imap_Client_Exception(
+ Horde_Imap_Client_Translation::t("Server write error."),
+ Horde_Imap_Client_Exception::SERVER_WRITEERROR
+ );
+ }
+
+ $this->_debug->client($buffer . $data);
+ } else {
+ $this->_buffer .= $data;
+ }
+ }
+
+ /**
+ * Writes literal data to the IMAP output stream.
+ *
+ * @param mixed $data Either a stream resource, or Horde_Stream
+ * object.
+ * @param integer $length The literal length.
+ * @param boolean $binary If true, this is binary data.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ public function writeLiteral($data, $length, $binary = false)
+ {
+ $this->_buffer = '';
+
+ if ($data instanceof Horde_Stream) {
+ $data = $data->stream;
+ }
+
+ rewind($data);
+ while (!feof($data)) {
+ if (fwrite($this->_stream, fread($data, 8192)) === false) {
+ throw new Horde_Imap_Client_Exception(
+ Horde_Imap_Client_Translation::t("Server write error."),
+ Horde_Imap_Client_Exception::SERVER_WRITEERROR
+ );
+ }
+ }
+
+ if ($this->_debugliteral) {
+ rewind($data);
+ while (!feof($data)) {
+ $this->_debug->raw(fread($data, 8192));
+ }
+ } else {
+ $this->_debug->client('[' . ($binary ? 'BINARY' : 'LITERAL') . ' DATA: ' . $length . ' bytes]');
+ }
+ }
+
+ /**
+ * Read data from incoming IMAP stream.
+ *
+ * @return Horde_Imap_Client_Tokenize The tokenized data.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ public function read()
+ {
+ $got_data = false;
+ $literal_len = null;
+ $token = new Horde_Imap_Client_Tokenize();
+
+ do {
+ if (feof($this->_stream)) {
+ $this->close();
+ $this->_debug->info("ERROR: Server closed the connection.");
+ throw new Horde_Imap_Client_Exception(
+ Horde_Imap_Client_Translation::t("Mail server closed the connection unexpectedly."),
+ Horde_Imap_Client_Exception::DISCONNECT
+ );
+ }
+
+ if (is_null($literal_len)) {
+ $buffer = '';
+
+ while (($in = fgets($this->_stream)) !== false) {
+ $got_data = true;
+
+ if (substr($in, -1) == "\n") {
+ $in = rtrim($in);
+ $this->_debug->server($buffer . $in);
+ $token->add($in);
+ break;
+ }
+
+ $buffer .= $in;
+ $token->add($in);
+ }
+
+ /* Check for literal data. */
+ if (is_null($len = $token->getLiteralLength())) {
+ break;
+ }
+
+ // Skip 0-length literal data.
+ if ($len['length']) {
+ $binary = $len['binary'];
+ $literal_len = $len['length'];
+ }
+
+ continue;
+ }
+
+ $old_len = $literal_len;
+
+ while (($literal_len > 0) && !feof($this->_stream)) {
+ $in = fread($this->_stream, min($literal_len, 8192));
+ $token->add($in);
+ if ($this->_debugliteral) {
+ $this->_debug->raw($in);
+ }
+
+ $got_data = true;
+ $literal_len -= strlen($in);
+ }
+
+ $literal_len = null;
+
+ if (!$this->_debugliteral) {
+ $this->_debug->server('[' . ($binary ? 'BINARY' : 'LITERAL') . ' DATA: ' . $old_len . ' bytes]');
+ }
+ } while (true);
+
+ if (!$got_data) {
+ $this->_debug->info("ERROR: IMAP read/timeout error.");
+ throw new Horde_Imap_Client_Exception(
+ Horde_Imap_Client_Translation::t("Error when communicating with the mail server."),
+ Horde_Imap_Client_Exception::SERVER_READERROR
+ );
+ }
+
+ return $token;
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientSocketConnectionphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Socket/Connection.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Socket/Connection.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Socket/Connection.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,128 @@
</span><ins>+<?php
+/**
+ * Copyright 2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * PHP stream connection to a server.
+ *
+ * NOTE: This class is NOT intended to be accessed outside of the package.
+ * There is NO guarantees that the API of this class will not change across
+ * versions.
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2013 Horde LLC
+ * @internal
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+class Horde_Imap_Client_Socket_Connection
+extends Horde_Imap_Client_Base_Connection
+{
+ /**
+ * Sending buffer.
+ *
+ * @var string
+ */
+ protected $_buffer = '';
+
+ /**
+ * The stream connection to the IMAP server.
+ *
+ * @var resource
+ */
+ protected $_stream = null;
+
+ /**
+ * Constructor.
+ *
+ * @param Horde_Imap_Client_Base $base The base client object.
+ * @param object $debug The debug handler.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ public function __construct(Horde_Imap_Client_Base $base, $debug)
+ {
+ parent::__construct($base, $debug);
+
+ switch ($secure = $base->getParam('secure')) {
+ case 'ssl':
+ case 'sslv2':
+ case 'sslv3':
+ $conn = $secure . '://';
+ $this->_secure = true;
+ break;
+
+ case 'tls':
+ default:
+ $conn = 'tcp://';
+ break;
+ }
+
+ $timeout = $base->getParam('timeout');
+
+ $this->_stream = @stream_socket_client(
+ $conn . $base->getParam('hostspec') . ':' . $base->getParam('port'),
+ $error_number,
+ $error_string,
+ $timeout
+ );
+
+ if ($this->_stream === false) {
+ $e = new Horde_Imap_Client_Exception(
+ Horde_Imap_Client_Translation::t("Error connecting to mail server."),
+ Horde_Imap_Client_Exception::SERVER_CONNECT
+ );
+ $e->details = sprintf("[%u] %s", $error_number, $error_string);
+ throw $e;
+ }
+
+ stream_set_timeout($this->_stream, $timeout);
+
+ if (function_exists('stream_set_read_buffer')) {
+ stream_set_read_buffer($this->_stream, 0);
+ }
+ stream_set_write_buffer($this->_stream, 0);
+
+ $this->_connected = true;
+ }
+
+ /**
+ * Start a TLS connection to the server.
+ *
+ * @return boolean Whether TLS was successfully started.
+ */
+ public function startTls()
+ {
+ if ($this->connected &&
+ !$this->secure &&
+ (@stream_socket_enable_crypto($this->_stream, true, STREAM_CRYPTO_METHOD_TLS_CLIENT) === true)) {
+ $this->_secure = true;
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Close the connection to the server.
+ */
+ public function close()
+ {
+ if ($this->connected) {
+ @fclose($this->_stream);
+ $this->_connected = $this->_secure = false;
+ $this->_stream = null;
+ }
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientSocketPop3php"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Socket/Pop3.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Socket/Pop3.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Socket/Pop3.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,1264 @@
</span><ins>+<?php
+/**
+ * Copyright 2009-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * ---------------------------------------------------------------------------
+ *
+ * Based on the PEAR Net_POP3 package (version 1.3.6) by:
+ * Richard Heyes <richard@phpguru.org>
+ * Damian Fernandez Sosa <damlists@cnba.uba.ar>
+ *
+ * Copyright (c) 2002, Richard Heyes
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * o Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * o Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * o The names of the authors may not be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * ---------------------------------------------------------------------------
+ *
+ * @category Horde
+ * @copyright 2002 Richard Heyes
+ * @copyright 2009-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * An interface to a POP3 server using PHP functions.
+ *
+ * It is an abstraction layer allowing POP3 commands to be used based on
+ * IMAP equivalents.
+ *
+ * This driver implements the following POP3-related RFCs:
+ * - STD 53/RFC 1939: POP3 specification
+ * - RFC 2195: CRAM-MD5 authentication
+ * - RFC 2449: POP3 extension mechanism
+ * - RFC 2595/4616: PLAIN authentication
+ * - RFC 2831: DIGEST-MD5 SASL Authentication (obsoleted by RFC 6331)
+ * - RFC 3206: AUTH/SYS response codes
+ * - RFC 1734/5034: POP3 SASL
+ *
+ * @author Richard Heyes <richard@phpguru.org>
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2002 Richard Heyes
+ * @copyright 2009-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+class Horde_Imap_Client_Socket_Pop3 extends Horde_Imap_Client_Base
+{
+ /**
+ * The list of deleted messages.
+ *
+ * @var array
+ */
+ protected $_deleted = array();
+
+ /**
+ * This object returns POP3 Fetch data objects.
+ *
+ * @var string
+ */
+ protected $_fetchDataClass = 'Horde_Imap_Client_Data_Fetch_Pop3';
+
+ /**
+ */
+ public function __construct(array $params = array())
+ {
+ parent::__construct($params);
+
+ if (empty($params['port'])) {
+ $this->setParam('port', in_array($this->getParam('secure'), array('ssl', 'sslv2', 'sslv3')) ? 995 : 110);
+ }
+ }
+
+ /**
+ */
+ protected function _initCache($current = false)
+ {
+ return parent::_initCache($current) &&
+ $this->queryCapability('UIDL');
+ }
+
+ /**
+ */
+ public function getIdsOb($ids = null, $sequence = false)
+ {
+ return new Horde_Imap_Client_Ids_Pop3($ids, $sequence);
+ }
+
+ /**
+ */
+ protected function _capability()
+ {
+ $this->_connect();
+
+ $capability = array();
+
+ try {
+ $res = $this->_sendLine('CAPA', array(
+ 'multiline' => 'array'
+ ));
+
+ foreach ($res['data'] as $val) {
+ $prefix = explode(' ', $val);
+ $capability[strtoupper($prefix[0])] = (count($prefix) > 1)
+ ? array_slice($prefix, 1)
+ : true;
+ }
+ } catch (Horde_Imap_Client_Exception $e) {
+ /* Need to probe for capabilities if CAPA command is not
+ * available. */
+ $capability = array('USER', 'SASL');
+
+ try {
+ $this->_sendLine('UIDL', array(
+ 'multiline' => 'none'
+ ));
+ $capability[] = 'UIDL';
+ } catch (Horde_Imap_Client_Exception $e) {}
+
+ try {
+ $this->_sendLine('TOP 1 0', array(
+ 'multiline' => 'none'
+ ));
+ $capability[] = 'TOP';
+ } catch (Horde_Imap_Client_Exception $e) {}
+ }
+
+ $this->_setInit('capability', $capability);
+ }
+
+ /**
+ */
+ protected function _noop()
+ {
+ $this->_sendLine('NOOP');
+ }
+
+ /**
+ * @throws Horde_Imap_Client_Exception_NoSupportPop3
+ */
+ protected function _getNamespaces()
+ {
+ throw new Horde_Imap_Client_Exception_NoSupportPop3('Namespaces');
+ }
+
+ /**
+ */
+ public function alerts()
+ {
+ return array();
+ }
+
+ /**
+ */
+ protected function _login()
+ {
+ $this->_connect();
+
+ // Switch to secure channel if using TLS.
+ if (!$this->isSecureConnection() &&
+ ($this->getParam('secure') == 'tls')) {
+ // Switch over to a TLS connection.
+ if (!$this->queryCapability('STLS')) {
+ throw new Horde_Imap_Client_Exception(
+ Horde_Imap_Client_Translation::t("Could not open secure connection to the POP3 server.") . ' ' . Horde_Imap_Client_Translation::t("Server does not support secure connections."),
+ Horde_Imap_Client_Exception::LOGIN_TLSFAILURE
+ );
+ }
+
+ $this->_sendLine('STLS');
+
+ if (!$this->_connection->startTls()) {
+ $this->logout();
+ throw new Horde_Imap_Client_Exception(
+ Horde_Imap_Client_Translation::t("Could not open secure connection to the POP3 server."),
+ Horde_Imap_Client_Exception::LOGIN_TLSFAILURE
+ );
+ }
+
+ // Expire cached CAPABILITY information
+ $this->_setInit('capability');
+ }
+
+ if (empty($this->_init['authmethod'])) {
+ $auth_mech = ($sasl = $this->queryCapability('SASL'))
+ ? $sasl
+ : array();
+
+ if (isset($this->_temp['pop3timestamp'])) {
+ $auth_mech[] = 'APOP';
+ }
+
+ $auth_mech[] = 'USER';
+ } else {
+ $auth_mech = array($this->_init['authmethod']);
+ }
+
+ foreach ($auth_mech as $method) {
+ try {
+ $this->_tryLogin($method);
+ $this->_setInit('authmethod', $method);
+ return true;
+ } catch (Horde_Imap_Client_Exception $e) {
+ if (!empty($this->_init['authmethod'])) {
+ $this->_setInit();
+ return $this->login();
+ }
+ }
+ }
+
+ throw new Horde_Imap_Client_Exception(
+ Horde_Imap_Client_Translation::t("POP3 server denied authentication."),
+ $e->getCode() ? $e->getCode() : Horde_Imap_Client_Exception::LOGIN_AUTHENTICATIONFAILED
+ );
+ }
+
+ /**
+ * Connects to the server.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ protected function _connect()
+ {
+ if (!is_null($this->_connection)) {
+ return;
+ }
+
+ $this->_connection = new Horde_Imap_Client_Socket_Connection_Pop3($this, $this->_debug);
+
+ $line = $this->_getResponse();
+
+ // Check for string matching APOP timestamp
+ if (preg_match('/<.+@.+>/U', $line['resp'], $matches)) {
+ $this->_temp['pop3timestamp'] = $matches[0];
+ }
+ }
+
+ /**
+ * Authenticate to the POP3 server.
+ *
+ * @param string $method POP3 login method.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ protected function _tryLogin($method)
+ {
+ $username = $this->getParam('username');
+ $password = $this->getParam('password');
+
+ switch ($method) {
+ case 'CRAM-MD5':
+ case 'CRAM-SHA1':
+ case 'CRAM-SHA256':
+ // RFC 5034: CRAM-MD5
+ // CRAM-SHA1 & CRAM-SHA256 supported by Courier SASL library
+ $challenge = $this->_sendLine('AUTH ' . $method);
+ $response = base64_encode($username . ' ' . hash_hmac(strtolower(substr($method, 5)), base64_decode(substr($challenge['resp'], 2)), $password, true));
+ $this->_sendLine($response, array(
+ 'debug' => sprintf('[%s Response - username: %s]', $method, $username)
+ ));
+ break;
+
+ case 'DIGEST-MD5':
+ // RFC 2831; Obsoleted by RFC 6331
+ $challenge = $this->_sendLine('AUTH DIGEST-MD5');
+ $response = base64_encode(new Horde_Imap_Client_Auth_DigestMD5(
+ $username,
+ $password,
+ base64_decode(substr($challenge['resp'], 2)),
+ $this->getParam('hostspec'),
+ 'pop3'
+ ));
+ $sresponse = $this->_sendLine($response, array(
+ 'debug' => sprintf('[%s Response - username: %s]', $method, $username)
+ ));
+ if (stripos(base64_decode(substr($sresponse['resp'], 2)), 'rspauth=') === false) {
+ throw new Horde_Imap_Client_Exception(
+ Horde_Imap_Client_Translation::t("Unexpected response from server when authenticating."),
+ Horde_Imap_Client_Exception::SERVER_CONNECT
+ );
+ }
+
+ /* POP3 doesn't use protocol's third step. */
+ $this->_sendLine('');
+ break;
+
+ case 'LOGIN':
+ // RFC 5034
+ $this->_sendLine('AUTH LOGIN');
+ $this->_sendLine(base64_encode($username), array(
+ 'debug' => sprintf('[AUTH LOGIN Command - username: %s]', $username)
+ ));
+ $this->_sendLine(base64_encode($password), array(
+ 'debug' => '[AUTH LOGIN Command - password]'
+ ));
+ break;
+
+ case 'PLAIN':
+ // RFC 5034
+ $this->_sendLine('AUTH PLAIN ' . base64_encode(implode("\0", array($username, $this->getParam('password')))), array(
+ 'debug' => sprintf('[AUTH PLAIN Command - username: %s]', $username)
+ ));
+ break;
+
+ case 'APOP':
+ // RFC 1939 [7]
+ $this->_sendLine('APOP ' . $username . ' ' . hash('md5', $this->_temp['pop3timestamp'] . $password));
+ break;
+
+ case 'USER':
+ // RFC 1939 [7]
+ $this->_sendLine('USER ' . $username);
+ $this->_sendLine('PASS ' . $password, array(
+ 'debug' => '[USER Command - password]'
+ ));
+ break;
+
+ default:
+ throw new Horde_Imap_Client_Exception(
+ sprintf(Horde_Imap_Client_Translation::t("Unknown authentication method: %s"), $method),
+ Horde_Imap_Client_Exception::SERVER_CONNECT
+ );
+ }
+ }
+
+ /**
+ */
+ protected function _logout()
+ {
+ try {
+ $this->_sendLine('QUIT');
+ } catch (Horde_Imap_Client_Exception $e) {}
+ $this->_deleted = array();
+ }
+
+ /**
+ * @throws Horde_Imap_Client_Exception_NoSupportPop3
+ */
+ protected function _sendID($info)
+ {
+ throw new Horde_Imap_Client_Exception_NoSupportPop3('ID command');
+ }
+
+ /**
+ * Return implementation information from the POP3 server (RFC 2449 [6.9]).
+ */
+ protected function _getID()
+ {
+ $id = $this->queryCapability('IMPLEMENTATION');
+ return empty($id)
+ ? array()
+ : array('implementation' => $id);
+ }
+
+ /**
+ * @throws Horde_Imap_Client_Exception_NoSupportPop3
+ */
+ protected function _setLanguage($langs)
+ {
+ throw new Horde_Imap_Client_Exception_NoSupportPop3('LANGUAGE extension');
+ }
+
+ /**
+ * @throws Horde_Imap_Client_Exception_NoSupportPop3
+ */
+ protected function _getLanguage($list)
+ {
+ throw new Horde_Imap_Client_Exception_NoSupportPop3('LANGUAGE extension');
+ }
+
+ /**
+ * @throws Horde_Imap_Client_Exception_NoSupportPop3
+ */
+ protected function _openMailbox(Horde_Imap_Client_Mailbox $mailbox, $mode)
+ {
+ if (strcasecmp($mailbox, 'INBOX') !== 0) {
+ throw new Horde_Imap_Client_Exception_NoSupportPop3('Mailboxes other than INBOX');
+ }
+ $this->_changeSelected($mailbox, $mode);
+ }
+
+ /**
+ * @throws Horde_Imap_Client_Exception_NoSupportPop3
+ */
+ protected function _createMailbox(Horde_Imap_Client_Mailbox $mailbox, $opts)
+ {
+ throw new Horde_Imap_Client_Exception_NoSupportPop3('Creating mailboxes');
+ }
+
+ /**
+ * @throws Horde_Imap_Client_Exception_NoSupportPop3
+ */
+ protected function _deleteMailbox(Horde_Imap_Client_Mailbox $mailbox)
+ {
+ throw new Horde_Imap_Client_Exception_NoSupportPop3('Deleting mailboxes');
+ }
+
+ /**
+ * @throws Horde_Imap_Client_Exception_NoSupportPop3
+ */
+ protected function _renameMailbox(Horde_Imap_Client_Mailbox $old,
+ Horde_Imap_Client_Mailbox $new)
+ {
+ throw new Horde_Imap_Client_Exception_NoSupportPop3('Renaming mailboxes');
+ }
+
+ /**
+ * @throws Horde_Imap_Client_Exception_NoSupportPop3
+ */
+ protected function _subscribeMailbox(Horde_Imap_Client_Mailbox $mailbox,
+ $subscribe)
+ {
+ throw new Horde_Imap_Client_Exception_NoSupportPop3('Mailboxes other than INBOX');
+ }
+
+ /**
+ */
+ protected function _listMailboxes($pattern, $mode, $options)
+ {
+ $tmp = array(
+ 'mailbox' => Horde_Imap_Client_Mailbox::get('INBOX')
+ );
+
+ if (!empty($options['attributes'])) {
+ $tmp['attributes'] = array();
+ }
+ if (!empty($options['delimiter'])) {
+ $tmp['delimiter'] = '';
+ }
+
+ return array('INBOX' => $tmp);
+ }
+
+ /**
+ * @param integer $flags This driver only supports the options listed
+ * under Horde_Imap_Client::STATUS_ALL.
+ * @throws Horde_Imap_Client_Exception_NoSupportPop3
+ */
+ protected function _status($mboxes, $flags)
+ {
+ if ((count($mboxes) > 1) ||
+ (strcasecmp(reset($mboxes), 'INBOX') !== 0)) {
+ throw new Horde_Imap_Client_Exception_NoSupportPop3('Mailboxes other than INBOX');
+ }
+
+ $this->openMailbox('INBOX');
+
+ $ret = array();
+
+ if ($flags & Horde_Imap_Client::STATUS_MESSAGES) {
+ $res = $this->_pop3Cache('stat');
+ $ret['messages'] = $res['msgs'];
+ }
+
+ if ($flags & Horde_Imap_Client::STATUS_RECENT) {
+ $res = $this->_pop3Cache('stat');
+ $ret['recent'] = $res['msgs'];
+ }
+
+ // No need for STATUS_UIDNEXT_FORCE handling since STATUS_UIDNEXT will
+ // always return a value.
+ if ($flags & Horde_Imap_Client::STATUS_UIDNEXT) {
+ $res = $this->_pop3Cache('stat');
+ $ret['uidnext'] = $res['msgs'] + 1;
+ }
+
+ if ($flags & Horde_Imap_Client::STATUS_UIDVALIDITY) {
+ $ret['uidvalidity'] = $this->queryCapability('UIDL')
+ ? 1
+ : microtime(true);
+ }
+
+ if ($flags & Horde_Imap_Client::STATUS_UNSEEN) {
+ $ret['unseen'] = 0;
+ }
+
+ return array('INBOX' => $ret);
+ }
+
+ /**
+ * @throws Horde_Imap_Client_Exception_NoSupportPop3
+ */
+ protected function _append(Horde_Imap_Client_Mailbox $mailbox, $data,
+ $options)
+ {
+ throw new Horde_Imap_Client_Exception_NoSupportPop3('Appending messages');
+ }
+
+ /**
+ */
+ protected function _check()
+ {
+ $this->noop();
+ }
+
+ /**
+ */
+ protected function _close($options)
+ {
+ if (!empty($options['expunge'])) {
+ $this->logout();
+ }
+ }
+
+ /**
+ * @param array $options Additional options. 'ids' has no effect in this
+ * driver.
+ */
+ protected function _expunge($options)
+ {
+ $msg_list = $this->_deleted;
+ $this->logout();
+ return empty($options['list'])
+ ? null
+ : $msg_list;
+ }
+
+ /**
+ * @throws Horde_Imap_Client_Exception_NoSupportPop3
+ */
+ protected function _search($query, $options)
+ {
+ $sort = empty($options['sort'])
+ ? null
+ : reset($options['sort']);
+
+ // Only support a single query: an ALL search sorted by sequence.
+ if ((strval($options['_query']['query']) != 'ALL') ||
+ ($sort &&
+ ((count($options['sort']) > 1) ||
+ ($sort != Horde_Imap_Client::SORT_SEQUENCE)))) {
+ throw new Horde_Imap_Client_Exception_NoSupportPop3('Server search');
+ }
+
+ $status = $this->status($this->_selected, Horde_Imap_Client::STATUS_MESSAGES);
+ $res = range(1, $status['messages']);
+
+ if (empty($options['sequence'])) {
+ $tmp = array();
+ $uidllist = $this->_pop3Cache('uidl');
+ foreach ($res as $val) {
+ $tmp[] = $uidllist[$val];
+ }
+ $res = $tmp;
+ }
+
+ $ret = array();
+ foreach ($options['results'] as $val) {
+ switch ($val) {
+ case Horde_Imap_Client::SEARCH_RESULTS_COUNT:
+ $ret['count'] = count($res);
+ break;
+
+ case Horde_Imap_Client::SEARCH_RESULTS_MATCH:
+ $ret['match'] = $this->getIdsOb($res);
+ break;
+
+ case Horde_Imap_Client::SEARCH_RESULTS_MAX:
+ $ret['max'] = empty($res) ? null : max($res);
+ break;
+
+ case Horde_Imap_Client::SEARCH_RESULTS_MIN:
+ $ret['min'] = empty($res) ? null : min($res);
+ break;
+ }
+ }
+
+ return $ret;
+ }
+
+ /**
+ * @throws Horde_Imap_Client_Exception_NoSupportPop3
+ */
+ protected function _setComparator($comparator)
+ {
+ throw new Horde_Imap_Client_Exception_NoSupportPop3('Search comparators');
+ }
+
+ /**
+ * @throws Horde_Imap_Client_Exception_NoSupportPop3
+ */
+ protected function _getComparator()
+ {
+ throw new Horde_Imap_Client_Exception_NoSupportPop3('Search comparators');
+ }
+
+ /**
+ * @throws Horde_Imap_Client_Exception_NoSupportPop3
+ */
+ protected function _thread($options)
+ {
+ throw new Horde_Imap_Client_Exception_NoSupportPop3('Server threading');
+ }
+
+ /**
+ */
+ protected function _fetch(Horde_Imap_Client_Fetch_Results $results,
+ $queries)
+ {
+ foreach ($queries as $options) {
+ $this->_fetchCmd($results, $options);
+ }
+
+ $this->_updateCache($results);
+ }
+
+ /**
+ * Fetch data for a given fetch query.
+ *
+ * @param Horde_Imap_Client_Fetch_Results $results Fetch results.
+ * @param array $options Fetch query options.
+ */
+ protected function _fetchCmd(Horde_Imap_Client_Fetch_Results $results,
+ $options)
+ {
+ // Grab sequence IDs - IDs will always be the message number for
+ // POP3 fetch commands.
+ $seq_ids = $this->_getSeqIds($options['ids']);
+ if (empty($seq_ids)) {
+ return;
+ }
+
+ $lookup = $options['ids']->sequence
+ ? array_combine($seq_ids, $seq_ids)
+ : $this->_pop3Cache('uidl');
+
+ foreach ($options['_query'] as $type => $c_val) {
+ switch ($type) {
+ case Horde_Imap_Client::FETCH_FULLMSG:
+ foreach ($seq_ids as $id) {
+ $tmp = $this->_pop3Cache('msg', $id);
+
+ if (empty($c_val['start']) && empty($c_val['length'])) {
+ $tmp2 = fopen('php://temp', 'r+');
+ stream_copy_to_stream($tmp, $tmp2, empty($c_val['length']) ? -1 : $c_val['length'], empty($c_val['start']) ? 0 : $c_val['start']);
+ $results->get($lookup[$id])->setFullMsg($tmp2);
+ } else {
+ $results->get($lookup[$id])->setFullMsg($tmp);
+ }
+ }
+ break;
+
+ case Horde_Imap_Client::FETCH_HEADERTEXT:
+ // Ignore 'peek' option
+ foreach ($c_val as $key => $val) {
+ foreach ($seq_ids as $id) {
+ /* Message header can be retrieved via TOP, if the
+ * command is available. */
+ try {
+ $tmp = ($key == 0)
+ ? $this->_pop3Cache('hdr', $id)
+ : Horde_Mime_Part::getRawPartText(stream_get_contents($this->_pop3Cache('msg', $id)), 'header', $key);
+ $results->get($lookup[$id])->setHeaderText($key, $this->_processString($tmp, $c_val));
+ } catch (Horde_Mime_Exception $e) {}
+ }
+ }
+ break;
+
+ case Horde_Imap_Client::FETCH_BODYTEXT:
+ // Ignore 'peek' option
+ foreach ($c_val as $key => $val) {
+ foreach ($seq_ids as $id) {
+ try {
+ $results->get($lookup[$id])->setBodyText($key, $this->_processString(Horde_Mime_Part::getRawPartText(stream_get_contents($this->_pop3Cache('msg', $id)), 'body', $key), $val));
+ } catch (Horde_Mime_Exception $e) {}
+ }
+ }
+ break;
+
+ case Horde_Imap_Client::FETCH_MIMEHEADER:
+ // Ignore 'peek' option
+ foreach ($c_val as $key => $val) {
+ foreach ($seq_ids as $id) {
+ try {
+ $results->get($lookup[$id])->setMimeHeader($key, $this->_processString(Horde_Mime_Part::getRawPartText(stream_get_contents($this->_pop3Cache('msg', $id)), 'header', $key), $val));
+ } catch (Horde_Mime_Exception $e) {}
+ }
+ }
+ break;
+
+ case Horde_Imap_Client::FETCH_BODYPART:
+ // Ignore 'decode', 'peek'
+ foreach ($c_val as $key => $val) {
+ foreach ($seq_ids as $id) {
+ try {
+ $results->get($lookup[$id])->setBodyPart($key, $this->_processString(Horde_Mime_Part::getRawPartText(stream_get_contents($this->_pop3Cache('msg', $id)), 'body', $key), $val));
+ } catch (Horde_Mime_Exception $e) {}
+ }
+ }
+ break;
+
+ case Horde_Imap_Client::FETCH_HEADERS:
+ // Ignore 'length', 'peek'
+ foreach ($seq_ids as $id) {
+ $ob = $this->_pop3Cache('hdrob', $id);
+ foreach ($c_val as $key => $val) {
+ $tmp = $ob;
+
+ if (empty($val['notsearch'])) {
+ $tmp2 = $tmp->toArray(array('nowrap' => true));
+ foreach (array_keys($tmp2) as $hdr) {
+ if (!in_array($hdr, $val['headers'])) {
+ $tmp->removeHeader($hdr);
+ }
+ }
+ } else {
+ foreach ($val['headers'] as $hdr) {
+ $tmp->removeHeader($hdr);
+ }
+ }
+
+ $results->get($lookup[$id])->setHeaders($key, $tmp);
+ }
+ }
+ break;
+
+ case Horde_Imap_Client::FETCH_STRUCTURE:
+ foreach ($seq_ids as $id) {
+ if ($ptr = $this->_pop3Cache('msg', $id)) {
+ try {
+ $results->get($lookup[$id])->setStructure(Horde_Mime_Part::parseMessage(stream_get_contents($ptr), array('no_body' => true)));
+ } catch (Horde_Exception $e) {}
+ }
+ }
+ break;
+
+ case Horde_Imap_Client::FETCH_ENVELOPE:
+ foreach ($seq_ids as $id) {
+ $tmp = $this->_pop3Cache('hdrob', $id);
+ $results->get($lookup[$id])->setEnvelope(array(
+ 'date' => $tmp->getValue('date'),
+ 'subject' => $tmp->getValue('subject'),
+ 'from' => $tmp->getOb('from'),
+ 'sender' => $tmp->getOb('sender'),
+ 'reply_to' => $tmp->getOb('reply-to'),
+ 'to' => $tmp->getOb('to'),
+ 'cc' => $tmp->getOb('cc'),
+ 'bcc' => $tmp->getOb('bcc'),
+ 'in_reply_to' => $tmp->getValue('in-reply-to'),
+ 'message_id' => $tmp->getValue('message-id')
+ ));
+ }
+ break;
+
+ case Horde_Imap_Client::FETCH_IMAPDATE:
+ foreach ($seq_ids as $id) {
+ $tmp = $this->_pop3Cache('hdrob', $id);
+ $results->get($lookup[$id])->setImapDate($tmp->getValue('date'));
+ }
+ break;
+
+ case Horde_Imap_Client::FETCH_SIZE:
+ $sizelist = $this->_pop3Cache('size');
+ foreach ($seq_ids as $id) {
+ $results->get($lookup[$id])->setSize($sizelist[$id]);
+ }
+ break;
+
+ case Horde_Imap_Client::FETCH_SEQ:
+ foreach ($seq_ids as $id) {
+ $results->get($lookup[$id])->setSeq($id);
+ }
+ break;
+
+ case Horde_Imap_Client::FETCH_UID:
+ $uidllist = $this->_pop3Cache('uidl');
+ foreach ($seq_ids as $id) {
+ if (isset($uidllist[$id])) {
+ $results->get($lookup[$id])->setUid($uidllist[$id]);
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ /**
+ * Retrieve locally cached message data.
+ *
+ * @param string $type Either 'hdr', 'hdrob', 'msg', 'size', 'stat',
+ * or 'uidl'.
+ * @param integer $index The message index.
+ * @param mixed $data Additional information needed.
+ *
+ * @return mixed The cached data. 'msg' returns a stream resource. All
+ * other types return strings.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ protected function _pop3Cache($type, $index = null, $data = null)
+ {
+ if (isset($this->_temp['pop3cache'][$index][$type])) {
+ if ($type == 'msg') {
+ rewind($this->_temp['pop3cache'][$index][$type]);
+ }
+ return $this->_temp['pop3cache'][$index][$type];
+ }
+
+ switch ($type) {
+ case 'hdr':
+ $data = null;
+ if ($this->queryCapability('TOP')) {
+ try {
+ $res = $this->_sendLine('TOP ' . $index . ' 0', array(
+ 'multiline' => 'stream'
+ ));
+ rewind($res['data']);
+ $data = stream_get_contents($res['data']);
+ fclose($res['data']);
+ } catch (Horde_Imap_Client_Exception $e) {}
+ }
+
+ if (is_null($data)) {
+ $data = Horde_Mime_Part::getRawPartText(stream_get_contents($this->_pop3Cache('msg', $index)), 'header', 0);
+ }
+ break;
+
+ case 'hdrob':
+ $data = Horde_Mime_Headers::parseHeaders($this->_pop3Cache('hdr', $index));
+ break;
+
+ case 'msg':
+ $res = $this->_sendLine('RETR ' . $index, array(
+ 'multiline' => 'stream'
+ ));
+ $data = $res['data'];
+ rewind($data);
+ break;
+
+ case 'size':
+ case 'uidl':
+ $data = array();
+ try {
+ $res = $this->_sendLine(($type == 'size') ? 'LIST' : 'UIDL', array(
+ 'multiline' => 'array'
+ ));
+ foreach ($res['data'] as $val) {
+ $resp_data = explode(' ', $val, 2);
+ $data[$resp_data[0]] = $resp_data[1];
+ }
+ } catch (Horde_Imap_Client_Exception $e) {}
+ break;
+
+ case 'stat':
+ $resp = $this->_sendLine('STAT');
+ $resp_data = explode(' ', $resp['resp'], 2);
+ $data = array('msgs' => $resp_data[0], 'size' => $resp_data[1]);
+ break;
+ }
+
+ $this->_temp['pop3cache'][$index][$type] = $data;
+
+ return $data;
+ }
+
+ /**
+ * Process a string response based on criteria options.
+ *
+ * @param string $str The original string.
+ * @param array $opts The criteria options.
+ *
+ * @return string The requested string.
+ */
+ protected function _processString($str, $opts)
+ {
+ if (!empty($opts['length'])) {
+ return substr($str, empty($opts['start']) ? 0 : $opts['start'], $opts['length']);
+ } elseif (!empty($opts['start'])) {
+ return substr($str, $opts['start']);
+ }
+
+ return $str;
+ }
+
+ /**
+ * @throws Horde_Imap_Client_Exception_NoSupportPop3
+ */
+ protected function _vanished($modseq, Horde_Imap_Client_Ids $ids)
+ {
+ throw new Horde_Imap_Client_Exception_NoSupportPop3('QRESYNC commands');
+ }
+
+ /**
+ * @param array $options Additional options. This driver does not support
+ * 'unchangedsince'.
+ */
+ protected function _store($options)
+ {
+ $delete = $reset = false;
+
+ /* Only support deleting/undeleting messages. */
+ if (isset($options['replace'])) {
+ $delete = (bool)(count(array_intersect($options['replace'], array(
+ Horde_Imap_Client::FLAG_DELETED
+ ))));
+ $reset = !$delete;
+ } else {
+ if (!empty($options['add'])) {
+ $delete = (bool)(count(array_intersect($options['add'], array(
+ Horde_Imap_Client::FLAG_DELETED
+ ))));
+ }
+
+ if (!empty($options['remove'])) {
+ $reset = !(bool)(count(array_intersect($options['remove'], array(
+ Horde_Imap_Client::FLAG_DELETED
+ ))));
+ }
+ }
+
+ if ($reset) {
+ $this->_sendLine('RSET');
+ } elseif ($delete) {
+ foreach ($this->_getSeqIds($options['ids']) as $id) {
+ try {
+ $this->_sendLine('DELE ' . $id);
+ $this->_deleted[] = $id;
+ } catch (Horde_Imap_Client_Exception $e) {}
+ }
+ }
+
+ return $this->getIdsOb();
+ }
+
+ /**
+ * @throws Horde_Imap_Client_Exception_NoSupportPop3
+ */
+ protected function _copy(Horde_Imap_Client_Mailbox $dest, $options)
+ {
+ throw new Horde_Imap_Client_Exception_NoSupportPop3('Copying messages');
+ }
+
+ /**
+ * @throws Horde_Imap_Client_Exception_NoSupportPop3
+ */
+ protected function _setQuota(Horde_Imap_Client_Mailbox $root, $options)
+ {
+ throw new Horde_Imap_Client_Exception_NoSupportPop3('Quotas');
+ }
+
+ /**
+ * @throws Horde_Imap_Client_Exception_NoSupportPop3
+ */
+ protected function _getQuota(Horde_Imap_Client_Mailbox $root)
+ {
+ throw new Horde_Imap_Client_Exception_NoSupportPop3('Quotas');
+ }
+
+ /**
+ * @throws Horde_Imap_Client_Exception_NoSupportPop3
+ */
+ protected function _getQuotaRoot(Horde_Imap_Client_Mailbox $mailbox)
+ {
+ throw new Horde_Imap_Client_Exception_NoSupportPop3('Quotas');
+ }
+
+ /**
+ * @throws Horde_Imap_Client_Exception_NoSupportPop3
+ */
+ protected function _setACL(Horde_Imap_Client_Mailbox $mailbox, $identifier,
+ $options)
+ {
+ throw new Horde_Imap_Client_Exception_NoSupportPop3('ACLs');
+ }
+
+ /**
+ * @throws Horde_Imap_Client_Exception_NoSupportPop3
+ */
+ protected function _deleteACL(Horde_Imap_Client_Mailbox $mailbox, $identifier)
+ {
+ throw new Horde_Imap_Client_Exception_NoSupportPop3('ACLs');
+ }
+
+ /**
+ * @throws Horde_Imap_Client_Exception_NoSupportPop3
+ */
+ protected function _getACL(Horde_Imap_Client_Mailbox $mailbox)
+ {
+ throw new Horde_Imap_Client_Exception_NoSupportPop3('ACLs');
+ }
+
+ /**
+ * @throws Horde_Imap_Client_Exception_NoSupportPop3
+ */
+ protected function _listACLRights(Horde_Imap_Client_Mailbox $mailbox,
+ $identifier)
+ {
+ throw new Horde_Imap_Client_Exception_NoSupportPop3('ACLs');
+ }
+
+ /**
+ * @throws Horde_Imap_Client_Exception_NoSupportPop3
+ */
+ protected function _getMyACLRights(Horde_Imap_Client_Mailbox $mailbox)
+ {
+ throw new Horde_Imap_Client_Exception_NoSupportPop3('ACLs');
+ }
+
+ /**
+ * @throws Horde_Imap_Client_Exception_NoSupportPop3
+ */
+ protected function _getMetadata(Horde_Imap_Client_Mailbox $mailbox,
+ $entries, $options)
+ {
+ throw new Horde_Imap_Client_Exception_NoSupportPop3('Metadata');
+ }
+
+ /**
+ * @throws Horde_Imap_Client_Exception_NoSupportPop3
+ */
+ protected function _setMetadata(Horde_Imap_Client_Mailbox $mailbox, $data)
+ {
+ throw new Horde_Imap_Client_Exception_NoSupportPop3('Metadata');
+ }
+
+ /**
+ */
+ protected function _getSearchCache($type, $options)
+ {
+ /* POP3 does not support search caching. */
+ return null;
+ }
+
+ /* Internal functions. */
+
+ /**
+ * Perform a command on the server. A connection to the server must have
+ * already been made.
+ *
+ * @param string $cmd The command to execute.
+ * @param array $options Additional options:
+ * <pre>
+ * - debug: (string) When debugging, send this string instead of the
+ * actual command/data sent.
+ * DEFAULT: Raw data output to debug stream.
+ * - multiline: (mixed) 'array', 'none', or 'stream'.
+ * </pre>
+ *
+ * @return array See _getResponse().
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ protected function _sendLine($cmd, $options = array())
+ {
+ $old_debug = $this->_debug->debug;
+ if (!empty($options['debug'])) {
+ $this->_debug->raw($options['debug'] . "\n");
+ $this->_debug->debug = false;
+ }
+
+ try {
+ $this->_connection->write($cmd);
+ } catch (Horde_Imap_Client_Exception $e) {
+ $this->_debug->debug = $old_debug;
+ throw $e;
+ }
+
+ $this->_debug->debug = $old_debug;
+
+ return $this->_getResponse(
+ empty($options['multiline']) ? false : $options['multiline']
+ );
+ }
+
+ /**
+ * Gets a line from the stream and parses it.
+ *
+ * @param mixed $multiline 'array', 'none', 'stream', or null.
+ *
+ * @return array An array with the following keys:
+ * - data: (mixed) Stream, array, or null.
+ * - resp: (string) The server response text.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ protected function _getResponse($multiline = false)
+ {
+ $ob = array('resp' => '');
+
+ $read = explode(' ', rtrim($this->_connection->read(), "\r\n"), 2);
+ if (!in_array($read[0], array('+OK', '-ERR'))) {
+ $this->_debug->info("ERROR: IMAP read/timeout error.");
+ throw new Horde_Imap_Client_Exception(
+ Horde_Imap_Client_Translation::t("Error when communicating with the mail server."),
+ Horde_Imap_Client_Exception::SERVER_READERROR
+ );
+ }
+
+ $respcode = null;
+ if (isset($read[1]) &&
+ isset($this->_init['capability']) &&
+ $this->queryCapability('RESP-CODES')) {
+ $respcode = $this->_parseResponseCode($read[1]);
+ }
+
+ switch ($read[0]) {
+ case '+OK':
+ if ($respcode) {
+ $ob['resp'] = $respcode->text;
+ } elseif (isset($read[1])) {
+ $ob['resp'] = $read[1];
+ }
+ break;
+
+ case '-ERR':
+ $errcode = 0;
+ if ($respcode) {
+ $errtext = $respcode->text;
+
+ if (isset($respcode->code)) {
+ switch ($respcode->code) {
+ // RFC 2449 [8.1.1]
+ case 'IN-USE':
+ // RFC 2449 [8.1.2]
+ case 'LOGIN-DELAY':
+ $errcode = Horde_Imap_Client_Exception::LOGIN_UNAVAILABLE;
+ break;
+
+ // RFC 3206 [4]
+ case 'SYS/TEMP':
+ $errcode = Horde_Imap_Client_Exception::POP3_TEMP_ERROR;
+ break;
+
+ // RFC 3206 [4]
+ case 'SYS/PERM':
+ $errcode = Horde_Imap_Client_Exception::POP3_PERM_ERROR;
+ break;
+
+ // RFC 3206 [5]
+ case 'AUTH':
+ $errcode = Horde_Imap_Client_Exception::LOGIN_AUTHENTICATIONFAILED;
+ break;
+ }
+ }
+ } elseif (isset($read[1])) {
+ $errtext = $read[1];
+ } else {
+ $errtext = '[No error message provided by server]';
+ }
+
+ $e = new Horde_Imap_Client_Exception(
+ Horde_Imap_Client_Translation::t("POP3 error reported by server."),
+ $errcode
+ );
+ $e->details = $errtext;
+ throw $e;
+ }
+
+ switch ($multiline) {
+ case 'array':
+ $ob['data'] = array();
+ break;
+
+ case 'none':
+ $ob['data'] = null;
+ break;
+
+ case 'stream':
+ $ob['data'] = fopen('php://temp', 'r+');
+ break;
+
+ default:
+ return $ob;
+ }
+
+ do {
+ $orig_read = $this->_connection->read();
+ $read = rtrim($orig_read, "\r\n");
+
+ if ($read == '.') {
+ break;
+ } elseif (substr($read, 0, 2) == '..') {
+ $read = substr($read, 1);
+ }
+
+ if (is_array($ob['data'])) {
+ $ob['data'][] = $read;
+ } elseif (!is_null($ob['data'])) {
+ fwrite($ob['data'], $orig_read);
+ }
+ } while (true);
+
+ return $ob;
+ }
+
+ /**
+ * Returns a list of sequence IDs.
+ *
+ * @param Horde_Imap_Client_Ids $ids The ID list.
+ *
+ * @return array A list of sequence IDs.
+ */
+ protected function _getSeqIds(Horde_Imap_Client_Ids $ids)
+ {
+ if (!count($ids)) {
+ $status = $this->status($this->_selected, Horde_Imap_Client::STATUS_MESSAGES);
+ return range(1, $status['messages']);
+ } elseif ($ids->sequence) {
+ return $ids->ids;
+ }
+
+ return array_keys(array_intersect($this->_pop3Cache('uidl'), $ids->ids));
+ }
+
+ /**
+ * Parses response text for response codes (RFC 2449 [8]).
+ *
+ * @param string $text The response text.
+ *
+ * @return object An object with the following properties:
+ * - code: (string) The response code, if it exists.
+ * - data: (string) The response code data, if it exists.
+ * - text: (string) The human-readable response text.
+ */
+ protected function _parseResponseCode($text)
+ {
+ $ret = new stdClass;
+
+ $text = trim($text);
+ if ($text[0] == '[') {
+ $pos = strpos($text, ' ', 2);
+ $end_pos = strpos($text, ']', 2);
+ if ($pos > $end_pos) {
+ $ret->code = strtoupper(substr($text, 1, $end_pos - 1));
+ } else {
+ $ret->code = strtoupper(substr($text, 1, $pos - 1));
+ $ret->data = substr($text, $pos + 1, $end_pos - $pos - 1);
+ }
+ $ret->text = trim(substr($text, $end_pos + 1));
+ } else {
+ $ret->text = $text;
+ }
+
+ return $ret;
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientSocketphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Socket.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Socket.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Socket.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,4608 @@
</span><ins>+<?php
+/**
+ * Originally based on code from:
+ * - auth.php (1.49)
+ * - imap_general.php (1.212)
+ * - imap_messages.php (revision 13038)
+ * - strings.php (1.184.2.35)
+ * from the Squirrelmail project.
+ * Copyright (c) 1999-2007 The SquirrelMail Project Team
+ *
+ * Copyright 2005-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 1999-2007 The SquirrelMail Project Team
+ * @copyright 2005-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * An interface to an IMAP4rev1 server (RFC 3501) using built-in PHP features.
+ *
+ * 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
+ * - RFC 6851: MOVE
+ * - RFC 6858: DOWNGRADED response code
+ *
+ * Implements the following non-RFC extensions:
+ * <ul>
+ * <li>draft-ietf-morg-inthread-01: THREAD=REFS</li>
+ * <li>draft-daboo-imap-annotatemore-07: ANNOTATEMORE</li>
+ * <li>draft-daboo-imap-annotatemore-08: ANNOTATEMORE2</li>
+ * <li>XIMAPPROXY
+ * <ul>
+ * <li>Requires imapproxy v1.2.7-rc1 or later</li>
+ * <li>
+ * See https://squirrelmail.svn.sourceforge.net/svnroot/squirrelmail/trunk/imap_proxy/README
+ * </li>
+ * </ul>
+ * </li>
+ * </ul>
+ *
+ * TODO (or not necessary?):
+ * <ul>
+ * <li>RFC 2177: IDLE
+ * <ul>
+ * <li>
+ * Probably not necessary due to the limited connection time of each
+ * HTTP/PHP request
+ * </li>
+ * </ul>
+ * <li>RFC 2193: MAILBOX-REFERRALS</li>
+ * <li>
+ * RFC 4467/5092/5524/5550/5593: URLAUTH, URLAUTH=BINARY, URL-PARTIAL
+ * </li>
+ * <li>RFC 4978: COMPRESS=DEFLATE
+ * <ul>
+ * <li>See: http://bugs.php.net/bug.php?id=48725</li>
+ * </ul>
+ * </li>
+ * <li>RFC 5257: ANNOTATE (Experimental)</li>
+ * <li>RFC 5259: CONVERT</li>
+ * <li>RFC 5267: CONTEXT=SEARCH; CONTEXT=SORT</li>
+ * <li>RFC 5465: NOTIFY</li>
+ * <li>RFC 5466: FILTERS</li>
+ * <li>RFC 6237: MULTISEARCH (Experimental)</li>
+ * <li>RFC 6855: UTF8
+ * </ul>
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 1999-2007 The SquirrelMail Project Team
+ * @copyright 2005-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+class Horde_Imap_Client_Socket extends Horde_Imap_Client_Base
+{
+ /* Cache names used exclusively within this class. */
+ const CACHE_FLAGS = 'HICflags';
+
+ /**
+ * Queued commands to send to the server.
+ *
+ * @var array
+ */
+ protected $_cmdQueue = array();
+
+ /**
+ * Mapping of status fields to IMAP names.
+ *
+ * @var array
+ */
+ protected $_statusFields = array(
+ 'messages' => Horde_Imap_Client::STATUS_MESSAGES,
+ 'recent' => Horde_Imap_Client::STATUS_RECENT,
+ 'uidnext' => Horde_Imap_Client::STATUS_UIDNEXT,
+ 'uidvalidity' => Horde_Imap_Client::STATUS_UIDVALIDITY,
+ 'unseen' => Horde_Imap_Client::STATUS_UNSEEN,
+ 'firstunseen' => Horde_Imap_Client::STATUS_FIRSTUNSEEN,
+ 'flags' => Horde_Imap_Client::STATUS_FLAGS,
+ 'permflags' => Horde_Imap_Client::STATUS_PERMFLAGS,
+ 'uidnotsticky' => Horde_Imap_Client::STATUS_UIDNOTSTICKY,
+ 'highestmodseq' => Horde_Imap_Client::STATUS_HIGHESTMODSEQ
+ );
+
+ /**
+ * The unique tag to use when making an IMAP query.
+ *
+ * @var integer
+ */
+ protected $_tag = 0;
+
+ /**
+ * @param array $params A hash containing configuration parameters.
+ * Additional parameters to base driver:
+ * - debug_literal: (boolean) If true, will output the raw text of
+ * literal responses to the debug stream. Otherwise,
+ * outputs a summary of the literal response.
+ * - envelope_addrs: (integer) The maximum number of address entries to
+ * read for FETCH ENVELOPE address fields.
+ * DEFAULT: 1000
+ * - envelope_string: (integer) The maximum length of string fields
+ * returned by the FETCH ENVELOPE command.
+ * DEFAULT: 2048
+ */
+ public function __construct(array $params = array())
+ {
+ parent::__construct(array_merge(array(
+ 'debug_literal' => false,
+ 'envelope_addrs' => 1000,
+ 'envelope_string' => 2048
+ ), $params));
+ }
+
+ /**
+ */
+ protected function _capability()
+ {
+ // Need to use connect call here or else we run into loop issues
+ // because _connect() can call capability() internally.
+ $this->_connect();
+
+ // It is possible the server provided capability information on
+ // connect, so check for it now.
+ if (!isset($this->_init['capability'])) {
+ $this->_sendCmd($this->_command('CAPABILITY'));
+ }
+ }
+
+ /**
+ * Parse a CAPABILITY Response (RFC 3501 [7.2.1]).
+ *
+ * @param Horde_Imap_Client_Interaction_Pipeline $pipeline Pipeline
+ * object.
+ * @param array $data An array of CAPABILITY strings.
+ */
+ protected function _parseCapability(
+ Horde_Imap_Client_Interaction_Pipeline $pipeline,
+ $data
+ )
+ {
+ if (!empty($this->_temp['no_cap'])) {
+ return;
+ }
+
+ /* Assume capabilities are additive. */
+ $c = empty($this->_init['capability'])
+ ? array()
+ : $this->_init['capability'];
+
+ $pipeline->data['capabilties_set'] = true;
+
+ foreach ($data as $val) {
+ $cap_list = explode('=', $val);
+ $cap_list[0] = strtoupper($cap_list[0]);
+ if (isset($cap_list[1])) {
+ if (!isset($c[$cap_list[0]]) || !is_array($c[$cap_list[0]])) {
+ $c[$cap_list[0]] = array();
+ }
+ $c[$cap_list[0]][] = $cap_list[1];
+ } elseif (!isset($c[$cap_list[0]])) {
+ $c[$cap_list[0]] = true;
+ }
+ }
+
+ $this->_setInit('capability', $c);
+ }
+
+ /**
+ * Unsets a capability.
+ *
+ * @param string $cap Capability to unset.
+ */
+ protected function _unsetCapability($cap)
+ {
+ $cap_list = $this->capability();
+ unset($cap_list[$cap]);
+ $this->_setInit('capability', $cap_list);
+ }
+
+ /**
+ */
+ protected function _noop()
+ {
+ // NOOP doesn't return any specific response
+ $this->_sendCmd($this->_command('NOOP'));
+ }
+
+ /**
+ */
+ protected function _getNamespaces()
+ {
+ $data = $this->queryCapability('NAMESPACE')
+ ? $this->_sendCmd($this->_command('NAMESPACE'))->data
+ : array();
+
+ return isset($data['namespace'])
+ ? $data['namespace']
+ : array();
+ }
+
+ /**
+ * Parse a NAMESPACE response (RFC 2342 [5] & RFC 5255 [3.4]).
+ *
+ * @param Horde_Imap_Client_Interaction_Pipeline $pipeline Pipeline
+ * object.
+ * @param Horde_Imap_Client_Tokenize $data The NAMESPACE data.
+ */
+ protected function _parseNamespace(
+ Horde_Imap_Client_Interaction_Pipeline $pipeline,
+ Horde_Imap_Client_Tokenize $data
+ )
+ {
+ $namespace_array = array(
+ Horde_Imap_Client::NS_PERSONAL,
+ Horde_Imap_Client::NS_OTHER,
+ Horde_Imap_Client::NS_SHARED
+ );
+
+ $c = array();
+
+ // Per RFC 2342, response from NAMESPACE command is:
+ // (PERSONAL NAMESPACES) (OTHER_USERS NAMESPACE) (SHARED NAMESPACES)
+ foreach ($namespace_array as $val) {
+ $entry = $data->next();
+
+ if (is_null($entry)) {
+ continue;
+ }
+
+ while ($data->next() !== false) {
+ $ob = Horde_Imap_Client_Mailbox::get($data->next(), true);
+
+ $c[strval($ob)] = array(
+ 'delimiter' => $data->next(),
+ 'hidden' => false,
+ 'name' => strval($ob),
+ 'translation' => '',
+ 'type' => $val
+ );
+
+ // RFC 4466: NAMESPACE extensions
+ while (($ext = $data->next()) !== false) {
+ switch (strtoupper($ext)) {
+ case 'TRANSLATION':
+ // RFC 5255 [3.4] - TRANSLATION extension
+ $data->next();
+ $c[strval($ob)]['translation'] = $data->next();
+ $data->next();
+ break;
+ }
+ }
+ }
+ }
+
+ $pipeline->data['namespace'] = $c;
+ }
+
+ /**
+ */
+ public function alerts()
+ {
+ $alerts = empty($this->_temp['alerts'])
+ ? array()
+ : $this->_temp['alerts'];
+ $this->_temp['alerts'] = array();
+ return $alerts;
+ }
+
+ /**
+ */
+ protected function _login()
+ {
+ if (!empty($this->_temp['preauth'])) {
+ unset($this->_temp['preauth']);
+ return $this->_loginTasks();
+ }
+
+ $this->_connect();
+
+ $first_login = empty($this->_init['authmethod']);
+
+ // Switch to secure channel if using TLS.
+ if (!$this->isSecureConnection() &&
+ ($this->getParam('secure') == 'tls')) {
+ if ($first_login && !$this->queryCapability('STARTTLS')) {
+ // We should never hit this - STARTTLS is required pursuant
+ // to RFC 3501 [6.2.1].
+ throw new Horde_Imap_Client_Exception(
+ Horde_Imap_Client_Translation::t("Server does not support TLS connections."),
+ Horde_Imap_Client_Exception::LOGIN_TLSFAILURE
+ );
+ }
+
+ // Switch over to a TLS connection.
+ // STARTTLS returns no untagged response.
+ $this->_sendCmd($this->_Command('STARTTLS'));
+
+ if (!$this->_connection->startTls()) {
+ $this->logout();
+ throw new Horde_Imap_Client_Exception(
+ Horde_Imap_Client_Translation::t("Could not open secure TLS connection to the IMAP server."),
+ Horde_Imap_Client_Exception::LOGIN_TLSFAILURE
+ );
+ }
+
+ if ($first_login) {
+ // Expire cached CAPABILITY information (RFC 3501 [6.2.1])
+ $this->_setInit('capability');
+
+ // Reset language (RFC 5255 [3.1])
+ $this->_setInit('lang');
+ }
+
+ // Set language if using imapproxy
+ if (!empty($this->_init['imapproxy'])) {
+ $this->setLanguage();
+ }
+ }
+
+ if ($first_login) {
+ $imap_auth_mech = array();
+
+ $auth_methods = $this->queryCapability('AUTH');
+ if (!empty($auth_methods)) {
+ // Add SASL methods. Prefer CRAM-MD5 over DIGEST-MD5, as the
+ // latter has been obsoleted (RFC 6331).
+ $imap_auth_mech = array_intersect(array('CRAM-MD5', 'DIGEST-MD5'), $auth_methods);
+
+ // Next, try 'PLAIN' authentication.
+ if (in_array('PLAIN', $auth_methods)) {
+ $imap_auth_mech[] = 'PLAIN';
+ }
+ }
+
+ // Fall back to 'LOGIN' if available.
+ if (!$this->queryCapability('LOGINDISABLED')) {
+ $imap_auth_mech[] = 'LOGIN';
+ }
+
+ if (empty($imap_auth_mech)) {
+ throw new Horde_Imap_Client_Exception(
+ Horde_Imap_Client_Translation::t("No supported IMAP authentication method could be found."),
+ Horde_Imap_Client_Exception::LOGIN_NOAUTHMETHOD
+ );
+ }
+
+ /* Use MD5 authentication first, if available. But no need to use
+ * special authentication if we are already using an encrypted
+ * connection. */
+ if ($this->isSecureConnection()) {
+ $imap_auth_mech = array_reverse($imap_auth_mech);
+ }
+ } else {
+ $imap_auth_mech = array($this->_init['authmethod']);
+ }
+
+ $login_err = null;
+
+ foreach ($imap_auth_mech as $method) {
+ try {
+ $resp = $this->_tryLogin($method);
+ $data = $resp->data;
+ $this->_setInit('authmethod', $method);
+ unset($this->_temp['referralcount']);
+ } catch (Horde_Imap_Client_Exception_ServerResponse $e) {
+ $data = $e->resp_data;
+ if (isset($data['loginerr'])) {
+ $login_err = $data['loginerr'];
+ }
+ $resp = false;
+ } catch (Horde_Imap_Client_Exception $e) {
+ $resp = false;
+ }
+
+ // Check for login referral (RFC 2221) response - can happen for
+ // an OK, NO, or BYE response.
+ if (isset($data['referral'])) {
+ foreach (array('hostspec', 'port', 'username') as $val) {
+ if (!is_null($data['referral']->$val)) {
+ $this->setParam($val, $data['referral']->$val);
+ }
+ }
+
+ if (!is_null($data['referral']->auth)) {
+ $this->_setInit('authmethod', $data['referral']->auth);
+ }
+
+ if (!isset($this->_temp['referralcount'])) {
+ $this->_temp['referralcount'] = 0;
+ }
+
+ // RFC 2221 [3] - Don't follow more than 10 levels of referral
+ // without consulting the user.
+ if (++$this->_temp['referralcount'] < 10) {
+ $this->logout();
+ $this->_setInit('capability');
+ $this->_setInit('namespace', array());
+ return $this->login();
+ }
+
+ unset($this->_temp['referralcount']);
+ }
+
+ if ($resp) {
+ return $this->_loginTasks($first_login, $resp->data);
+ }
+ }
+
+ /* Try again from scratch if authentication failed in an established,
+ * previously-authenticated object. */
+ if (!empty($this->_init['authmethod'])) {
+ $this->_setInit();
+ try {
+ return $this->login();
+ } catch (Horde_Imap_Client_Exception $e) {}
+ }
+
+ /* Default to AUTHENTICATIONFAILED error (see RFC 5530[3]). */
+ if (is_null($login_err)) {
+ throw new Horde_Imap_Client_Exception(
+ Horde_Imap_Client_Translation::t("Mail server denied authentication."),
+ Horde_Imap_Client_Exception::LOGIN_AUTHENTICATIONFAILED
+ );
+ }
+
+ throw $login_err;
+ }
+
+ /**
+ * Connects to the IMAP server.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ protected function _connect()
+ {
+ if (!is_null($this->_connection)) {
+ return;
+ }
+
+ $this->_connection = new Horde_Imap_Client_Socket_Connection_Socket($this, $this->_debug);
+
+ // If we already have capability information, don't re-set with
+ // (possibly) limited information sent in the initial banner.
+ if (isset($this->_init['capability'])) {
+ $this->_temp['no_cap'] = true;
+ }
+
+ /* Get greeting information. This is untagged so we need to specially
+ * deal with it here. */
+ try {
+ $this->_getLine($this->_pipeline());
+ } catch (Horde_Imap_Client_Exception_ServerResponse $e) {
+ if ($e->status == Horde_Imap_Client_Interaction_Server::BYE) {
+ /* Server is explicitly rejecting our connection (RFC 3501
+ * [7.1.5]). */
+ $e->setMessage(Horde_Imap_Client_Translation::t("Server rejected connection."));
+ $e->setCode(Horde_Imap_Client_Exception::SERVER_CONNECT);
+ }
+ throw $e;
+ }
+
+ // Check for IMAP4rev1 support
+ if (!$this->queryCapability('IMAP4REV1')) {
+ throw new Horde_Imap_Client_Exception(
+ Horde_Imap_Client_Translation::t("The mail server does not support IMAP4rev1 (RFC 3501)."),
+ Horde_Imap_Client_Exception::SERVER_CONNECT
+ );
+ }
+
+ // Set language if NOT using imapproxy
+ if (empty($this->_init['imapproxy'])) {
+ if ($this->queryCapability('XIMAPPROXY')) {
+ $this->_setInit('imapproxy', true);
+ } else {
+ $this->setLanguage();
+ }
+ }
+
+ // If pre-authenticated, we need to do all login tasks now.
+ if (!empty($this->_temp['preauth'])) {
+ $this->login();
+ }
+ }
+
+ /**
+ * Authenticate to the IMAP server.
+ *
+ * @param string $method IMAP login method.
+ *
+ * @return Horde_Imap_Client_Interaction_Pipeline Pipeline object.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ protected function _tryLogin($method)
+ {
+ $username = $this->getParam('username');
+ $password = $this->getParam('password');
+
+ switch ($method) {
+ case 'CRAM-MD5':
+ case 'CRAM-SHA1':
+ case 'CRAM-SHA256':
+ // RFC 2195: CRAM-MD5
+ // CRAM-SHA1 & CRAM-SHA256 supported by Courier SASL library
+
+ // Need $args because PHP 5.3 doesn't allow access to $this in
+ // anonymous functions.
+ $args = array(
+ $username,
+ strtolower(substr($method, 5)),
+ $password
+ );
+
+ $cmd = $this->_command('AUTHENTICATE')->add(array(
+ $method,
+ new Horde_Imap_Client_Interaction_Command_Continuation(function($ob) use ($args) {
+ return new Horde_Imap_Client_Data_Format_List(
+ base64_encode($args[0] . ' ' . hash_hmac($args[1], base64_decode($ob->token->current()), $args[2], false))
+ );
+ })
+ ));
+ $cmd->debug = sprintf('[AUTHENTICATE %s Command - username: %s]', $method, $username);
+ break;
+
+ case 'DIGEST-MD5':
+ // RFC 2831/4422; obsoleted by RFC 6331
+
+ // Need $args because PHP 5.3 doesn't allow access to $this in
+ // anonymous functions.
+ $args = array(
+ $username,
+ $password,
+ $this->getParam('hostspec')
+ );
+
+ $cmd = $this->_command('AUTHENTICATE')->add(array(
+ $method,
+ new Horde_Imap_Client_Interaction_Command_Continuation(function($ob) use ($args) {
+ return new Horde_Imap_Client_Data_Format_List(
+ base64_encode(new Horde_Imap_Client_Auth_DigestMD5(
+ $args[0],
+ $args[1],
+ base64_decode($ob->token->current()),
+ $args[2],
+ 'imap'
+ ))
+ );
+ }),
+ new Horde_Imap_Client_Interaction_Command_Continuation(function($ob) {
+ if (strpos(base64_decode($ob->token->current()), 'rspauth=') === false) {
+ throw new Horde_Imap_Client_Exception(
+ Horde_Imap_Client_Translation::t("Unexpected response from server when authenticating."),
+ Horde_Imap_Client_Exception::SERVER_CONNECT
+ );
+ }
+
+ return new Horde_Imap_Client_Data_Format_List();
+ })
+ ));
+ $cmd->debug = sprintf('[AUTHENTICATE DIGEST-MD5 Command - username: %s]', $username);
+ break;
+
+ case 'LOGIN':
+ $cmd = $this->_command('LOGIN')->add(array(
+ new Horde_Imap_Client_Data_Format_Astring($username),
+ new Horde_Imap_Client_Data_Format_Astring($password)
+ ));
+ $cmd->debug = sprintf('[LOGIN Command - username: %s]', $username);
+ break;
+
+ case 'PLAIN':
+ // RFC 2595/4616 - PLAIN SASL mechanism
+ $auth = base64_encode(implode("\0", array(
+ $username,
+ $username,
+ $password
+ )));
+ $cmd = $this->_command('AUTHENTICATE')->add('PLAIN');
+
+ if ($this->queryCapability('SASL-IR')) {
+ // IMAP Extension for SASL Initial Client Response (RFC 4959)
+ $cmd->add($auth);
+ $cmd->debug = sprintf('[SASL-IR AUTHENTICATE Command - username: %s]', $username);
+ } else {
+ $cmd->add(new Horde_Imap_Client_Interaction_Command_Continuation(function($ob) use ($auth) {
+ return new Horde_Imap_Client_Data_Format_List($auth);
+ }));
+ $cmd->debug = sprintf('[AUTHENTICATE Command - username: %s]', $username);
+ }
+ break;
+
+ default:
+ throw new Horde_Imap_Client_Exception(
+ sprintf(Horde_Imap_Client_Translation::t("Unknown authentication method: %s"), $method),
+ Horde_Imap_Client_Exception::SERVER_CONNECT
+ );
+ }
+
+ $pipeline = $this->_pipeline($cmd);
+
+ /* Set a flag indicating whether we have received a CAPABILITY
+ * response after we successfully login. Since capabilities may
+ * be different after login, we need to merge this information into
+ * the current CAPABILITY array (since some servers, e.g. Cyrus,
+ * may not include authentication capabilities that are still
+ * needed in the event this object is eventually serialized). */
+ $pipeline->data['in_login'] = true;
+
+ return $this->_sendCmd($pipeline);
+ }
+
+ /**
+ * Perform login tasks.
+ *
+ * @param boolean $firstlogin Is this the first login?
+ * @param array $resp The data response from the login command.
+ * May include:
+ * - logincapset: (boolean) True if CAPABILITY sent after login.
+ * - proxyreuse: (boolean) True if re-used connection via imapproxy.
+ *
+ * @return boolean True if global login tasks should be performed.
+ */
+ protected function _loginTasks($firstlogin = true, array $resp = array())
+ {
+ /* If reusing an imapproxy connection, no need to do any of these
+ * login tasks again. */
+ if (!$firstlogin && !empty($resp['proxyreuse'])) {
+ if (isset($this->_init['enabled'])) {
+ $this->_temp['enabled'] = $this->_init['enabled'];
+ }
+
+ // If we have not yet set the language, set it now.
+ if (!isset($this->_init['lang'])) {
+ $this->_temp['lang_queue'] = true;
+ $this->setLanguage();
+ unset($this->_temp['lang_queue']);
+ }
+ return false;
+ }
+
+ /* If we logged in for first time, and server did not return
+ * capability information, we need to mark for retrieval. */
+ if ($firstlogin && empty($resp['capabilities_set'])) {
+ $this->_setInit('capability');
+ }
+
+ $this->_temp['lang_queue'] = true;
+ $this->setLanguage();
+ unset($this->_temp['lang_queue']);
+
+ /* Only active QRESYNC/CONDSTORE if caching is enabled. */
+ if ($this->_initCache()) {
+ if ($this->queryCapability('QRESYNC')) {
+ $this->_enable(array('QRESYNC'));
+ } elseif ($this->queryCapability('CONDSTORE')) {
+ $this->_enable(array('CONDSTORE'));
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ */
+ protected function _logout()
+ {
+ if (empty($this->_temp['logout'])) {
+ /* If using imapproxy, force sending these commands, since they
+ * may not be sent again if they are (likely) initialization
+ * commands. */
+ if (!empty($this->_cmdQueue) &&
+ !empty($this->_init['imapproxy'])) {
+ $this->_sendCmd($this->_pipeline());
+ }
+
+ $this->_temp['logout'] = true;
+ try {
+ $this->_sendCmd($this->_command('LOGOUT'));
+ } catch (Horde_Imap_Client_Exception_ServerResponse $e) {
+ // Ignore server errors
+ }
+ unset($this->_temp['logout']);
+ }
+ }
+
+ /**
+ */
+ protected function _sendID($info)
+ {
+ $cmd = $this->_command('ID');
+
+ if (empty($info)) {
+ $cmd->add(new Horde_Imap_Client_Data_Format_Nil());
+ } else {
+ $tmp = new Horde_Imap_Client_Data_Format_List();
+ foreach ($info as $key => $val) {
+ $tmp->add(array(
+ new Horde_Imap_Client_Data_Format_String(strtolower($key)),
+ new Horde_Imap_Client_Data_Format_Nstring($val)
+ ));
+ }
+ $cmd->add($tmp);
+ }
+
+ $this->_temp['id'] = $this->_sendCmd($cmd)->data['id'];
+ }
+
+ /**
+ * Parse an ID response (RFC 2971 [3.2]).
+ *
+ * @param Horde_Imap_Client_Interaction_Pipeline $pipeline Pipeline
+ * object.
+ * @param Horde_Imap_Client_Tokenize $data The server response.
+ */
+ protected function _parseID(
+ Horde_Imap_Client_Interaction_Pipeline $pipeline,
+ Horde_Imap_Client_Tokenize $data
+ )
+ {
+ $ids = array();
+
+ if (!is_null($data->next())) {
+ while (($curr = $data->next()) !== false) {
+ if (!is_null($id = $data->next())) {
+ $ids[$curr] = $id;
+ }
+ }
+ }
+
+ $pipeline->data['id'] = $ids;
+ }
+
+ /**
+ */
+ protected function _getID()
+ {
+ if (!isset($this->_temp['id'])) {
+ $this->sendID();
+ }
+
+ return $this->_temp['id'];
+ }
+
+ /**
+ */
+ protected function _setLanguage($langs)
+ {
+ $cmd = $this->_command('LANGUAGE');
+ foreach ($langs as $lang) {
+ $cmd->add(new Horde_Imap_Client_Data_Format_Astring($lang));
+ }
+
+ if (!empty($this->_temp['lang_queue'])) {
+ $this->_cmdQueue[] = $cmd;
+ return array();
+ }
+
+ try {
+ $this->_sendCmd($cmd);
+ } catch (Horde_Imap_Client_Exception $e) {
+ $this->_setInit('lang', false);
+ return null;
+ }
+
+ return $this->_init['lang'];
+ }
+
+ /**
+ */
+ protected function _getLanguage($list)
+ {
+ if (!$list) {
+ return empty($this->_init['lang'])
+ ? null
+ : $this->_init['lang'];
+ }
+
+ if (!isset($this->_init['langavail'])) {
+ try {
+ $this->_sendCmd($this->_command('LANGUAGE'));
+ } catch (Horde_Imap_Client_Exception $e) {
+ $this->_setInit('langavail', array());
+ }
+ }
+
+ return $this->_init['langavail'];
+ }
+
+ /**
+ * Parse a LANGUAGE response (RFC 5255 [3.3]).
+ *
+ * @param Horde_Imap_Client_Tokenize $data The server response.
+ */
+ protected function _parseLanguage(Horde_Imap_Client_Tokenize $data)
+ {
+ $lang_list = $data->flushIterator();
+
+ if (count($lang_list) == 1) {
+ // This is the language that was set.
+ $this->_setInit('lang', reset($lang_list));
+ } else {
+ // These are the languages that are available.
+ $this->_setInit('langavail', $lang_list);
+ }
+ }
+
+ /**
+ * Enable an IMAP extension (see RFC 5161).
+ *
+ * @param array $exts The extensions to enable.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ protected function _enable($exts)
+ {
+ if ($this->queryCapability('ENABLE')) {
+ // Only enable non-enabled extensions.
+ $exts = array_diff($exts, array_keys($this->_temp['enabled']));
+ if (!empty($exts)) {
+ $this->_cmdQueue[] = $this->_command('ENABLE')->add($exts);
+ $this->_enabled($exts, 1);
+ }
+ }
+ }
+
+ /**
+ * Parse an ENABLED response (RFC 5161 [3.2]).
+ *
+ * @param Horde_Imap_Client_Tokenize $data The server response.
+ */
+ protected function _parseEnabled(Horde_Imap_Client_Tokenize $data)
+ {
+ $this->_enabled($data->flushIterator(), 2);
+ }
+
+ /**
+ */
+ protected function _enabled($exts, $status)
+ {
+ parent::_enabled($exts, $status);
+
+ if (($status == 2) && !empty($this->_init['imapproxy'])) {
+ $this->_setInit('enabled', $this->_temp['enabled']);
+ }
+ }
+
+ /**
+ */
+ protected function _openMailbox(Horde_Imap_Client_Mailbox $mailbox, $mode)
+ {
+ $qresync = isset($this->_temp['enabled']['QRESYNC']);
+
+ $cmd = $this->_command(
+ ($mode == Horde_Imap_Client::OPEN_READONLY) ? 'EXAMINE' : 'SELECT'
+ )->add(
+ new Horde_Imap_Client_Data_Format_Mailbox($mailbox)
+ );
+ $pipeline = $this->_pipeline($cmd);
+
+ /* If QRESYNC is available, synchronize the mailbox. */
+ if ($qresync) {
+ $this->_initCache();
+ $md = $this->_cache->getMetaData($mailbox, null, array(self::CACHE_MODSEQ, 'uidvalid'));
+
+ if (isset($md[self::CACHE_MODSEQ])) {
+ if ($uids = $this->_cache->get($mailbox)) {
+ $uids = $this->getIdsOb($uids);
+
+ /* Check for extra long UID string. Assume that any
+ * server that can handle QRESYNC can also handle long
+ * input strings (at least 8 KB), so 7 KB is as good as
+ * any guess as to an upper limit. If this occurs, provide
+ * a range string (min -> max) instead. */
+ if (strlen($uid_str = strval($uids)) > 7000) {
+ $uid_str = $uids->range_string;
+ }
+ } else {
+ $uid_str = null;
+ }
+
+ /* Several things can happen with a QRESYNC:
+ * 1. UIDVALIDITY may have changed. If so, we need to expire
+ * the cache immediately (done below).
+ * 2. NOMODSEQ may have been returned. We can keep current
+ * message cache data but won't be able to do flag caching.
+ * 3. VANISHED/FETCH information was returned. These responses
+ * will have already been handled by those response handlers.
+ * 4. We are already synced with the local server in which
+ * case it acts like a normal EXAMINE/SELECT. */
+ $cmd->add(new Horde_Imap_Client_Data_Format_List(array(
+ 'QRESYNC',
+ new Horde_Imap_Client_Data_Format_List(array_filter(array(
+ $md['uidvalid'],
+ $md[self::CACHE_MODSEQ],
+ $uid_str
+ )))
+ )));
+ }
+
+ /* Let the 'CLOSED' response code handle mailbox switching if
+ * QRESYNC is active. */
+ if ($this->_selected) {
+ $pipeline->data['qresyncmbox'] = array($mailbox, $mode);
+ } else {
+ $this->_changeSelected($mailbox, $mode);
+ }
+ } else {
+ if (!isset($this->_temp['enabled']['CONDSTORE']) &&
+ $this->_initCache() &&
+ $this->queryCapability('CONDSTORE')) {
+ /* Activate CONDSTORE now if ENABLE is not available. */
+ $cmd->add(new Horde_Imap_Client_Data_Format_List('CONDSTORE'));
+ $this->_enabled(array('CONDSTORE'), 2);
+ }
+
+ $this->_changeSelected($mailbox, $mode);
+ }
+
+ try {
+ $this->_sendCmd($pipeline);
+ } catch (Horde_Imap_Client_Exception_ServerResponse $e) {
+ // An EXAMINE/SELECT failure with a return of 'NO' will cause the
+ // current mailbox to be unselected.
+ if ($e->status == Horde_Imap_Client_Interaction_Server::NO) {
+ $this->_changeSelected(null);
+ $this->_mode = 0;
+ if (!$e->getCode()) {
+ throw new Horde_Imap_Client_Exception(
+ sprintf(Horde_Imap_Client_Translation::t("Could not open mailbox \"%s\"."), $mailbox),
+ Horde_Imap_Client_Exception::MAILBOX_NOOPEN
+ );
+ }
+ }
+ throw $e;
+ }
+
+ if ($qresync) {
+ /* Mailbox is fully sync'd. */
+ $this->_mailboxOb()->sync = true;
+ }
+ }
+
+ /**
+ */
+ protected function _createMailbox(Horde_Imap_Client_Mailbox $mailbox, $opts)
+ {
+ $cmd = $this->_command('CREATE')->add(
+ new Horde_Imap_Client_Data_Format_Mailbox($mailbox)
+ );
+
+ if (!empty($opts['special_use'])) {
+ $cmd->add(array(
+ 'USE',
+ new Horde_Imap_Client_Data_Format_List($opts['special_use'])
+ ));
+ }
+
+ // CREATE returns no untagged information (RFC 3501 [6.3.3])
+ $this->_sendCmd($cmd);
+ }
+
+ /**
+ */
+ protected function _deleteMailbox(Horde_Imap_Client_Mailbox $mailbox)
+ {
+ // Some IMAP servers will not allow a delete of a currently open
+ // mailbox.
+ if ($mailbox->equals($this->_selected)) {
+ $this->close();
+ }
+
+ $cmd = $this->_command('DELETE')->add(
+ new Horde_Imap_Client_Data_Format_Mailbox($mailbox)
+ );
+
+ try {
+ // DELETE returns no untagged information (RFC 3501 [6.3.4])
+ $this->_sendCmd($cmd);
+ } catch (Horde_Imap_Client_Exception $e) {
+ // Some IMAP servers won't allow a mailbox delete unless all
+ // messages in that mailbox are deleted.
+ $this->expunge($mailbox, array(
+ 'delete' => true
+ ));
+ $this->_sendCmd($cmd);
+ }
+ }
+
+ /**
+ */
+ protected function _renameMailbox(Horde_Imap_Client_Mailbox $old,
+ Horde_Imap_Client_Mailbox $new)
+ {
+ // Some IMAP servers will not allow a rename of a currently open
+ // mailbox.
+ if ($old->equals($this->_selected)) {
+ $this->close();
+ }
+
+ // RENAME returns no untagged information (RFC 3501 [6.3.5])
+ $this->_sendCmd(
+ $this->_command('RENAME')->add(array(
+ new Horde_Imap_Client_Data_Format_Mailbox($old),
+ new Horde_Imap_Client_Data_Format_Mailbox($new)
+ ))
+ );
+ }
+
+ /**
+ */
+ protected function _subscribeMailbox(Horde_Imap_Client_Mailbox $mailbox,
+ $subscribe)
+ {
+ // SUBSCRIBE/UNSUBSCRIBE returns no untagged information (RFC 3501
+ // [6.3.6 & 6.3.7])
+ $this->_sendCmd(
+ $this->_command(
+ $subscribe ? 'SUBSCRIBE' : 'UNSUBSCRIBE'
+ )->add(
+ new Horde_Imap_Client_Data_Format_Mailbox($mailbox)
+ )
+ );
+ }
+
+ /**
+ */
+ protected function _listMailboxes($pattern, $mode, $options)
+ {
+ // RFC 5258 [3.1]: Use LSUB for MBOX_SUBSCRIBED if no other server
+ // return options are specified.
+ if (($mode == Horde_Imap_Client::MBOX_SUBSCRIBED) &&
+ empty($options['attributes']) &&
+ empty($options['children']) &&
+ empty($options['recursivematch']) &&
+ empty($options['remote']) &&
+ empty($options['special_use']) &&
+ empty($options['status'])) {
+ return $this->_getMailboxList(
+ $pattern,
+ Horde_Imap_Client::MBOX_SUBSCRIBED,
+ array(
+ 'delimiter' => !empty($options['delimiter']),
+ 'flat' => !empty($options['flat']),
+ 'no_listext' => true
+ )
+ );
+ }
+
+ // Get the list of subscribed/unsubscribed mailboxes. Since LSUB is
+ // not guaranteed to have correct attributes, we must use LIST to
+ // ensure we receive the correct information.
+ if (($mode != Horde_Imap_Client::MBOX_ALL) &&
+ !$this->queryCapability('LIST-EXTENDED')) {
+ $subscribed = $this->_getMailboxList($pattern, Horde_Imap_Client::MBOX_SUBSCRIBED, array('flat' => true));
+
+ // If mode is subscribed, and 'flat' option is true, we can
+ // return now.
+ if (($mode == Horde_Imap_Client::MBOX_SUBSCRIBED) &&
+ !empty($options['flat'])) {
+ return $subscribed;
+ }
+ } else {
+ $subscribed = null;
+ }
+
+ return $this->_getMailboxList($pattern, $mode, $options, $subscribed);
+ }
+
+ /**
+ * Obtain a list of mailboxes.
+ *
+ * @param array $pattern The mailbox search pattern(s).
+ * @param integer $mode Which mailboxes to return.
+ * @param array $options Additional options. 'no_listext' will skip
+ * using the LIST-EXTENDED capability.
+ * @param array $subscribed A list of subscribed mailboxes.
+ *
+ * @return array See listMailboxes(().
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ protected function _getMailboxList($pattern, $mode, $options,
+ $subscribed = null)
+ {
+ $check = (($mode != Horde_Imap_Client::MBOX_ALL) && !is_null($subscribed));
+
+ // Setup entry for use in _parseList().
+ $pipeline = $this->_pipeline();
+ $pipeline->data['mailboxlist'] = array(
+ 'check' => $check,
+ 'ext' => false,
+ 'options' => $options,
+ 'subexist' => ($mode == Horde_Imap_Client::MBOX_SUBSCRIBED_EXISTS),
+ 'subscribed' => ($check ? array_flip(array_map('strval', $subscribed)) : null)
+ );
+ $pipeline->data['listresponse'] = array();
+
+ $cmds = array();
+ $return_opts = new Horde_Imap_Client_Data_Format_List();
+
+ if ($this->queryCapability('LIST-EXTENDED') &&
+ empty($options['no_listext'])) {
+ $cmd = $this->_command('LIST');
+ $pipeline->data['mailboxlist']['ext'] = true;
+
+ $select_opts = new Horde_Imap_Client_Data_Format_List();
+
+ if (($mode == Horde_Imap_Client::MBOX_SUBSCRIBED) ||
+ ($mode == Horde_Imap_Client::MBOX_SUBSCRIBED_EXISTS)) {
+ $select_opts->add('SUBSCRIBED');
+ $return_opts->add('SUBSCRIBED');
+ }
+
+ if (!empty($options['remote'])) {
+ $select_opts->add('REMOTE');
+ }
+
+ if (!empty($options['recursivematch'])) {
+ $select_opts->add('RECURSIVEMATCH');
+ }
+
+ $cmd->add(array(
+ $select_opts,
+ ''
+ ));
+
+ $tmp = new Horde_Imap_Client_Data_Format_List();
+ foreach ($pattern as $val) {
+ $tmp->add(new Horde_Imap_Client_Data_Format_ListMailbox($val));
+ }
+ $cmd->add($tmp);
+
+ if (!empty($options['children'])) {
+ $return_opts->add('CHILDREN');
+ }
+
+ if (!empty($options['special_use'])) {
+ $return_opts->add('SPECIAL-USE');
+ }
+
+ $cmds[] = $cmd;
+ } else {
+ foreach ($pattern as $val) {
+ $cmds[] = $this->_command(
+ ($mode == Horde_Imap_Client::MBOX_SUBSCRIBED) ? 'LSUB' : 'LIST'
+ )->add(array(
+ '',
+ new Horde_Imap_Client_Data_Format_ListMailbox($val)
+ ));
+ }
+ }
+
+ /* LIST-STATUS does NOT depend on LIST-EXTENDED. */
+ if (!empty($options['status']) &&
+ $this->queryCapability('LIST-STATUS')) {
+ $available_status = array(
+ Horde_Imap_Client::STATUS_MESSAGES,
+ Horde_Imap_Client::STATUS_RECENT,
+ Horde_Imap_Client::STATUS_UIDNEXT,
+ Horde_Imap_Client::STATUS_UIDVALIDITY,
+ Horde_Imap_Client::STATUS_UNSEEN,
+ Horde_Imap_Client::STATUS_HIGHESTMODSEQ
+ );
+
+ $status_opts = array();
+ foreach (array_intersect($this->_statusFields, $available_status) as $key => $val) {
+ if ($options['status'] & $val) {
+ $status_opts[] = $key;
+ }
+ }
+
+ if (count($status_opts)) {
+ $return_opts->add(array(
+ 'STATUS',
+ new Horde_Imap_Client_Data_Format_List(
+ array_map('strtoupper', $status_opts)
+ )
+ ));
+ }
+ }
+
+ foreach ($cmds as $val) {
+ if (count($return_opts)) {
+ $val->add(array(
+ 'RETURN',
+ $return_opts
+ ));
+ }
+
+ $pipeline->add($val);
+ }
+
+ try {
+ $lr = $this->_sendCmd($pipeline)->data['listresponse'];
+ } catch (Horde_Imap_Client_Exception_ServerResponse $e) {
+ /* Archiveopteryx 3.1.3 can't process empty list-select-opts list.
+ * Retry using base IMAP4rev1 functionality. */
+ if (($e->status == Horde_Imap_Client_Interaction_Server::BAD) &&
+ $this->queryCapability('LIST-EXTENDED')) {
+ $this->_unsetCapability('LIST-EXTENDED');
+ return $this->_listMailboxes($pattern, $mode, $options);
+ }
+
+ throw $e;
+ }
+
+ if (!empty($options['flat'])) {
+ return array_values($lr);
+ }
+
+ /* Add in STATUS return, if needed. */
+ if (!empty($options['status'])) {
+ foreach ($pattern as $val) {
+ $val_utf8 = Horde_Imap_Client_Utf7imap::Utf7ImapToUtf8($val);
+ if (isset($lr[$val_utf8])) {
+ $lr[$val_utf8]['status'] = $this->_prepareStatusResponse($status_opts, $val_utf8);
+ }
+ }
+ }
+
+ return $lr;
+ }
+
+ /**
+ * Parse a LIST/LSUB response (RFC 3501 [7.2.2 & 7.2.3]).
+ *
+ * @param Horde_Imap_Client_Interaction_Pipeline $pipeline Pipeline
+ * object.
+ * @param Horde_Imap_Client_Tokenize $data The server response (includes
+ * type as first token).
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ protected function _parseList(
+ Horde_Imap_Client_Interaction_Pipeline $pipeline,
+ Horde_Imap_Client_Tokenize $data
+ )
+ {
+ $data->next();
+ $attr = $data->flushIterator();
+ $delimiter = $data->next();
+ $mbox = Horde_Imap_Client_Mailbox::get($data->next(), true);
+ $ml = $pipeline->data['mailboxlist'];
+
+ if ($ml['check'] &&
+ $ml['subexist'] &&
+ // subscribed list is in UTF-8
+ !isset($ml['subscribed'][strval($mbox)])) {
+ return;
+ } elseif ((!$ml['check'] && $ml['subexist']) ||
+ (empty($ml['options']['flat']) &&
+ !empty($ml['options']['attributes']))) {
+ $attr = array_flip(array_map('strtolower', $attr));
+ if ($ml['subexist'] &&
+ !$ml['check'] &&
+ isset($attr['\\nonexistent'])) {
+ return;
+ }
+ }
+
+ if (empty($ml['options']['flat'])) {
+ $tmp = array(
+ 'mailbox' => $mbox
+ );
+
+ if (!empty($ml['options']['attributes'])) {
+ /* RFC 5258 [3.4]: inferred attributes. */
+ if ($ml['ext']) {
+ if (isset($attr['\\noinferiors'])) {
+ $attr['\\hasnochildren'] = 1;
+ }
+ if (isset($attr['\\nonexistent'])) {
+ $attr['\\noselect'] = 1;
+ }
+ }
+ $tmp['attributes'] = array_keys($attr);
+ }
+ if (!empty($ml['options']['delimiter'])) {
+ $tmp['delimiter'] = $delimiter;
+ }
+ if ($data->next() !== false) {
+ $tmp['extended'] = $data->flushIterator();
+ }
+ $pipeline->data['listresponse'][strval($mbox)] = $tmp;
+ } else {
+ $pipeline->data['listresponse'][] = $mbox;
+ }
+ }
+
+ /**
+ */
+ protected function _status($mboxes, $flags)
+ {
+ $out = $to_process = array();
+ $pipeline = $this->_pipeline();
+ $unseen_flags = array(
+ Horde_Imap_Client::STATUS_FIRSTUNSEEN,
+ Horde_Imap_Client::STATUS_UNSEEN
+ );
+
+ foreach ($mboxes as $mailbox) {
+ /* If FLAGS/PERMFLAGS/UIDNOTSTICKY/FIRSTUNSEEN are needed, we must
+ * do a SELECT/EXAMINE to get this information (data will be
+ * caught in the code below). */
+ if (($flags & Horde_Imap_Client::STATUS_FIRSTUNSEEN) ||
+ ($flags & Horde_Imap_Client::STATUS_FLAGS) ||
+ ($flags & Horde_Imap_Client::STATUS_PERMFLAGS) ||
+ ($flags & Horde_Imap_Client::STATUS_UIDNOTSTICKY)) {
+ $this->openMailbox($mailbox);
+ }
+
+ $mbox_ob = $this->_mailboxOb($mailbox);
+ $data = $query = array();
+
+ foreach ($this->_statusFields as $key => $val) {
+ if (!($val & $flags)) {
+ continue;
+ }
+
+ if ($val == Horde_Imap_Client::STATUS_HIGHESTMODSEQ) {
+ /* Don't include modseq returns if server does not support
+ * it. */
+ if (!$this->queryCapability('CONDSTORE')) {
+ continue;
+ }
+
+ /* Even though CONDSTORE is available, it may not yet have
+ * been enabled. */
+ if (!isset($this->_temp['enabled']['CONDSTORE'])) {
+ $this->_enabled(array('CONDSTORE'), 2);
+ }
+ }
+
+ if ($mailbox->equals($this->_selected)) {
+ if (!is_null($tmp = $mbox_ob->getStatus($val))) {
+ $data[$key] = $tmp;
+ } elseif (($val == Horde_Imap_Client::STATUS_UIDNEXT) &&
+ ($flags & Horde_Imap_Client::STATUS_UIDNEXT_FORCE)) {
+ /* UIDNEXT is not mandatory. */
+ if ($mbox_ob->getStatus(Horde_Imap_Client::STATUS_MESSAGES) == 0) {
+ $data[$key] = 0;
+ } else {
+ $fquery = new Horde_Imap_Client_Fetch_Query();
+ $fquery->uid();
+ $fetch_res = $this->fetch($this->_selected, $fquery, array(
+ 'ids' => $this->getIdsOb(Horde_Imap_Client_Ids::LARGEST)
+ ));
+ $data[$key] = $fetch_res->first()->getUid() + 1;
+ }
+ } elseif (in_array($val, $unseen_flags)) {
+ /* RFC 3501 [6.3.1] - FIRSTUNSEEN information is not
+ * mandatory. If missing in EXAMINE/SELECT results, we
+ * need to do a search. An UNSEEN count also requires
+ * a search. */
+ $squery = new Horde_Imap_Client_Search_Query();
+ $squery->flag(Horde_Imap_Client::FLAG_SEEN, false);
+ $search = $this->search($mailbox, $squery, array(
+ 'results' => array(
+ Horde_Imap_Client::SEARCH_RESULTS_MIN,
+ Horde_Imap_Client::SEARCH_RESULTS_COUNT
+ ),
+ 'sequence' => true
+ ));
+
+ $mbox_ob->setStatus(Horde_Imap_Client::STATUS_FIRSTUNSEEN, $search['min']);
+ $mbox_ob->setStatus(Horde_Imap_Client::STATUS_UNSEEN, $search['count']);
+
+ $data[$key] = $mbox_ob->getStatus($val);
+ }
+ } else {
+ $query[] = $key;
+ }
+ }
+
+ $out[strval($mailbox)] = $data;
+
+ if (count($query)) {
+ $pipeline->add(
+ $this->_command('STATUS')->add(array(
+ new Horde_Imap_Client_Data_Format_Mailbox($mailbox),
+ new Horde_Imap_Client_Data_Format_List(
+ array_map('strtoupper', $query)
+ )
+ ))
+ );
+ $to_process[] = array($query, $mailbox);
+ }
+ }
+
+ if (count($pipeline)) {
+ $this->_sendCmd($pipeline);
+
+ foreach ($to_process as $val) {
+ $out[strval($val[1])] += $this->_prepareStatusResponse($val[0], $val[1]);
+ }
+ }
+
+ return $out;
+ }
+
+ /**
+ * Parse a STATUS response (RFC 3501 [7.2.4], RFC 4551 [3.6])
+ *
+ * @param Horde_Imap_Client_Tokenize $data Token data
+ */
+ protected function _parseStatus(Horde_Imap_Client_Tokenize $data)
+ {
+ // Mailbox name is in UTF7-IMAP
+ $mbox_ob = $this->_mailboxOb(
+ Horde_Imap_Client_Mailbox::get($data->next(), true)
+ );
+
+ $data->next();
+
+ while (($k = $data->next()) !== false) {
+ $mbox_ob->setStatus(
+ $this->_statusFields[strtolower($k)],
+ $data->next()
+ );
+ }
+ }
+
+ /**
+ * Prepares a status response for a mailbox.
+ *
+ * @param array $request The status keys to return.
+ * @param string $mailbox The mailbox to query.
+ */
+ protected function _prepareStatusResponse($request, $mailbox)
+ {
+ $mbox_ob = $this->_mailboxOb($mailbox);
+ $out = array();
+
+ foreach ($request as $val) {
+ $out[$val] = $mbox_ob->getStatus($this->_statusFields[$val]);
+ }
+
+ return $out;
+ }
+
+ /**
+ */
+ protected function _append(Horde_Imap_Client_Mailbox $mailbox, $data,
+ $options)
+ {
+ // Check for MULTIAPPEND extension (RFC 3502)
+ if ((count($data) > 1) && !$this->queryCapability('MULTIAPPEND')) {
+ $result = $this->getIdsOb();
+ foreach (array_keys($data) as $key) {
+ $res = $this->_append($mailbox, array($data[$key]), $options);
+ if (($res === true) || ($result === true)) {
+ $result = true;
+ } else {
+ $result->add($res);
+ }
+ }
+ return $result;
+ }
+
+ // Check for CATENATE extension (RFC 4469)
+ $catenate = $this->queryCapability('CATENATE');
+
+ $asize = 0;
+
+ $cmd = $this->_command('APPEND')->add(
+ new Horde_Imap_Client_Data_Format_Mailbox($mailbox)
+ );
+ $cmd->literal8 = true;
+
+ foreach (array_keys($data) as $key) {
+ if (!empty($data[$key]['flags'])) {
+ $tmp = new Horde_Imap_Client_Data_Format_List();
+ foreach ($data[$key]['flags'] as $val) {
+ /* Ignore recent flag. RFC 3501 [9]: flag definition */
+ if (strcasecmp($val, Horde_Imap_Client::FLAG_RECENT) !== 0) {
+ $tmp->add($val);
+ }
+ }
+ $cmd->add($tmp);
+ }
+
+ if (!empty($data[$key]['internaldate'])) {
+ $cmd->add(new Horde_Imap_Client_Data_Format_DateTime($data[$key]['internaldate']));
+ }
+
+ if (is_array($data[$key]['data'])) {
+ if ($catenate) {
+ $cmd->add('CATENATE');
+ $tmp = new Horde_Imap_Client_Data_Format_List();
+ } else {
+ $data_stream = new Horde_Stream_Temp();
+ }
+
+ reset($data[$key]['data']);
+ while (list(,$v) = each($data[$key]['data'])) {
+ switch ($v['t']) {
+ case 'text':
+ if ($catenate) {
+ $tmp->add(array(
+ 'TEXT',
+ $this->_appendData($v['v'], $asize)
+ ));
+ } else {
+ if (is_resource($v['v'])) {
+ rewind($v['v']);
+ }
+ $data_stream->add($v['v']);
+ }
+ break;
+
+ case 'url':
+ if ($catenate) {
+ $tmp->add(array(
+ 'URL',
+ new Horde_Imap_Client_Data_Format_Astring($v['v'])
+ ));
+ } else {
+ $data_stream->add($this->_convertCatenateUrl($v['v']));
+ }
+ break;
+ }
+ }
+
+ if ($catenate) {
+ $cmd->add($tmp);
+ } else {
+ $cmd->add($this->_appendData($data_stream->stream, $asize));
+ }
+ } else {
+ $cmd->add($this->_appendData($data[$key]['data'], $asize));
+ }
+ }
+
+ /* Although it is normally more efficient to use LITERAL+, disable if
+ * payload is over 0.5 MB because it allows the server to throw error
+ * before we potentially push a lot of data to server that would
+ * otherwise be ignored (see RFC 4549 [4.2.2.3]).
+ * Additionally, if using BINARY, since so many IMAP servers have
+ * issues with APPEND + BINARY, don't use LITERAL+ since servers may
+ * send BAD after initial command. */
+ $cmd->literalplus = (($asize < 524288) && !$this->queryCapability('BINARY'));
+
+ // If the mailbox is currently selected read-only, we need to close
+ // because some IMAP implementations won't allow an append. And some
+ // implementations don't support append on ANY open mailbox. Be safe
+ // and always make sure we are in a non-selected state.
+ $this->close();
+
+ try {
+ $resp = $this->_sendCmd($cmd);
+ } catch (Horde_Imap_Client_Exception $e) {
+ switch ($e->getCode()) {
+ case $e::CATENATE_BADURL:
+ case $e::CATENATE_TOOBIG:
+ /* Cyrus 2.4 (at least as of .14) has a broken CATENATE (see
+ * Bug #11111). Regardless, if CATENATE is broken, we can try
+ * to fallback to APPEND. */
+ $this->_unsetCapability('CATENATE');
+ return $this->_append($mailbox, $data, $options);
+
+ case $e::DISCONNECT:
+ /* Workaround broken literal8 on Cyrus. */
+ if ($this->queryCapability('BINARY')) {
+ // Need to re-login first before removing capability.
+ $this->login();
+ $this->_unsetCapability('BINARY');
+ return $this->_append($mailbox, $data, $options);
+ }
+ break;
+ }
+
+ if (!empty($options['create']) &&
+ !empty($e->resp_data['trycreate'])) {
+ $this->createMailbox($mailbox);
+ unset($options['create']);
+ return $this->_append($mailbox, $data, $options);
+ }
+
+ /* RFC 3516/4466 says we should be able to append binary data
+ * using literal8 "~{#} format", but it doesn't seem to work on
+ * all servers tried (UW-IMAP/Cyrus). Do a last-ditch check for
+ * broken BINARY and attempt to fix here. */
+ if ($this->queryCapability('BINARY') &&
+ ($e instanceof Horde_Imap_Client_Exception_ServerResponse)) {
+ switch ($e->status) {
+ case Horde_Imap_Client_Interaction_Server::BAD:
+ case Horde_Imap_Client_Interaction_Server::NO:
+ $this->_unsetCapability('BINARY');
+ return $this->_append($mailbox, $data, $options);
+ }
+ }
+
+ throw $e;
+ }
+
+ /* If we reach this point and have data in 'appenduid', UIDPLUS (RFC
+ * 4315) has done the dirty work for us. */
+ return isset($resp->data['appenduid'])
+ ? $resp->data['appenduid']
+ : true;
+ }
+
+ /**
+ * Prepares append message data for insertion into the IMAP command
+ * string.
+ *
+ * @param mixed $data Either a resource or a string.
+ * @param integer &$asize Total append size.
+ *
+ * @return Horde_Imap_Client_Data_Format_String The data object.
+ */
+ protected function _appendData($data, &$asize)
+ {
+ if (is_resource($data)) {
+ rewind($data);
+ }
+
+ $ob = new Horde_Imap_Client_Data_Format_String($data, array(
+ 'eol' => true,
+ 'skipscan' => true
+ ));
+
+ // APPEND data MUST be sent in a literal (RFC 3501 [6.3.11]).
+ $ob->forceLiteral();
+
+ $asize += $ob->length();
+
+ return $ob;
+ }
+
+ /**
+ * Converts a CATENATE URL to stream data.
+ *
+ * @param string $url The CATENATE URL.
+ *
+ * @return resource A stream containing the data.
+ */
+ protected function _convertCatenateUrl($url)
+ {
+ $e = $part = null;
+ $url = new Horde_Imap_Client_Url($url);
+
+ if (!is_null($url->mailbox) && !is_null($url->uid)) {
+ try {
+ $status_res = is_null($url->uidvalidity)
+ ? null
+ : $this->status($url->mailbox, Horde_Imap_Client::STATUS_UIDVALIDITY);
+
+ if (is_null($status_res) ||
+ ($status_res['uidvalidity'] == $url->uidvalidity)) {
+ if (!isset($this->_temp['catenate_ob'])) {
+ $this->_temp['catenate_ob'] = new Horde_Imap_Client_Socket_Catenate($this);
+ }
+ $part = $this->_temp['catenate_ob']->fetchFromUrl($url);
+ }
+ } catch (Horde_Imap_Client_Exception $e) {}
+ }
+
+ if (is_null($part)) {
+ $message = 'Bad IMAP URL given in CATENATE data: ' . strval($url);
+ if ($e) {
+ $message .= ' ' . $e->getMessage();
+ }
+
+ throw new InvalidArgumentException($message);
+ }
+
+ return $part;
+ }
+
+ /**
+ */
+ protected function _check()
+ {
+ // CHECK returns no untagged information (RFC 3501 [6.4.1])
+ $this->_sendCmd($this->_command('CHECK'));
+ }
+
+ /**
+ */
+ protected function _close($options)
+ {
+ if (empty($options['expunge'])) {
+ if ($this->queryCapability('UNSELECT')) {
+ // RFC 3691 defines 'UNSELECT' for precisely this purpose
+ $this->_sendCmd($this->_command('UNSELECT'));
+ } else {
+ // RFC 3501 [6.4.2]: to close a mailbox without expunge,
+ // select a non-existent mailbox. Selecting a null mailbox
+ // should do the trick.
+ try {
+ $this->_sendCmd($this->_command('SELECT')->add(''));
+ } catch (Horde_Imap_Client_Exception_ServerResponse $e) {
+ // Ignore error; it is expected.
+ }
+ }
+ } else {
+ // If caching, we need to know the UIDs being deleted, so call
+ // expunge() before calling close().
+ if ($this->_initCache(true)) {
+ $this->expunge($this->_selected);
+ }
+
+ // CLOSE returns no untagged information (RFC 3501 [6.4.2])
+ $this->_sendCmd($this->_command('CLOSE'));
+ }
+ }
+
+ /**
+ */
+ protected function _expunge($options)
+ {
+ $expunged_ob = $modseq = null;
+ $ids = $options['ids'];
+ $list_msgs = !empty($options['list']);
+ $uidplus = $this->queryCapability('UIDPLUS');
+ $unflag = array();
+ $use_cache = $this->_initCache(true);
+
+ if ($ids->all) {
+ if (!$uidplus && ($list_msgs || $use_cache)) {
+ $ids = $this->resolveIds($this->_selected, $ids, 2);
+ }
+ } elseif ($uidplus) {
+ /* If QRESYNC is not available, and we are returning the list of
+ * expunged messages (or we are caching), we have to make sure we
+ * have a mapping of Sequence -> UIDs. If we have QRESYNC, the
+ * server SHOULD return a VANISHED response with UIDs. However,
+ * even if the server returns EXPUNGEs instead, we can use
+ * vanished() to grab the list. */
+ unset($this->_temp['search_save']);
+ if (isset($this->_temp['enabled']['QRESYNC'])) {
+ $ids = $this->resolveIds($this->_selected, $ids, 1);
+ if ($list_msgs) {
+ $modseq = $this->_mailboxOb()->getStatus(Horde_Imap_Client::STATUS_HIGHESTMODSEQ);
+ }
+ } else {
+ $ids = $this->resolveIds($this->_selected, $ids, ($list_msgs || $use_cache) ? 2 : 1);
+ }
+ if (!empty($this->_temp['search_save'])) {
+ $ids = $this->getIdsOb(Horde_Imap_Client_Ids::SEARCH_RES);
+ }
+ } else {
+ /* Without UIDPLUS, need to temporarily unflag all messages marked
+ * as deleted but not a part of requested IDs to delete. Use NOT
+ * searches to accomplish this goal. */
+ $squery = new Horde_Imap_Client_Search_Query();
+ $squery->flag(Horde_Imap_Client::FLAG_DELETED, true);
+ $squery->ids($ids, true);
+
+ $s_res = $this->search($this->_selected, $squery, array(
+ 'results' => array(
+ Horde_Imap_Client::SEARCH_RESULTS_MATCH,
+ Horde_Imap_Client::SEARCH_RESULTS_SAVE
+ )
+ ));
+
+ $this->store($this->_selected, array(
+ 'ids' => empty($s_res['save']) ? $s_res['match'] : $this->getIdsOb(Horde_Imap_Client_Ids::SEARCH_RES),
+ 'remove' => array(Horde_Imap_Client::FLAG_DELETED)
+ ));
+
+ $unflag = $s_res['match'];
+ }
+
+ if ($list_msgs) {
+ $expunged_ob = $this->getIdsOb();
+ $this->_temp['expunged'] = $expunged_ob;
+ }
+
+ /* Always use UID EXPUNGE if available. */
+ if ($uidplus) {
+ /* We can only pipeline STORE w/ EXPUNGE if using UIDs and UIDPLUS
+ * is available. */
+ if (empty($options['delete'])) {
+ $pipeline = $this->_pipeline();
+ } else {
+ $pipeline = $this->_storeCmd(array(
+ 'add' => array(
+ Horde_Imap_Client::FLAG_DELETED
+ ),
+ 'ids' => $ids
+ ));
+ }
+
+ foreach ($ids->split(2000) as $val) {
+ $pipeline->add(
+ $this->_command('UID EXPUNGE')->add($val)
+ );
+ }
+
+ $resp = $this->_sendCmd($pipeline);
+ } else {
+ if (!empty($options['delete'])) {
+ $this->store($this->_selected, array(
+ 'add' => array(Horde_Imap_Client::FLAG_DELETED),
+ 'ids' => $ids
+ ));
+ }
+
+ if ($use_cache || $list_msgs) {
+ $this->_sendCmd($this->_command('EXPUNGE'));
+ } else {
+ /* This is faster than an EXPUNGE because the server will not
+ * return untagged EXPUNGE responses. We can only do this if
+ * we are not updating cache information. */
+ $this->close(array('expunge' => true));
+ }
+ }
+
+ unset($this->_temp['expunged']);
+
+ if (!empty($unflag)) {
+ $this->store($this->_selected, array(
+ 'add' => array(Horde_Imap_Client::FLAG_DELETED),
+ 'ids' => $unflag
+ ));
+ }
+
+ if (!is_null($modseq) && !empty($resp->data['expunge_seen'])) {
+ /* There's a chance we actually did a full map of sequence -> UID,
+ * but this code should never be reached in the first place so
+ * be ultra-safe and just do a full VANISHED search. */
+ $expunged_ob = $this->vanished($this->_selected, $modseq, array(
+ 'ids' => $ids
+ ));
+ $this->_deleteMsgs($this->_selected, $expunged_ob, array(
+ 'pipeline' => $resp
+ ));
+ }
+
+ return $expunged_ob;
+ }
+
+ /**
+ * Parse a VANISHED response (RFC 5162 [3.6]).
+ *
+ * @param Horde_Imap_Client_Interaction_Pipeline $pipeline Pipeline
+ * object.
+ * @param Horde_Imap_Client_Tokenize $data The response data.
+ */
+ protected function _parseVanished(
+ Horde_Imap_Client_Interaction_Pipeline $pipeline,
+ Horde_Imap_Client_Tokenize $data
+ )
+ {
+ /* There are two forms of VANISHED. VANISHED (EARLIER) will be sent
+ * in a FETCH (VANISHED) or SELECT/EXAMINE (QRESYNC) call.
+ * If this is the case, we can go ahead and update the cache
+ * immediately (we know we are caching or else QRESYNC would not be
+ * enabled). HIGHESTMODSEQ information will be updated via the tagged
+ * response. */
+ if (($curr = $data->next()) === true) {
+ if (strtoupper($data->next()) == 'EARLIER') {
+ /* Caching is guaranteed to be active if we are using
+ * QRESYNC. */
+ $data->next();
+ $vanished = $this->getIdsOb($data->next());
+ if (isset($pipeline->data['vanished'])) {
+ $pipeline->data['vanished']->add($vanished);
+ } else {
+ $this->_deleteMsgs($this->_selected, $vanished, array(
+ 'pipeline' => $pipeline
+ ));
+ }
+ }
+ } else {
+ /* The second form is just VANISHED. This is analogous to EXPUNGE
+ * and requires the message count to decrement. */
+ $this->_deleteMsgs($this->_selected, $this->getIdsOb($curr), array(
+ 'decrement' => true,
+ 'pipeline' => $pipeline
+ ));
+ }
+ }
+
+ /**
+ * Search a mailbox. This driver supports all IMAP4rev1 search criteria
+ * as defined in RFC 3501.
+ */
+ protected function _search($query, $options)
+ {
+ $sort_criteria = array(
+ Horde_Imap_Client::SORT_ARRIVAL => 'ARRIVAL',
+ Horde_Imap_Client::SORT_CC => 'CC',
+ Horde_Imap_Client::SORT_DATE => 'DATE',
+ Horde_Imap_Client::SORT_DISPLAYFROM => 'DISPLAYFROM',
+ Horde_Imap_Client::SORT_DISPLAYTO => 'DISPLAYTO',
+ Horde_Imap_Client::SORT_FROM => 'FROM',
+ Horde_Imap_Client::SORT_REVERSE => 'REVERSE',
+ Horde_Imap_Client::SORT_RELEVANCY => 'RELEVANCY',
+ // This is a bogus entry to allow the sort options check to
+ // correctly work below.
+ Horde_Imap_Client::SORT_SEQUENCE => 'SEQUENCE',
+ Horde_Imap_Client::SORT_SIZE => 'SIZE',
+ Horde_Imap_Client::SORT_SUBJECT => 'SUBJECT',
+ Horde_Imap_Client::SORT_TO => 'TO'
+ );
+
+ $results_criteria = array(
+ Horde_Imap_Client::SEARCH_RESULTS_COUNT => 'COUNT',
+ Horde_Imap_Client::SEARCH_RESULTS_MATCH => 'ALL',
+ Horde_Imap_Client::SEARCH_RESULTS_MAX => 'MAX',
+ Horde_Imap_Client::SEARCH_RESULTS_MIN => 'MIN',
+ Horde_Imap_Client::SEARCH_RESULTS_RELEVANCY => 'RELEVANCY',
+ Horde_Imap_Client::SEARCH_RESULTS_SAVE => 'SAVE'
+ );
+
+ // Check if the server supports sorting (RFC 5256).
+ $esearch = $return_sort = $server_seq_sort = $server_sort = false;
+ if (!empty($options['sort'])) {
+ /* Make sure sort options are correct. If not, default to no
+ * sort. */
+ if (count(array_intersect($options['sort'], array_keys($sort_criteria))) === 0) {
+ unset($options['sort']);
+ } else {
+ $return_sort = true;
+
+ if ($server_sort = $this->queryCapability('SORT')) {
+ /* Make sure server supports DISPLAYFROM & DISPLAYTO. */
+ $server_sort =
+ !array_intersect($options['sort'], array(Horde_Imap_Client::SORT_DISPLAYFROM, Horde_Imap_Client::SORT_DISPLAYTO)) ||
+ (is_array($server_sort) &&
+ in_array('DISPLAY', $server_sort));
+ }
+
+ /* If doing a sequence sort, need to do this on the client
+ * side. */
+ if ($server_sort &&
+ in_array(Horde_Imap_Client::SORT_SEQUENCE, $options['sort'])) {
+ $server_sort = false;
+
+ /* Optimization: If doing only a sequence sort, just do a
+ * simple search and sort UIDs/sequences on client side. */
+ switch (count($options['sort'])) {
+ case 1:
+ $server_seq_sort = true;
+ break;
+
+ case 2:
+ $server_seq_sort = (reset($options['sort']) == Horde_Imap_Client::SORT_REVERSE);
+ break;
+ }
+ }
+ }
+ }
+
+ $charset = is_null($options['_query']['charset'])
+ ? 'US-ASCII'
+ : $options['_query']['charset'];
+
+ if ($server_sort) {
+ $cmd = $this->_command(
+ empty($options['sequence']) ? 'UID SORT' : 'SORT'
+ );
+ $results = array();
+
+ // Use ESEARCH (RFC 4466) response if server supports.
+ $esearch = false;
+
+ // Check for ESORT capability (RFC 5267)
+ if ($this->queryCapability('ESORT')) {
+ foreach ($options['results'] as $val) {
+ if (isset($results_criteria[$val]) &&
+ ($val != Horde_Imap_Client::SEARCH_RESULTS_SAVE)) {
+ $results[] = $results_criteria[$val];
+ }
+ }
+ $esearch = true;
+ }
+
+ // Add PARTIAL limiting (RFC 5267 [4.4])
+ if ((!$esearch || !empty($options['partial'])) &&
+ ($cap = $this->queryCapability('CONTEXT')) &&
+ in_array('SORT', $cap)) {
+ /* RFC 5267 indicates RFC 4466 ESEARCH support,
+ * notwithstanding RFC 4731 support. */
+ $esearch = true;
+
+ if (!empty($options['partial'])) {
+ /* Can't have both ALL and PARTIAL returns. */
+ $results = array_diff($results, array('ALL'));
+
+ $results[] = 'PARTIAL';
+ $results[] = strval($this->getIdsOb($options['partial']));
+ }
+ }
+
+ if ($esearch && empty($this->_init['noesearch'])) {
+ $cmd->add(array(
+ 'RETURN',
+ new Horde_Imap_Client_Data_Format_List($results)
+ ));
+ }
+
+ $tmp = new Horde_Imap_Client_Data_Format_List();
+ foreach ($options['sort'] as $val) {
+ if (isset($sort_criteria[$val])) {
+ $tmp->add($sort_criteria[$val]);
+ }
+ }
+ $cmd->add($tmp);
+
+ // Charset is mandatory for SORT (RFC 5256 [3]).
+ $cmd->add($charset);
+ } else {
+ $cmd = $this->_command(
+ empty($options['sequence']) ? 'UID SEARCH' : 'SEARCH'
+ );
+ $esearch = false;
+ $results = array();
+
+ // Check if the server supports ESEARCH (RFC 4731).
+ if ($this->queryCapability('ESEARCH')) {
+ foreach ($options['results'] as $val) {
+ if (isset($results_criteria[$val])) {
+ $results[] = $results_criteria[$val];
+ }
+ }
+ $esearch = true;
+ }
+
+ // Add PARTIAL limiting (RFC 5267 [4.4]).
+ if ((!$esearch || !empty($options['partial'])) &&
+ ($cap = $this->queryCapability('CONTEXT')) &&
+ in_array('SEARCH', $cap)) {
+ /* RFC 5267 indicates RFC 4466 ESEARCH support,
+ * notwithstanding RFC 4731 support. */
+ $esearch = true;
+
+ if (!empty($options['partial'])) {
+ // Can't have both ALL and PARTIAL returns.
+ $results = array_diff($results, array('ALL'));
+
+ $results[] = 'PARTIAL';
+ $results[] = strval($this->getIdsOb($options['partial']));
+ }
+ }
+
+ if ($esearch && empty($this->_init['noesearch'])) {
+ // Always use ESEARCH if available because it returns results
+ // in a more compact sequence-set list
+ $cmd->add(array(
+ 'RETURN',
+ new Horde_Imap_Client_Data_Format_List($results)
+ ));
+ }
+
+ // Charset is optional for SEARCH (RFC 3501 [6.4.4]).
+ if ($charset != 'US-ASCII') {
+ $cmd->add(array(
+ 'CHARSET',
+ $options['_query']['charset']
+ ));
+ }
+ }
+
+ $cmd->add($options['_query']['query'], true);
+
+ $pipeline = $this->_pipeline($cmd);
+ $pipeline->data['esearchresp'] = array();
+ $er = &$pipeline->data['esearchresp'];
+ $pipeline->data['searchresp'] = $this->getIdsOb(array(), !empty($options['sequence']));
+ $sr = &$pipeline->data['searchresp'];
+
+ try {
+ $resp = $this->_sendCmd($pipeline);
+ } catch (Horde_Imap_Client_Exception $e) {
+ if (($e instanceof Horde_Imap_Client_Exception_ServerResponse) &&
+ ($e->status == Horde_Imap_Client_Interaction_Server::NO) &&
+ ($charset != 'US-ASCII')) {
+ /* RFC 3501 [6.4.4]: BADCHARSET response code is only a
+ * SHOULD return. If it doesn't exist, need to check for
+ * command status of 'NO'. List of supported charsets in
+ * the BADCHARSET response has already been parsed and stored
+ * at this point. */
+ $s_charset = $this->_init['s_charset'];
+ $s_charset[$charset] = false;
+ $this->_setInit('s_charset', $s_charset);
+ $e->setCode(Horde_Imap_Client_Exception::BADCHARSET);
+ }
+
+ if (empty($this->_temp['search_retry'])) {
+ $this->_temp['search_retry'] = true;
+
+ /* Bug #9842: Workaround broken Cyrus servers (as of
+ * 2.4.7). */
+ if ($esearch && ($charset != 'US-ASCII')) {
+ $this->_unsetCapability('ESEARCH');
+ $this->_setInit('noesearch', true);
+
+ try {
+ return $this->_search($query, $options);
+ } catch (Horde_Imap_Client_Exception $e) {}
+ }
+
+ /* Try to convert charset. */
+ if (($e->getCode() == Horde_Imap_Client_Exception::BADCHARSET) &&
+ ($charset != 'US-ASCII')) {
+ foreach (array_merge(array_keys(array_filter($this->_init['s_charset'])), array('US-ASCII')) as $val) {
+ $this->_temp['search_retry'] = 1;
+ $new_query = clone($query);
+ try {
+ $new_query->charset($val);
+ $options['_query'] = $new_query->build($this->capability());
+ return $this->_search($new_query, $options);
+ } catch (Horde_Imap_Client_Exception $e) {}
+ }
+ }
+
+ unset($this->_temp['search_retry']);
+ }
+
+ throw $e;
+ }
+
+ if ($return_sort && !$server_sort) {
+ if ($server_seq_sort) {
+ $sr->sort();
+ if (reset($options['sort']) == Horde_Imap_Client::SORT_REVERSE) {
+ $sr->reverse();
+ }
+ } else {
+ if (!isset($this->_temp['clientsort'])) {
+ $this->_temp['clientsort'] = new Horde_Imap_Client_Socket_ClientSort($this);
+ }
+ $sr = $this->getIdsOb($this->_temp['clientsort']->clientSort($sr, $options), !empty($options['sequence']));
+ }
+ }
+
+ $ret = array();
+ foreach ($options['results'] as $val) {
+ switch ($val) {
+ case Horde_Imap_Client::SEARCH_RESULTS_COUNT:
+ $ret['count'] = $esearch ? $er['count'] : count($sr);
+ break;
+
+ case Horde_Imap_Client::SEARCH_RESULTS_MATCH:
+ $ret['match'] = $sr;
+ break;
+
+ case Horde_Imap_Client::SEARCH_RESULTS_MAX:
+ $ret['max'] = $esearch
+ ? (isset($er['max']) ? $er['max'] : null)
+ : (count($sr) ? max($sr->ids) : null);
+ break;
+
+ case Horde_Imap_Client::SEARCH_RESULTS_MIN:
+ $ret['min'] = $esearch
+ ? (isset($er['min']) ? $er['min'] : null)
+ : (count($sr) ? min($sr->ids) : null);
+ break;
+
+ case Horde_Imap_Client::SEARCH_RESULTS_RELEVANCY:
+ $ret['relevancy'] = ($esearch && isset($er['relevancy'])) ? $er['relevancy'] : array();
+ break;
+
+ case Horde_Imap_Client::SEARCH_RESULTS_SAVE:
+ $this->_temp['search_save'] = $ret['save'] = $esearch ? empty($resp->data['searchnotsaved']) : false;
+ break;
+ }
+ }
+
+ // Add modseq data, if needed.
+ if (!empty($er['modseq'])) {
+ $ret['modseq'] = $er['modseq'];
+ }
+
+ unset($this->_temp['search_retry']);
+
+ /* Check for EXPUNGEISSUED (RFC 2180 [4.3]/RFC 5530 [3]). */
+ if (!empty($resp->data['expungeissued'])) {
+ $this->noop();
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Parse a SEARCH/SORT response (RFC 3501 [7.2.5]; RFC 4466 [3];
+ * RFC 5256 [4]; RFC 5267 [3]).
+ *
+ * @param Horde_Imap_Client_Interaction_Pipeline $pipeline Pipeline
+ * object.
+ * @param array $data A list of IDs (message sequence numbers or UIDs).
+ */
+ protected function _parseSearch(
+ Horde_Imap_Client_Interaction_Pipeline $pipeline,
+ $data
+ )
+ {
+ /* More than one search response may be sent. */
+ $pipeline->data['searchresp']->add($data);
+ }
+
+ /**
+ * Parse an ESEARCH response (RFC 4466 [2.6.2])
+ * Format: (TAG "a567") UID COUNT 5 ALL 4:19,21,28
+ *
+ * @param Horde_Imap_Client_Interaction_Pipeline $pipeline Pipeline
+ * object.
+ * @param Horde_Imap_Client_Tokenize $data The server response.
+ */
+ protected function _parseEsearch(
+ Horde_Imap_Client_Interaction_Pipeline $pipeline,
+ Horde_Imap_Client_Tokenize $data
+ )
+ {
+ // Ignore search correlator information
+ if ($data->next() === true) {
+ $data->flushIterator(false);
+ }
+
+ // Ignore UID tag
+ $current = $data->next();
+ if (strtoupper($current) == 'UID') {
+ $current = $data->next();
+ }
+
+ do {
+ $val = $data->next();
+ $tag = strtoupper($current);
+
+ switch ($tag) {
+ case 'ALL':
+ $this->_parseSearch($pipeline, $val);
+ break;
+
+ case 'COUNT':
+ case 'MAX':
+ case 'MIN':
+ case 'MODSEQ':
+ case 'RELEVANCY':
+ $pipeline->data['esearchresp'][strtolower($tag)] = $val;
+ break;
+
+ case 'PARTIAL':
+ // RFC 5267 [4.4]
+ $partial = $val->flushIterator();
+ $this->_parseSearch($pipeline, end($partial));
+ break;
+ }
+ } while (($current = $data->next()) !== false);
+ }
+
+ /**
+ */
+ protected function _setComparator($comparator)
+ {
+ $cmd = $this->_command('COMPARATOR');
+ foreach ($comparator as $val) {
+ $cmd->add(new Horde_Imap_Client_Data_Format_Astring($val));
+ }
+ $this->_sendCmd($cmd);
+ }
+
+ /**
+ */
+ protected function _getComparator()
+ {
+ $resp = $this->_sendCmd($this->_command('COMPARATOR'));
+
+ return isset($resp->data['comparator'])
+ ? $resp->data['comparator']
+ : null;
+ }
+
+ /**
+ * Parse a COMPARATOR response (RFC 5255 [4.8])
+ *
+ * @param Horde_Imap_Client_Interaction_Pipeline $pipeline Pipeline
+ * object.
+ * @param Horde_Imap_Client_Tokenize $data The server response.
+ */
+ protected function _parseComparator(
+ Horde_Imap_Client_Interaction_Pipeline $pipeline,
+ $data
+ )
+ {
+ $pipeline->data['comparator'] = $data->next();
+ // Ignore optional matching comparator list
+ }
+
+ /**
+ * @throws Horde_Imap_Client_Exception_NoSupportExtension
+ */
+ protected function _thread($options)
+ {
+ $thread_criteria = array(
+ Horde_Imap_Client::THREAD_ORDEREDSUBJECT => 'ORDEREDSUBJECT',
+ Horde_Imap_Client::THREAD_REFERENCES => 'REFERENCES',
+ Horde_Imap_Client::THREAD_REFS => 'REFS'
+ );
+
+ $tsort = (isset($options['criteria']))
+ ? (is_string($options['criteria']) ? strtoupper($options['criteria']) : $thread_criteria[$options['criteria']])
+ : 'ORDEREDSUBJECT';
+
+ $cap = $this->queryCapability('THREAD');
+ if (!$cap || !in_array($tsort, $cap)) {
+ switch ($tsort) {
+ case 'ORDEREDSUBJECT':
+ if (empty($options['search'])) {
+ $ids = $this->getIdsOb(Horde_Imap_Client_Ids::ALL, !empty($options['sequence']));
+ } else {
+ $search_res = $this->search($this->_selected, $options['search'], array('sequence' => !empty($options['sequence'])));
+ $ids = $search_res['match'];
+ }
+
+ /* Do client-side ORDEREDSUBJECT threading. */
+ $query = new Horde_Imap_Client_Fetch_Query();
+ $query->envelope();
+ $query->imapDate();
+
+ $fetch_res = $this->fetch($this->_selected, $query, array(
+ 'ids' => $ids
+ ));
+
+ if (!isset($this->_temp['clientsort'])) {
+ $this->_temp['clientsort'] = new Horde_Imap_Client_Socket_ClientSort($this);
+ }
+ return $this->_temp['clientsort']->threadOrderedSubject($fetch_res, empty($options['sequence']));
+
+ case 'REFERENCES':
+ case 'REFS':
+ throw new Horde_Imap_Client_Exception_NoSupportExtension(
+ 'THREAD',
+ sprintf('Server does not support "%s" thread sort.', $tsort)
+ );
+ }
+ }
+
+ $cmd = $this->_command(
+ empty($options['sequence']) ? 'UID THREAD' : 'THREAD'
+ )->add($tsort);
+
+ if (empty($options['search'])) {
+ $cmd->add(array(
+ 'US-ASCII',
+ 'ALL'
+ ));
+ } else {
+ $search_query = $options['search']->build();
+ $cmd->add(is_null($search_query['charset']) ? 'US-ASCII' : $search_query['charset']);
+ $cmd->add($search_query['query'], true);
+ }
+
+ return new Horde_Imap_Client_Data_Thread(
+ $this->_sendCmd($cmd)->data['threadparse'],
+ empty($options['sequence']) ? 'uid' : 'sequence'
+ );
+ }
+
+ /**
+ * Parse a THREAD response (RFC 5256 [4]).
+ *
+ * @param Horde_Imap_Client_Interaction_Pipeline $pipeline Pipeline
+ * object.
+ * @param Horde_Imap_Client_Tokenize $data Thread data.
+ */
+ protected function _parseThread(
+ Horde_Imap_Client_Interaction_Pipeline $pipeline,
+ Horde_Imap_Client_Tokenize $data
+ )
+ {
+ $out = array();
+
+ while ($data->next() !== false) {
+ $thread = array();
+ $this->_parseThreadLevel($thread, $data);
+ $out[] = $thread;
+ }
+
+ $pipeline->data['threadparse'] = $out;
+ }
+
+ /**
+ * Parse a level of a THREAD response (RFC 5256 [4]).
+ *
+ * @param array $thread Results.
+ * @param Horde_Imap_Client_Tokenize $data Thread data.
+ * @param integer $level The current tree level.
+ */
+ protected function _parseThreadLevel(&$thread,
+ Horde_Imap_Client_Tokenize $data,
+ $level = 0)
+ {
+ while (($curr = $data->next()) !== false) {
+ if ($curr === true) {
+ $this->_parseThreadLevel($thread, $data, $level);
+ } elseif (!is_bool($curr)) {
+ $thread[$curr] = $level++;
+ }
+ }
+ }
+
+ /**
+ */
+ protected function _fetch(Horde_Imap_Client_Fetch_Results $results,
+ $queries)
+ {
+ $pipeline = $this->_pipeline();
+ $pipeline->data['fetch_lookup'] = array();
+
+ foreach ($queries as $options) {
+ $this->_fetchCmd($pipeline, $options);
+ $sequence = $options['ids']->sequence;
+ }
+
+ try {
+ $resp = $this->_sendCmd($pipeline);
+
+ /* Check for EXPUNGEISSUED (RFC 2180 [4.1]/RFC 5530 [3]). */
+ if (!empty($resp->data['expungeissued'])) {
+ $this->noop();
+ }
+ } catch (Horde_Imap_Client_Exception_ServerResponse $e) {
+ // A NO response, when coupled with a sequence FETCH, most
+ // likely means that messages were expunged. RFC 2180 [4.1]
+ if ($sequence &&
+ ($e->status == Horde_Imap_Client_Interaction_Server::NO)) {
+ $this->noop();
+ }
+ } catch (Exception $e) {
+ // For any other error, ignore the Exception - fetch() is nice in
+ // that the return value explicitly handles missing data for any
+ // given message.
+ }
+
+ foreach ($resp->fetch as $k => $v) {
+ $results->get($sequence ? $k : $v->getUid())->merge($v);
+ }
+ }
+
+ /**
+ * Add a FETCH command to the given pipeline.
+ *
+ * @param Horde_Imap_Client_Interaction_Pipeline $pipeline Pipeline
+ * object.
+ * @param array $options Fetch query
+ * options
+ */
+ protected function _fetchCmd(
+ Horde_Imap_Client_Interaction_Pipeline $pipeline,
+ $options
+ )
+ {
+ $fetch = new Horde_Imap_Client_Data_Format_List();
+ $sequence = $options['ids']->sequence;
+
+ /* Build an IMAP4rev1 compliant FETCH query. We handle the following
+ * criteria:
+ * BINARY[.PEEK][<section #>]<<partial>> (RFC 3516)
+ * see BODY[] response
+ * BINARY.SIZE[<section #>] (RFC 3516)
+ * BODY[.PEEK][<section>]<<partial>>
+ * <section> = HEADER, HEADER.FIELDS, HEADER.FIELDS.NOT, MIME,
+ * TEXT, empty
+ * <<partial>> = 0.# (# of bytes)
+ * BODYSTRUCTURE
+ * ENVELOPE
+ * FLAGS
+ * INTERNALDATE
+ * MODSEQ (RFC 4551)
+ * RFC822.SIZE
+ * UID
+ *
+ * No need to support these (can be built from other queries):
+ * ===========================================================
+ * ALL macro => (FLAGS INTERNALDATE RFC822.SIZE ENVELOPE)
+ * BODY => Use BODYSTRUCTURE instead
+ * FAST macro => (FLAGS INTERNALDATE RFC822.SIZE)
+ * FULL macro => (FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY)
+ * RFC822 => BODY[]
+ * RFC822.HEADER => BODY[HEADER]
+ * RFC822.TEXT => BODY[TEXT]
+ */
+
+ foreach ($options['_query'] as $type => $c_val) {
+ switch ($type) {
+ case Horde_Imap_Client::FETCH_STRUCTURE:
+ $fetch->add('BODYSTRUCTURE');
+ break;
+
+ case Horde_Imap_Client::FETCH_FULLMSG:
+ if (empty($c_val['peek'])) {
+ $this->openMailbox($this->_selected, Horde_Imap_Client::OPEN_READWRITE);
+ }
+ $fetch->add(
+ 'BODY' .
+ (!empty($c_val['peek']) ? '.PEEK' : '') .
+ '[]' .
+ $this->_partialAtom($c_val)
+ );
+ break;
+
+ case Horde_Imap_Client::FETCH_HEADERTEXT:
+ case Horde_Imap_Client::FETCH_BODYTEXT:
+ case Horde_Imap_Client::FETCH_MIMEHEADER:
+ case Horde_Imap_Client::FETCH_BODYPART:
+ case Horde_Imap_Client::FETCH_HEADERS:
+ foreach ($c_val as $key => $val) {
+ $cmd = ($key == 0)
+ ? ''
+ : $key . '.';
+ $main_cmd = 'BODY';
+
+ switch ($type) {
+ case Horde_Imap_Client::FETCH_HEADERTEXT:
+ $cmd .= 'HEADER';
+ break;
+
+ case Horde_Imap_Client::FETCH_BODYTEXT:
+ $cmd .= 'TEXT';
+ break;
+
+ case Horde_Imap_Client::FETCH_MIMEHEADER:
+ $cmd .= 'MIME';
+ break;
+
+ case Horde_Imap_Client::FETCH_BODYPART:
+ // Remove the last dot from the string.
+ $cmd = substr($cmd, 0, -1);
+
+ if (!empty($val['decode']) &&
+ $this->queryCapability('BINARY')) {
+ $main_cmd = 'BINARY';
+ }
+ break;
+
+ case Horde_Imap_Client::FETCH_HEADERS:
+ $cmd .= 'HEADER.FIELDS';
+ if (!empty($val['notsearch'])) {
+ $cmd .= '.NOT';
+ }
+ $cmd .= ' (' . implode(' ', array_map('strtoupper', $val['headers'])) . ')';
+
+ // Maintain a command -> label lookup so we can put
+ // the results in the proper location.
+ $pipeline->data['fetch_lookup'][$cmd] = $key;
+ }
+
+ if (empty($val['peek'])) {
+ $this->openMailbox($this->_selected, Horde_Imap_Client::OPEN_READWRITE);
+ }
+
+ $fetch->add(
+ $main_cmd .
+ (!empty($val['peek']) ? '.PEEK' : '') .
+ '[' . $cmd . ']' .
+ $this->_partialAtom($val)
+ );
+ }
+ break;
+
+ case Horde_Imap_Client::FETCH_BODYPARTSIZE:
+ if ($this->queryCapability('BINARY')) {
+ foreach ($c_val as $val) {
+ $fetch->add('BINARY.SIZE[' . $key . ']');
+ }
+ }
+ break;
+
+ case Horde_Imap_Client::FETCH_ENVELOPE:
+ $fetch->add('ENVELOPE');
+ break;
+
+ case Horde_Imap_Client::FETCH_FLAGS:
+ $fetch->add('FLAGS');
+ break;
+
+ case Horde_Imap_Client::FETCH_IMAPDATE:
+ $fetch->add('INTERNALDATE');
+ break;
+
+ case Horde_Imap_Client::FETCH_SIZE:
+ $fetch->add('RFC822.SIZE');
+ break;
+
+ case Horde_Imap_Client::FETCH_UID:
+ /* A UID FETCH will always return UID information (RFC 3501
+ * [6.4.8]). Don't add to query as it just creates a longer
+ * FETCH command. */
+ if ($sequence || (count($options['_query']) == 1)) {
+ $fetch->add('UID');
+ }
+ break;
+
+ case Horde_Imap_Client::FETCH_SEQ:
+ // Nothing we need to add to fetch request unless sequence is
+ // the only criteria.
+ if (count($options['_query']) == 1) {
+ $fetch->add('UID');
+ }
+ break;
+
+ case Horde_Imap_Client::FETCH_MODSEQ:
+ /* The 'changedsince' modifier implicitly adds the MODSEQ
+ * FETCH item (RFC 4551 [3.3.1]). Don't add to query as it
+ * just creates a longer FETCH command. */
+ if (empty($options['changedsince'])) {
+ $fetch->add('MODSEQ');
+ }
+ break;
+ }
+ }
+
+ /* Add changedsince parameters. */
+ if (empty($options['changedsince'])) {
+ $fetch_cmd = $fetch;
+ } else {
+ /* We might just want the list of UIDs changed since a given
+ * modseq. In that case, we don't have any other FETCH attributes,
+ * but RFC 3501 requires at least one specified attribute. */
+ $fetch_cmd = array(
+ count($fetch)
+ ? $fetch
+ : new Horde_Imap_Client_Data_Format_List('UID'),
+ new Horde_Imap_Client_Data_Format_List(array(
+ 'CHANGEDSINCE',
+ new Horde_Imap_Client_Data_Format_Number($options['changedsince'])
+ ))
+ );
+ }
+
+ /* RFC 2683 [3.2.1.5] recommends that lines should be limited to
+ * "approximately 1000 octets". However, servers should allow a
+ * command line of at least "8000 octets". As a compromise, assume
+ * all modern IMAP servers handle ~2000 octets. The FETCH command
+ * should be the only command issued by this library that should ever
+ * approach this limit. For simplification, assume that the UID list
+ * is the limiting factor and split this list at a sequence comma
+ * delimiter if it exceeds 2000 characters. */
+ foreach ($options['ids']->split(2000) as $val) {
+ $cmd = $this->_command(
+ $sequence ? 'FETCH' : 'UID FETCH'
+ )->add(array(
+ $val,
+ $fetch_cmd
+ ));
+ $pipeline->add($cmd);
+ }
+ }
+
+ /**
+ * Add a partial atom to an IMAP command based on the criteria options.
+ *
+ * @param array $opts Criteria options.
+ *
+ * @return string The partial atom.
+ */
+ protected function _partialAtom($opts)
+ {
+ if (!empty($opts['length'])) {
+ return '<' . (empty($opts['start']) ? 0 : intval($opts['start'])) . '.' . intval($opts['length']) . '>';
+ }
+
+ return empty($opts['start'])
+ ? ''
+ : ('<' . intval($opts['start']) . '>');
+ }
+
+ /**
+ * Parse a FETCH response (RFC 3501 [7.4.2]). A FETCH response may occur
+ * due to a FETCH command, or due to a change in a message's state (i.e.
+ * the flags change).
+ *
+ * @param Horde_Imap_Client_Interaction_Pipeline $pipeline Pipeline
+ * object.
+ * @param integer $id The message sequence number.
+ * @param Horde_Imap_Client_Tokenize $data The server response.
+ */
+ protected function _parseFetch(
+ Horde_Imap_Client_Interaction_Pipeline $pipeline,
+ $id,
+ Horde_Imap_Client_Tokenize $data
+ )
+ {
+ if ($data->next() !== true) {
+ return;
+ }
+
+ $ob = $pipeline->fetch->get($id);
+ $ob->setSeq($id);
+
+ $flags = $modseq = $uid = false;
+
+ while (($tag = $data->next()) !== false) {
+ $tag = strtoupper($tag);
+
+ switch ($tag) {
+ case 'BODYSTRUCTURE':
+ $data->next();
+ $structure = $this->_parseBodystructure($data);
+ $structure->buildMimeIds();
+ $ob->setStructure($structure);
+ break;
+
+ case 'ENVELOPE':
+ $data->next();
+ $ob->setEnvelope($this->_parseEnvelope($data));
+ break;
+
+ case 'FLAGS':
+ $data->next();
+ $ob->setFlags($data->flushIterator());
+ $flags = true;
+ break;
+
+ case 'INTERNALDATE':
+ $ob->setImapDate($data->next());
+ break;
+
+ case 'RFC822.SIZE':
+ $ob->setSize($data->next());
+ break;
+
+ case 'UID':
+ $ob->setUid($data->next());
+ $uid = true;
+ break;
+
+ case 'MODSEQ':
+ $data->next();
+ $modseq = $data->next();
+ $data->next();
+
+ /* MODSEQ must be greater than 0, so do sanity checking. */
+ if ($modseq > 0) {
+ $ob->setModSeq($modseq);
+
+ /* Store MODSEQ value. It may be used as the highestmodseq
+ * once a tagged response is received (RFC 5162 [5]). */
+ $pipeline->data['modseqs'][] = $modseq;
+ }
+ break;
+
+ default:
+ // Catch BODY[*]<#> responses
+ if (strpos($tag, 'BODY[') === 0) {
+ // Remove the beginning 'BODY['
+ $tag = substr($tag, 5);
+
+ // BODY[HEADER.FIELDS] request
+ if (!empty($pipeline->data['fetch_lookup']) &&
+ (strpos($tag, 'HEADER.FIELDS') !== false)) {
+ $data->next();
+ $sig = $tag . ' (' . implode(' ', array_map('strtoupper', $data->flushIterator())) . ')';
+
+ // Ignore the trailing bracket
+ $data->next();
+
+ $ob->setHeaders($pipeline->data['fetch_lookup'][$sig], $data->next());
+ } else {
+ // Remove trailing bracket and octet start info
+ $tag = substr($tag, 0, strrpos($tag, ']'));
+
+ if (!strlen($tag)) {
+ // BODY[] request
+ if (!is_null($tmp = $data->next())) {
+ $ob->setFullMsg($tmp);
+ }
+ } elseif (is_numeric(substr($tag, -1))) {
+ // BODY[MIMEID] request
+ if (!is_null($tmp = $data->next())) {
+ $ob->setBodyPart($tag, $tmp);
+ }
+ } else {
+ // BODY[HEADER|TEXT|MIME] request
+ if (($last_dot = strrpos($tag, '.')) === false) {
+ $mime_id = 0;
+ } else {
+ $mime_id = substr($tag, 0, $last_dot);
+ $tag = substr($tag, $last_dot + 1);
+ }
+
+ if (!is_null($tmp = $data->next())) {
+ switch ($tag) {
+ case 'HEADER':
+ $ob->setHeaderText($mime_id, $tmp);
+ break;
+
+ case 'TEXT':
+ $ob->setBodyText($mime_id, $tmp);
+ break;
+
+ case 'MIME':
+ $ob->setMimeHeader($mime_id, $tmp);
+ break;
+ }
+ }
+ }
+ }
+ } elseif (strpos($tag, 'BINARY[') === 0) {
+ // Catch BINARY[*]<#> responses
+ // Remove the beginning 'BINARY[' and the trailing bracket
+ // and octet start info
+ $tag = substr($tag, 7, strrpos($tag, ']') - 7);
+ $ob->setBodyPart($tag, $data->next(), empty($this->_temp['literal8']) ? '8bit' : 'binary');
+ } elseif (strpos($tag, 'BINARY.SIZE[') === 0) {
+ // Catch BINARY.SIZE[*] responses
+ // Remove the beginning 'BINARY.SIZE[' and the trailing
+ // bracket and octet start info
+ $tag = substr($tag, 12, strrpos($tag, ']') - 12);
+ $ob->setBodyPartSize($tag, $data->next());
+ }
+ break;
+ }
+ }
+
+ /* MODSEQ issue: Oh joy. Per RFC 5162 (see Errata #1807), FETCH FLAGS
+ * responses are NOT required to provide UID information, even if
+ * QRESYNC is explicitly enabled. Caveat: the FLAGS information
+ * returned during a SELECT/EXAMINE MUST contain UIDs so we are OK
+ * there.
+ * The good news: all decent IMAP servers (Cyrus, Dovecot) will always
+ * provide UID information, so this is not normally an issue.
+ * The bad news: spec-wise, this behavior cannot be 100% guaranteed.
+ * Compromise: We will watch for a FLAGS response with a MODSEQ and
+ * check if a UID exists also. If not, put the sequence number in a
+ * queue - it is possible the UID information may appear later in an
+ * untagged response. When the command is over, double check to make
+ * sure there are none of these MODSEQ/FLAGS that are still UID-less.
+ * In the (rare) event that there is, don't cache anything and
+ * immediately close the mailbox: flags will be correctly sync'd next
+ * mailbox open so we only lose a bit of caching efficiency.
+ * Otherwise, we could end up with an inconsistent cached state. */
+ if ($flags && $modseq && !$uid) {
+ $pipeline->data['modseqs_nouid'][] = $id;
+ }
+ }
+
+ /**
+ * Recursively parse BODYSTRUCTURE data from a FETCH return (see
+ * RFC 3501 [7.4.2]).
+ *
+ * @param Horde_Imap_Client_Tokenize $data Data returned from the server.
+ *
+ * @return Horde_Mime_Part Mime part object.
+ */
+ protected function _parseBodystructure(Horde_Imap_Client_Tokenize $data)
+ {
+ $ob = new Horde_Mime_Part();
+
+ // If index 0 is an array, this is a multipart part.
+ if (($entry = $data->next()) === true) {
+ do {
+ $ob->addPart($this->_parseBodystructure($data));
+ } while (($entry = $data->next()) === true);
+
+ // The subpart type.
+ $ob->setType('multipart/' . $entry);
+
+ // After the subtype is further extension information. This
+ // information MAY appear for BODYSTRUCTURE requests.
+
+ // This is parameter information.
+ if (($tmp = $data->next()) === false) {
+ return $ob;
+ } elseif ($tmp === true) {
+ foreach ($this->_parseStructureParams($data, 'content-type') as $key => $val) {
+ $ob->setContentTypeParameter($key, $val);
+ }
+ }
+ } else {
+ $ob->setType($entry . '/' . $data->next());
+
+ if ($data->next() === true) {
+ foreach ($this->_parseStructureParams($data, 'content-type') as $key => $val) {
+ $ob->setContentTypeParameter($key, $val);
+ }
+ }
+
+ if (!is_null($tmp = $data->next())) {
+ $ob->setContentId($tmp);
+ }
+
+ if (!is_null($tmp = $data->next())) {
+ $ob->setDescription(Horde_Mime::decode($tmp));
+ }
+
+ if (!is_null($tmp = $data->next())) {
+ $ob->setTransferEncoding($tmp);
+ }
+
+ $ob->setBytes($data->next());
+
+ // If the type is 'message/rfc822' or 'text/*', several extra
+ // fields are included
+ switch ($ob->getPrimaryType()) {
+ case 'message':
+ if ($ob->getSubType() == 'rfc822') {
+ if ($data->next() === true) {
+ // Ignore: envelope
+ $data->flushIterator(false);
+ }
+ if ($data->next() === true) {
+ $ob->addPart($this->_parseBodystructure($data));
+ }
+ $data->next(); // Ignore: lines
+ }
+ break;
+
+ case 'text':
+ $data->next(); // Ignore: lines
+ break;
+ }
+
+ // After the subtype is further extension information. This
+ // information MAY appear for BODYSTRUCTURE requests.
+
+ // Ignore: MD5
+ if ($data->next() === false) {
+ return $ob;
+ }
+ }
+
+ // This is disposition information
+ if (($tmp = $data->next()) === false) {
+ return $ob;
+ } elseif ($tmp === true) {
+ $ob->setDisposition($data->next());
+
+ if ($data->next() === true) {
+ foreach ($this->_parseStructureParams($data, 'content-disposition') as $key => $val) {
+ $ob->setDispositionParameter($key, $val);
+ }
+ }
+ $data->next();
+ }
+
+ // This is language information. It is either a single value or a list
+ // of values.
+ if (($tmp = $data->next()) === false) {
+ return $ob;
+ } elseif (!is_null($tmp)) {
+ $ob->setLanguage(($tmp === true) ? $data->flushIterator() : $tmp);
+ }
+
+ // Ignore location (RFC 2557) and consume closing paren.
+ $data->flushIterator(false);
+
+ return $ob;
+ }
+
+ /**
+ * Helper function to parse a parameters-like tokenized array.
+ *
+ * @param mixed $data Message data. Either a Horde_Imap_Client_Tokenize
+ * object or null.
+ * @param string $type The header name.
+ *
+ * @return array The parameter array.
+ */
+ protected function _parseStructureParams($data, $type)
+ {
+ $params = array();
+
+ if (is_null($data)) {
+ return $params;
+ }
+
+ while (($name = $data->next()) !== false) {
+ $params[strtolower($name)] = $data->next();
+ }
+
+ $ret = Horde_Mime::decodeParam($type, $params);
+
+ return $ret['params'];
+ }
+
+ /**
+ * Parse ENVELOPE data from a FETCH return (see RFC 3501 [7.4.2]).
+ *
+ * @param Horde_Imap_Client_Tokenize $data Data returned from the server.
+ *
+ * @return Horde_Imap_Client_Data_Envelope An envelope object.
+ */
+ protected function _parseEnvelope(Horde_Imap_Client_Tokenize $data)
+ {
+ // 'route', the 2nd element, is deprecated by RFC 2822.
+ $addr_structure = array(
+ 0 => 'personal',
+ 2 => 'mailbox',
+ 3 => 'host'
+ );
+ $env_data = array(
+ 0 => 'date',
+ 1 => 'subject',
+ 2 => 'from',
+ 3 => 'sender',
+ 4 => 'reply_to',
+ 5 => 'to',
+ 6 => 'cc',
+ 7 => 'bcc',
+ 8 => 'in_reply_to',
+ 9 => 'message_id'
+ );
+
+ $addr_ob = new Horde_Mail_Rfc822_Address();
+ $env_addrs = $this->getParam('envelope_addrs');
+ $env_str = $this->getParam('envelope_string');
+ $key = 0;
+ $ret = new Horde_Imap_Client_Data_Envelope();
+
+ while (($val = $data->next()) !== false) {
+ if (!isset($env_data[$key]) || is_null($val)) {
+ ++$key;
+ continue;
+ }
+
+ if (is_string($val)) {
+ // These entries are text fields.
+ $ret->$env_data[$key] = substr($val, 0, $env_str);
+ } else {
+ // These entries are address structures.
+ $group = null;
+ $key2 = 0;
+ $tmp = new Horde_Mail_Rfc822_List();
+
+ while ($data->next() !== false) {
+ $a_val = $data->flushIterator();
+
+ // RFC 3501 [7.4.2]: Group entry when host is NIL.
+ // Group end when mailbox is NIL; otherwise, this is
+ // mailbox name.
+ if (is_null($a_val[3])) {
+ if (is_null($a_val[2])) {
+ $group = null;
+ } else {
+ $group = new Horde_Mail_Rfc822_Group($a_val[2]);
+ $tmp->add($group);
+ }
+ } else {
+ $addr = clone $addr_ob;
+
+ foreach ($addr_structure as $add_key => $add_val) {
+ if (!is_null($a_val[$add_key])) {
+ $addr->$add_val = $a_val[$add_key];
+ }
+ }
+
+ if ($group) {
+ $group->addresses->add($addr);
+ } else {
+ $tmp->add($addr);
+ }
+ }
+
+ if (++$key2 >= $env_addrs) {
+ $data->flushIterator(false);
+ break;
+ }
+ }
+
+ $ret->$env_data[$key] = $tmp;
+ }
+
+ ++$key;
+ }
+
+ return $ret;
+ }
+
+ /**
+ */
+ protected function _vanished($modseq, Horde_Imap_Client_Ids $ids)
+ {
+ $pipeline = $this->_pipeline(
+ $this->_command('UID FETCH')->add(array(
+ strval($ids),
+ 'UID',
+ new Horde_Imap_Client_Data_Format_List(array(
+ 'VANISHED',
+ 'CHANGEDSINCE',
+ new Horde_Imap_Client_Data_Format_Number($modseq)
+ ))
+ ))
+ );
+ $pipeline->data['vanished'] = $this->getIdsOb();
+
+ return $this->_sendCmd($pipeline)->data['vanished'];
+ }
+
+ /**
+ */
+ protected function _store($options)
+ {
+ $pipeline = $this->_storeCmd($options);
+ $pipeline->data['modified'] = $this->getIdsOb();
+
+ try {
+ $resp = $this->_sendCmd($pipeline);
+
+ /* Check for EXPUNGEISSUED (RFC 2180 [4.2]/RFC 5530 [3]). */
+ if (!empty($resp->data['expungeissued'])) {
+ $this->noop();
+ }
+
+ return $resp->data['modified'];
+ } catch (Horde_Imap_Client_Exception_ServerResponse $e) {
+ /* A NO response, when coupled with a sequence STORE and
+ * non-SILENT behavior, most likely means that messages were
+ * expunged. RFC 2180 [4.2] */
+ if (empty($pipeline->data['store_silent']) &&
+ !empty($options['sequence']) &&
+ ($e->status == Horde_Imap_Client_Interaction_Server::NO)) {
+ $this->noop();
+ }
+
+ return $pipeline->data['modified'];
+ }
+ }
+
+ /**
+ * Create a store command.
+ *
+ * @param array $options See Horde_Imap_Client_Base#_store().
+ *
+ * @return Horde_Imap_Client_Interaction_Pipeline Pipeline object.
+ */
+ protected function _storeCmd($options)
+ {
+ $cmds = array();
+ $silent = empty($options['unchangedsince'])
+ ? !($this->_debug->debug || $this->_initCache(true))
+ : false;
+
+ if (!empty($options['replace'])) {
+ $cmds[] = array(
+ 'FLAGS' . ($silent ? '.SILENT' : ''),
+ $options['replace']
+ );
+ } else {
+ foreach (array('add' => '+', 'remove' => '-') as $k => $v) {
+ if (!empty($options[$k])) {
+ $cmds[] = array(
+ $v . 'FLAGS' . ($silent ? '.SILENT' : ''),
+ $options[$k]
+ );
+ }
+ }
+ }
+
+ $pipeline = $this->_pipeline();
+ $pipeline->data['store_silent'] = $silent;
+
+ foreach ($cmds as $val) {
+ $cmd = $this->_command(
+ empty($options['sequence']) ? 'UID STORE' : 'STORE'
+ )->add(strval($options['ids']));
+ if (!empty($options['unchangedsince'])) {
+ $cmd->add(new Horde_Imap_Client_Data_Format_List(array(
+ 'UNCHANGEDSINCE',
+ new Horde_Imap_Client_Data_Format_Number(intval($options['unchangedsince']))
+ )));
+ }
+ $cmd->add($val);
+
+ $pipeline->add($cmd);
+ }
+
+ return $pipeline;
+ }
+
+ /**
+ */
+ protected function _copy(Horde_Imap_Client_Mailbox $dest, $options)
+ {
+ /* Check for MOVE command (RFC 6851). */
+ $move_cmd = (!empty($options['move']) &&
+ $this->queryCapability('MOVE'));
+
+ $cmd = $this->_pipeline(
+ $this->_command(
+ ($options['ids']->sequence ? '' : 'UID ') . ($move_cmd ? 'MOVE' : 'COPY')
+ )->add(array(
+ strval($options['ids']),
+ new Horde_Imap_Client_Data_Format_Mailbox($dest)
+ ))
+ );
+ $cmd->data['copydest'] = $dest;
+
+ // COPY returns no untagged information (RFC 3501 [6.4.7])
+ try {
+ $resp = $this->_sendCmd($cmd);
+ } catch (Horde_Imap_Client_Exception $e) {
+ if (!empty($options['create']) &&
+ !empty($e->resp_data['trycreate'])) {
+ $this->createMailbox($dest);
+ unset($options['create']);
+ return $this->_copy($dest, $options);
+ }
+ throw $e;
+ }
+
+ // If moving, delete the old messages now. Short-circuit if nothing
+ // was moved.
+ if (!$move_cmd &&
+ !empty($options['move']) &&
+ (isset($resp->data['copyuid']) ||
+ !$this->queryCapability('UIDPLUS'))) {
+ $this->expunge($this->_selected, array(
+ 'delete' => true,
+ 'ids' => $options['ids']
+ ));
+ }
+
+ return isset($resp->data['copyuid'])
+ ? $resp->data['copyuid']
+ : true;
+ }
+
+ /**
+ */
+ protected function _setQuota(Horde_Imap_Client_Mailbox $root, $resources)
+ {
+ $limits = new Horde_Imap_Client_Data_Format_List();
+
+ foreach ($resources as $key => $val) {
+ $limits->add(array(
+ strtoupper($key),
+ new Horde_Imap_Client_Data_Format_Number($val)
+ ));
+ }
+
+ $this->_sendCmd(
+ $this->_command('SETQUOTA')->add(array(
+ new Horde_Imap_Client_Data_Format_Mailbox($root),
+ $limits
+ ))
+ );
+ }
+
+ /**
+ */
+ protected function _getQuota(Horde_Imap_Client_Mailbox $root)
+ {
+ $pipeline = $this->_pipeline(
+ $this->_command('GETQUOTA')->add(
+ new Horde_Imap_Client_Data_Format_Mailbox($root)
+ )
+ );
+ $pipeline->data['quotaresp'] = array();
+
+ return reset($this->_sendCmd($pipeline)->data['quotaresp']);
+ }
+
+ /**
+ * Parse a QUOTA response (RFC 2087 [5.1]).
+ *
+ * @param Horde_Imap_Client_Interaction_Pipeline $pipeline Pipeline
+ * object.
+ * @param Horde_Imap_Client_Tokenize $data The server response.
+ */
+ protected function _parseQuota(
+ Horde_Imap_Client_Interaction_Pipeline $pipeline,
+ Horde_Imap_Client_Tokenize $data
+ )
+ {
+ $c = &$pipeline->data['quotaresp'];
+
+ $root = $data->next();
+ $c[$root] = array();
+
+ $data->next();
+
+ while (($curr = $data->next()) !== false) {
+ $c[$root][strtolower($curr)] = array(
+ 'usage' => $data->next(),
+ 'limit' => $data->next()
+ );
+ }
+ }
+
+ /**
+ */
+ protected function _getQuotaRoot(Horde_Imap_Client_Mailbox $mailbox)
+ {
+ $pipeline = $this->_pipeline(
+ $this->_command('GETQUOTAROOT')->add(
+ new Horde_Imap_Client_Data_Format_Mailbox($mailbox)
+ )
+ );
+ $pipeline->data['quotaresp'] = array();
+
+ return $this->_sendCmd($pipeline)->data['quotaresp'];
+ }
+
+ /**
+ */
+ protected function _setACL(Horde_Imap_Client_Mailbox $mailbox, $identifier,
+ $options)
+ {
+ // SETACL returns no untagged information (RFC 4314 [3.1]).
+ $this->_sendCmd(
+ $this->_command('SETACL')->add(array(
+ new Horde_Imap_Client_Data_Format_Mailbox($mailbox),
+ new Horde_Imap_Client_Data_Format_Astring($identifier),
+ new Horde_Imap_Client_Data_Format_Astring($options['rights'])
+ ))
+ );
+ }
+
+ /**
+ */
+ protected function _deleteACL(Horde_Imap_Client_Mailbox $mailbox, $identifier)
+ {
+ // DELETEACL returns no untagged information (RFC 4314 [3.2]).
+ $this->_sendCmd(
+ $this->_command('DELETEACL')->add(array(
+ new Horde_Imap_Client_Data_Format_Mailbox($mailbox),
+ new Horde_Imap_Client_Data_Format_Astring($identifier)
+ ))
+ );
+ }
+
+ /**
+ */
+ protected function _getACL(Horde_Imap_Client_Mailbox $mailbox)
+ {
+ return $this->_sendCmd(
+ $this->_command('GETACL')->add(
+ new Horde_Imap_Client_Data_Format_Mailbox($mailbox)
+ )
+ )->data['getacl'];
+ }
+
+ /**
+ * Parse an ACL response (RFC 4314 [3.6]).
+ *
+ * @param Horde_Imap_Client_Interaction_Pipeline $pipeline Pipeline
+ * object.
+ * @param Horde_Imap_Client_Tokenize $data The server response.
+ */
+ protected function _parseACL(
+ Horde_Imap_Client_Interaction_Pipeline $pipeline,
+ Horde_Imap_Client_Tokenize $data
+ )
+ {
+ $acl = array();
+
+ // Ignore mailbox argument -> index 1
+ $data->next();
+
+ while (($curr = $data->next()) !== false) {
+ $acl[$curr] = ($curr[0] == '-')
+ ? new Horde_Imap_Client_Data_AclNegative($data->next())
+ : new Horde_Imap_Client_Data_Acl($data->next());
+ }
+
+ $pipeline->data['getacl'] = $acl;
+ }
+
+ /**
+ */
+ protected function _listACLRights(Horde_Imap_Client_Mailbox $mailbox,
+ $identifier)
+ {
+ $resp = $this->_sendCmd(
+ $this->_command('LISTRIGHTS')->add(array(
+ new Horde_Imap_Client_Data_Format_Mailbox($mailbox),
+ new Horde_Imap_Client_Data_Format_Astring($identifier)
+ ))
+ );
+
+ return isset($resp->data['listaclrights'])
+ ? $resp->data['listaclrights']
+ : new Horde_Imap_Client_Data_AclRights();
+ }
+
+ /**
+ * Parse a LISTRIGHTS response (RFC 4314 [3.7]).
+ *
+ * @param Horde_Imap_Client_Interaction_Pipeline $pipeline Pipeline
+ * object.
+ * @param Horde_Imap_Client_Tokenize $data The server response.
+ */
+ protected function _parseListRights(
+ Horde_Imap_Client_Interaction_Pipeline $pipeline,
+ Horde_Imap_Client_Tokenize $data
+ )
+ {
+ // Ignore mailbox and identifier arguments
+ $data->next();
+ $data->next();
+
+ $pipeline->data['listaclrights'] = new Horde_Imap_Client_Data_AclRights(
+ str_split($data->next()),
+ $data->flushIterator()
+ );
+ }
+
+ /**
+ */
+ protected function _getMyACLRights(Horde_Imap_Client_Mailbox $mailbox)
+ {
+ $resp = $this->_sendCmd(
+ $this->_command('MYRIGHTS')->add(
+ new Horde_Imap_Client_Data_Format_Mailbox($mailbox)
+ )
+ );
+
+ return isset($resp->data['myrights'])
+ ? $resp->data['myrights']
+ : new Horde_Imap_Client_Data_Acl();
+ }
+
+ /**
+ * Parse a MYRIGHTS response (RFC 4314 [3.8]).
+ *
+ * @param Horde_Imap_Client_Interaction_Pipeline $pipeline Pipeline
+ * object.
+ * @param Horde_Imap_Client_Tokenize $data The server response.
+ */
+ protected function _parseMyRights(
+ Horde_Imap_Client_Interaction_Pipeline $pipeline,
+ Horde_Imap_Client_Tokenize $data
+ )
+ {
+ // Ignore 1st token (mailbox name)
+ $data->next();
+
+ $pipeline->data['myrights'] = new Horde_Imap_Client_Data_Acl($data->next());
+ }
+
+ /**
+ */
+ protected function _getMetadata(Horde_Imap_Client_Mailbox $mailbox,
+ $entries, $options)
+ {
+ $pipeline = $this->_pipeline();
+ $pipeline->data['metadata'] = array();
+
+ if ($this->queryCapability('METADATA') ||
+ ((strlen($mailbox) == 0) &&
+ $this->queryCapability('METADATA-SERVER'))) {
+ $cmd_options = new Horde_Imap_Client_Data_Format_List();
+
+ if (!empty($options['maxsize'])) {
+ $cmd_options->add(array(
+ 'MAXSIZE',
+ new Horde_Imap_Client_Data_Format_Number($options['maxsize'])
+ ));
+ }
+ if (!empty($options['depth'])) {
+ $cmd_options->add(array(
+ 'DEPTH',
+ new Horde_Imap_Client_Data_Format_Number($options['depth'])
+ ));
+ }
+
+ $queries = new Horde_Imap_Client_Data_Format_List();
+ foreach ($entries as $md_entry) {
+ $queries->add(new Horde_Imap_Client_Data_Format_Astring($md_entry));
+ }
+
+ $cmd = $this->_command('GETMETADATA')->add(
+ new Horde_Imap_Client_Data_Format_Mailbox($mailbox)
+ );
+ if (count($cmd_options)) {
+ $cmd->add($cmd_options);
+ }
+ $cmd->add($queries);
+
+ $pipeline->add($cmd);
+ } else {
+ if (!$this->queryCapability('ANNOTATEMORE') &&
+ !$this->queryCapability('ANNOTATEMORE2')) {
+ throw new Horde_Imap_Client_Exception_NoSupportExtension('METADATA');
+ }
+
+ $queries = array();
+ foreach ($entries as $md_entry) {
+ list($entry, $type) = $this->_getAnnotateMoreEntry($md_entry);
+
+ if (!isset($queries[$type])) {
+ $queries[$type] = new Horde_Imap_Client_Data_Format_List();
+ }
+ $queries[$type]->add(new Horde_Imap_Client_Data_Format_String($entry));
+ }
+
+ foreach ($queries as $key => $val) {
+ // TODO: Honor maxsize and depth options.
+ $pipeline->add(
+ $this->_command('GETANNOTATION')->add(array(
+ new Horde_Imap_Client_Data_Format_Mailbox($mailbox),
+ $val,
+ new Horde_Imap_Client_Data_Format_String($key)
+ ))
+ );
+ }
+ }
+
+ return $this->_sendCmd($pipeline)->data['metadata'];
+ }
+
+ /**
+ * Split a name for the METADATA extension into the correct syntax for the
+ * older ANNOTATEMORE version.
+ *
+ * @param string $name A name for a metadata entry.
+ *
+ * @return array A list of two elements: The entry name and the value
+ * type.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ protected function _getAnnotateMoreEntry($name)
+ {
+ if (substr($name, 0, 7) == '/shared') {
+ return array(substr($name, 7), 'value.shared');
+ } else if (substr($name, 0, 8) == '/private') {
+ return array(substr($name, 8), 'value.priv');
+ }
+
+ throw new Horde_Imap_Client_Exception(
+ sprintf(Horde_Imap_Client_Translation::t("Invalid METADATA entry: \"%s\"."), $name),
+ Horde_Imap_Client_Exception::METADATA_INVALID
+ );
+ }
+
+ /**
+ */
+ protected function _setMetadata(Horde_Imap_Client_Mailbox $mailbox, $data)
+ {
+ if ($this->queryCapability('METADATA') ||
+ ((strlen($mailbox) == 0) &&
+ $this->queryCapability('METADATA-SERVER'))) {
+ $data_elts = new Horde_Imap_Client_Data_Format_List();
+
+ foreach ($data as $key => $value) {
+ $data_elts->add(array(
+ new Horde_Imap_Client_Data_Format_Astring($key),
+ new Horde_Imap_Client_Data_Format_Nstring($value)
+ ));
+ }
+
+ $cmd = $this->_command('SETMETADATA')->add(array(
+ new Horde_Imap_Client_Data_Format_Mailbox($mailbox),
+ $data_elts
+ ));
+ } else {
+ if (!$this->queryCapability('ANNOTATEMORE') &&
+ !$this->queryCapability('ANNOTATEMORE2')) {
+ throw new Horde_Imap_Client_Exception_NoSupportExtension('METADATA');
+ }
+
+ $cmd = $this->_pipeline();
+
+ foreach ($data as $md_entry => $value) {
+ list($entry, $type) = $this->_getAnnotateMoreEntry($md_entry);
+
+ $cmd->add(
+ $this->_command('SETANNOTATION')->add(array(
+ new Horde_Imap_Client_Data_Format_Mailbox($mailbox),
+ new Horde_Imap_Client_Data_Format_String($entry),
+ new Horde_Imap_Client_Data_Format_List(array(
+ new Horde_Imap_Client_Data_Format_String($type),
+ new Horde_Imap_Client_Data_Format_Nstring($value)
+ ))
+ ))
+ );
+ }
+ }
+
+ $this->_sendCmd($cmd);
+ }
+
+ /**
+ * Parse a METADATA response (RFC 5464 [4.4]).
+ *
+ * @param Horde_Imap_Client_Interaction_Pipeline $pipeline Pipeline
+ * object.
+ * @param Horde_Imap_Client_Tokenize $data The server response.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ protected function _parseMetadata(
+ Horde_Imap_Client_Interaction_Pipeline $pipeline,
+ Horde_Imap_Client_Tokenize $data
+ )
+ {
+ switch ($data->current()) {
+ case 'ANNOTATION':
+ $mbox = $data->next();
+ $entry = $data->next();
+
+ // Ignore unsolicited responses.
+ if ($data->next() !== true) {
+ break;
+ }
+
+ while (($type = $data->next()) !== false) {
+ switch ($type) {
+ case 'value.priv':
+ $pipeline->data['metadata'][$mbox]['/private' . $entry] = $data->next();
+ break;
+
+ case 'value.shared':
+ $pipeline->data['metadata'][$mbox]['/shared' . $entry] = $data->next();
+ break;
+
+ default:
+ throw new Horde_Imap_Client_Exception(
+ sprintf(Horde_Imap_Client_Translation::t("Invalid METADATA value type \"%s\"."), $type),
+ Horde_Imap_Client_Exception::METADATA_INVALID
+ );
+ }
+ }
+ break;
+
+ case 'METADATA':
+ $mbox = $data->next();
+
+ // Ignore unsolicited responses.
+ if ($data->next() !== true) {
+ break;
+ }
+
+ while (($entry = $data->next()) !== false) {
+ $pipeline->data['metadata'][$mbox][$entry] = $data->next();
+ }
+ break;
+ }
+ }
+
+ /* Overriden methods. */
+
+ /**
+ * @param array $opts Options:
+ * - decrement: (boolean) If true, decrement the message count.
+ * - pipeline: (Horde_Imap_Client_Interaction_Pipeline) Pipeline object.
+ */
+ protected function _deleteMsgs(Horde_Imap_Client_Mailbox $mailbox,
+ Horde_Imap_Client_Ids $ids,
+ array $opts = array())
+ {
+ /* If there are pending FETCH cache writes, we need to write them
+ * before the UID -> sequence number mapping changes. */
+ if (isset($opts['pipeline'])) {
+ $this->_updateCache($opts['pipeline']->fetch);
+ }
+
+ $res = parent::_deleteMsgs($mailbox, $ids);
+
+ if (isset($this->_temp['expunged'])) {
+ $this->_temp['expunged']->add($res);
+ }
+
+ if (!empty($opts['decrement'])) {
+ $mbox_ob = $this->_mailboxOb();
+ $mbox_ob->setStatus(
+ Horde_Imap_Client::STATUS_MESSAGES,
+ $mbox_ob->getStatus(Horde_Imap_Client::STATUS_MESSAGES) - count($ids)
+ );
+ }
+ }
+
+ /* Internal functions. */
+
+ /**
+ * Sends command(s) to the IMAP server. A connection to the server must
+ * have already been made.
+ *
+ * @param mixed $cmd Either a Command object or a Pipeline object.
+ *
+ * @return Horde_Imap_Client_Interaction_Pipeline A pipeline object.
+ * @throws Horde_Imap_Client_Exception
+ */
+ protected function _sendCmd($cmd)
+ {
+ $pipeline = ($cmd instanceof Horde_Imap_Client_Interaction_Command)
+ ? $this->_pipeline($cmd)
+ : $cmd;
+
+ if (!empty($this->_cmdQueue)) {
+ /* Add commands in reverse order. */
+ foreach (array_reverse($this->_cmdQueue) as $val) {
+ $pipeline->add($val, true);
+ }
+
+ $this->_cmdQueue = array();
+ }
+
+ $cmd_list = array();
+
+ foreach ($pipeline as $val) {
+ if ($val->continuation) {
+ $this->_sendCmdChunk($pipeline, $cmd_list);
+ $this->_sendCmdChunk($pipeline, array($val));
+ $cmd_list = array();
+ } else {
+ $cmd_list[] = $val;
+ }
+ }
+
+ $this->_sendCmdChunk($pipeline, $cmd_list);
+
+ /* If any FLAGS responses contain MODSEQs but not UIDs, don't
+ * cache any data and immediately close the mailbox. */
+ foreach ($pipeline->data['modseqs_nouid'] as $val) {
+ if (!$pipeline->fetch[$val]->getUid()) {
+ $this->_debug->info('Server provided FLAGS MODSEQ without providing UID.');
+ $this->close();
+ return $pipeline;
+ }
+ }
+
+ /* Update HIGHESTMODSEQ value. */
+ if (!empty($pipeline->data['modseqs'])) {
+ $modseq = max($pipeline->data['modseqs']);
+ $this->_mailboxOb()->setStatus(Horde_Imap_Client::STATUS_HIGHESTMODSEQ, $modseq);
+ $this->_updateModSeq($modseq);
+ }
+
+ /* Update cache items. */
+ $this->_updateCache($pipeline->fetch);
+
+ return $pipeline;
+ }
+
+ /**
+ * @param Horde_Imap_Client_Interaction_Pipeline $pipeline The pipeline
+ * object.
+ * @param array $chunk List of commands to send.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ protected function _sendCmdChunk($pipeline, $chunk)
+ {
+ if (empty($chunk)) {
+ return;
+ }
+
+ $cmd_count = count($chunk);
+ $exception = null;
+
+ foreach ($chunk as $val) {
+ try {
+ $old_debug = $this->_debug->debug;
+ if (!is_null($val->debug)) {
+ $this->_debug->raw($val->tag . ' ' . $val->debug . "\n");
+ $this->_debug->debug = false;
+ }
+ $this->_processCmd($pipeline, $val, $val);
+ $this->_connection->write('', true);
+ $this->_debug->debug = $old_debug;
+ } catch (Horde_Imap_Client_Exception $e) {
+ $this->_debug->debug = $old_debug;
+
+ switch ($e->getCode()) {
+ case Horde_Imap_Client_Exception::SERVER_WRITEERROR:
+ $this->_temp['logout'] = true;
+ $this->logout();
+ break;
+ }
+
+ throw $e;
+ }
+ }
+
+ while ($cmd_count) {
+ try {
+ if ($this->_getLine($pipeline) instanceof Horde_Imap_Client_Interaction_Server_Tagged) {
+ --$cmd_count;
+ }
+ } catch (Horde_Imap_Client_Exception $e) {
+ switch ($e->getCode()) {
+ case $e::DISCONNECT:
+ $this->_temp['logout'] = true;
+ // Fall-through
+
+ case $e::SERVER_READERROR:
+ $this->logout();
+ throw $e;
+ }
+
+ // Catch and store exception; don't throw until all input
+ // is read. (For now, only store first exception.)
+ if (is_null($exception)) {
+ $exception = $e;
+ }
+
+ if (($e instanceof Horde_Imap_Client_Exception_ServerResponse) &&
+ $e->command) {
+ --$cmd_count;
+ }
+ }
+ }
+
+ if (!is_null($exception)) {
+ throw $exception;
+ }
+ }
+
+ /**
+ * Process/send a command to the remote server.
+ *
+ * @param Horde_Imap_Client_Interaction_Pipeline $pipeline The pipeline
+ * object.
+ * @param Horde_Imap_Client_Interaction_Command $cmd The master command.
+ * @param Horde_Imap_Client_Data_Format_List $data Commands to send.
+ *
+ * @throws Horde_Imap_Client_Exception
+ * @throws Horde_Imap_Client_Exception_NoSupport
+ */
+ protected function _processCmd($pipeline, $cmd, $data)
+ {
+ foreach ($data as $key => $val) {
+ if ($val instanceof Horde_Imap_Client_Interaction_Command_Continuation) {
+ $this->_connection->write('', true);
+
+ $this->_processCmd(
+ $pipeline,
+ $cmd,
+ $val->getCommands($this->_processCmdContinuation($pipeline))
+ );
+ continue;
+ }
+
+ if ($key) {
+ $this->_connection->write(' ');
+ }
+
+ if ($val instanceof Horde_Imap_Client_Data_Format_List) {
+ $this->_connection->write('(');
+ $this->_processCmd($pipeline, $cmd, $val);
+ $this->_connection->write(')');
+ } elseif (($val instanceof Horde_Imap_Client_Data_Format_String) &&
+ $val->literal()) {
+ /* RFC 3516/4466: Send literal8 if we have binary data. */
+ if ($cmd->literal8 &&
+ $val->binary() &&
+ $this->queryCapability('BINARY')) {
+ $binary = true;
+ $this->_connection->write('~');
+ } else {
+ $binary = false;
+ }
+
+ $literal_len = $val->length();
+ $this->_connection->write('{' . $literal_len);
+
+ /* RFC 2088 - If LITERAL+ is available, saves a roundtrip from
+ * the server. */
+ if ($cmd->literalplus && $this->queryCapability('LITERAL+')) {
+ $this->_connection->write('+}', true);
+ } else {
+ $this->_connection->write('}', true);
+ $this->_processCmdContinuation($pipeline);
+ }
+
+ $this->_connection->writeLiteral($val->getStream(), $literal_len, $binary);
+ } else {
+ $this->_connection->write($val->escape());
+ }
+ }
+ }
+
+ /**
+ * Process a command continuation response.
+ *
+ * @param Horde_Imap_Client_Interaction_Pipeline $pipeline The pipeline
+ * object.
+ *
+ * @return Horde_Imap_Client_Interaction_Server_Continuation Continuation
+ * object.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ protected function _processCmdContinuation($pipeline)
+ {
+ $ob = $this->_getLine($pipeline);
+ if ($ob instanceof Horde_Imap_Client_Interaction_Server_Continuation) {
+ return $ob;
+ }
+
+ $this->_debug->info("ERROR: Unexpected response from server while waiting for a continuation request.");
+ $e = new Horde_Imap_Client_Exception(
+ Horde_Imap_Client_Translation::t("Error when communicating with the mail server."),
+ Horde_Imap_Client_Exception::SERVER_READERROR
+ );
+ $e->details = strval($ob);
+
+ throw $e;
+ }
+
+ /**
+ * Shortcut to creating a new IMAP client command object.
+ *
+ * @param string $cmd The IMAP command.
+ *
+ * @return Horde_Imap_Client_Interaction_Command A command object.
+ */
+ protected function _command($cmd)
+ {
+ return new Horde_Imap_Client_Interaction_Command($cmd, ++$this->_tag);
+ }
+
+ /**
+ * Shortcut to creating a new pipeline object.
+ *
+ * @param Horde_Imap_Client_Interaction_Command $cmd An IMAP command to
+ * add.
+ *
+ * @return Horde_Imap_Client_Interaction_Pipeline A pipeline object.
+ */
+ protected function _pipeline($cmd = null)
+ {
+ if (!isset($this->_temp['fetchob'])) {
+ $this->_temp['fetchob'] = new Horde_Imap_Client_Fetch_Results(
+ $this->_fetchDataClass,
+ Horde_Imap_Client_Fetch_Results::SEQUENCE
+ );
+ }
+
+ $ob = new Horde_Imap_Client_Interaction_Pipeline(
+ clone $this->_temp['fetchob']
+ );
+
+ if (!is_null($cmd)) {
+ $ob->add($cmd);
+ }
+
+ return $ob;
+ }
+
+ /**
+ * Gets data from the IMAP server stream and parses it.
+ *
+ * @param Horde_Imap_Client_Interaction_Pipeline $pipeline Pipeline
+ * object.
+ *
+ * @return Horde_Imap_Client_Interaction_Server Server object.
+ *
+ * @throws Horde_Imap_Client_Exception
+ */
+ protected function _getLine(
+ Horde_Imap_Client_Interaction_Pipeline $pipeline
+ )
+ {
+ $server = Horde_Imap_Client_Interaction_Server::create(
+ $this->_connection->read()
+ );
+
+ switch (get_class($server)) {
+ case 'Horde_Imap_Client_Interaction_Server_Continuation':
+ $this->_responseCode($pipeline, $server);
+ break;
+
+ case 'Horde_Imap_Client_Interaction_Server_Tagged':
+ $pipeline->complete($server);
+ $this->_responseCode($pipeline, $server);
+ break;
+
+ case 'Horde_Imap_Client_Interaction_Server_Untagged':
+ if (is_null($server->status)) {
+ $this->_serverResponse($pipeline, $server);
+ } else {
+ $this->_responseCode($pipeline, $server);
+ }
+ break;
+ }
+
+ switch ($server->status) {
+ case $server::BAD:
+ /* A tagged BAD response indicates that the tagged command caused
+ * the error. This information is unknown if untagged (RFC 3501
+ * [7.1.3]). */
+ throw new Horde_Imap_Client_Exception_ServerResponse(
+ Horde_Imap_Client_Translation::t("IMAP error reported by server."),
+ 0,
+ $server,
+ $pipeline
+ );
+
+ case $server::BYE:
+ /* A BYE response received as part of a logout command should be
+ * be treated like a regular command: a client MUST process the
+ * entire command until logging out (RFC 3501 [3.4; 7.1.5]). */
+ if (empty($this->_temp['logout'])) {
+ $e = new Horde_Imap_Client_Exception(
+ Horde_Imap_Client_Translation::t("IMAP Server closed the connection."),
+ Horde_Imap_Client_Exception::DISCONNECT
+ );
+ $e->details = strval($server);
+ throw $e;
+ }
+ break;
+
+ case $server::NO:
+ /* An untagged NO response indicates a warning; ignore and assume
+ * that it also included response text code that is handled
+ * elsewhere. Throw exception if tagged; command handlers can
+ * catch this if able to workaround this issue (RFC 3501
+ * [7.1.2]). */
+ if ($server instanceof Horde_Imap_Client_Interaction_Server_Tagged) {
+ throw new Horde_Imap_Client_Exception_ServerResponse(
+ Horde_Imap_Client_Translation::t("IMAP error reported by server."),
+ 0,
+ $server,
+ $pipeline
+ );
+ }
+
+ case $server::PREAUTH:
+ /* The user was pre-authenticated. (RFC 3501 [7.1.4]) */
+ $this->_temp['preauth'] = true;
+ break;
+ }
+
+ return $server;
+ }
+
+ /**
+ * Handle untagged server responses (see RFC 3501 [2.2.2]).
+ *
+ * @param Horde_Imap_Client_Interaction_Pipeline $pipeline Pipeline
+ * object.
+ * @param Horde_Imap_Client_Interaction_Server $ob Server
+ * response.
+ */
+ protected function _serverResponse(
+ Horde_Imap_Client_Interaction_Pipeline $pipeline,
+ Horde_Imap_Client_Interaction_Server $ob
+ )
+ {
+ $token = $ob->token;
+
+ /* First, catch untagged responses where the name appears first on the
+ * line. */
+ switch ($first = strtoupper($token->current())) {
+ case 'CAPABILITY':
+ $this->_parseCapability($pipeline, $token->flushIterator());
+ break;
+
+ case 'LIST':
+ case 'LSUB':
+ $this->_parseList($pipeline, $token);
+ break;
+
+ case 'STATUS':
+ // Parse a STATUS response (RFC 3501 [7.2.4]).
+ $this->_parseStatus($token);
+ break;
+
+ case 'SEARCH':
+ case 'SORT':
+ // Parse a SEARCH/SORT response (RFC 3501 [7.2.5] & RFC 5256 [4]).
+ $this->_parseSearch($pipeline, $token->flushIterator());
+ break;
+
+ case 'ESEARCH':
+ // Parse an ESEARCH response (RFC 4466 [2.6.2]).
+ $this->_parseEsearch($pipeline, $token);
+ break;
+
+ case 'FLAGS':
+ $token->next();
+ $this->_mailboxOb()->setStatus(Horde_Imap_Client::STATUS_FLAGS, array_map('strtolower', $token->flushIterator()));
+ break;
+
+ case 'QUOTA':
+ $this->_parseQuota($pipeline, $token);
+ break;
+
+ case 'QUOTAROOT':
+ // Ignore this line - we can get this information from
+ // the untagged QUOTA responses.
+ break;
+
+ case 'NAMESPACE':
+ $this->_parseNamespace($pipeline, $token);
+ break;
+
+ case 'THREAD':
+ $this->_parseThread($pipeline, $token);
+ break;
+
+ case 'ACL':
+ $this->_parseACL($pipeline, $token);
+ break;
+
+ case 'LISTRIGHTS':
+ $this->_parseListRights($pipeline, $token);
+ break;
+
+ case 'MYRIGHTS':
+ $this->_parseMyRights($pipeline, $token);
+ break;
+
+ case 'ID':
+ // ID extension (RFC 2971)
+ $this->_parseID($pipeline, $token);
+ break;
+
+ case 'ENABLED':
+ // ENABLE extension (RFC 5161)
+ $this->_parseEnabled($token);
+ break;
+
+ case 'LANGUAGE':
+ // LANGUAGE extension (RFC 5255 [3.2])
+ $this->_parseLanguage($token);
+ break;
+
+ case 'COMPARATOR':
+ // I18NLEVEL=2 extension (RFC 5255 [4.7])
+ $this->_parseComparator($pipeline, $token);
+ break;
+
+ case 'VANISHED':
+ // QRESYNC extension (RFC 5162 [3.6])
+ $this->_parseVanished($pipeline, $token);
+ break;
+
+ case 'ANNOTATION':
+ case 'METADATA':
+ // Parse a ANNOTATEMORE/METADATA response.
+ $this->_parseMetadata($pipeline, $token);
+ break;
+
+ default:
+ // Next, look for responses where the keywords occur second.
+ switch (strtoupper($token->next())) {
+ case 'EXISTS':
+ // EXISTS response - RFC 3501 [7.3.2]
+ $mbox_ob = $this->_mailboxOb();
+
+ // Increment UIDNEXT if it is set.
+ if ($mbox_ob->open &&
+ ($uidnext = $mbox_ob->getStatus(Horde_Imap_Client::STATUS_UIDNEXT))) {
+ $mbox_ob->setStatus(Horde_Imap_Client::STATUS_UIDNEXT, $uidnext + $first - $mbox_ob->getStatus(Horde_Imap_Client::STATUS_MESSAGES));
+ }
+
+ $mbox_ob->setStatus(Horde_Imap_Client::STATUS_MESSAGES, $first);
+ break;
+
+ case 'RECENT':
+ // RECENT response - RFC 3501 [7.3.1]
+ $this->_mailboxOb()->setStatus(Horde_Imap_Client::STATUS_RECENT, $first);
+ break;
+
+ case 'EXPUNGE':
+ // EXPUNGE response - RFC 3501 [7.4.1]
+ $this->_deleteMsgs($this->_selected, $this->getIdsOb($first, true), array(
+ 'decrement' => true,
+ 'pipeline' => $pipeline
+ ));
+ $pipeline->data['expunge_seen'] = true;
+ break;
+
+ case 'FETCH':
+ // FETCH response - RFC 3501 [7.4.2]
+ $this->_parseFetch($pipeline, $first, $token);
+ break;
+ }
+ break;
+ }
+ }
+
+ /**
+ * Handle status responses (see RFC 3501 [7.1]).
+ *
+ * @param Horde_Imap_Client_Interaction_Pipeline $pipeline Pipeline
+ * object.
+ * @param Horde_Imap_Client_Interaction_Server $ob Server object.
+ *
+ * @throws Horde_Imap_Client_Exception_ServerResponse
+ */
+ protected function _responseCode(
+ Horde_Imap_Client_Interaction_Pipeline $pipeline,
+ Horde_Imap_Client_Interaction_Server $ob
+ )
+ {
+ if (is_null($ob->responseCode)) {
+ return;
+ }
+
+ $rc = $ob->responseCode;
+
+ switch ($rc->code) {
+ case 'ALERT':
+ // Defined by RFC 5530 [3] - Treat as an alert for now.
+ case 'CONTACTADMIN':
+ if (!isset($this->_temp['alerts'])) {
+ $this->_temp['alerts'] = array();
+ }
+ $this->_temp['alerts'][] = strval($ob->token);
+ break;
+
+ case 'BADCHARSET':
+ /* Store valid search charsets if returned by server. */
+ $s_charset = array();
+ foreach ($rc->data[0] as $val) {
+ $s_charset[$val] = true;
+ }
+
+ if (!empty($s_charset)) {
+ $this->_setInit('s_charset', array_merge(
+ $this->_init['s_charset'],
+ $s_charset
+ ));
+ }
+
+ throw new Horde_Imap_Client_Exception_ServerResponse(
+ Horde_Imap_Client_Translation::t("Charset used in search query is not supported on the mail server."),
+ Horde_Imap_Client_Exception::BADCHARSET,
+ $ob,
+ $pipeline
+ );
+
+ case 'CAPABILITY':
+ $this->_parseCapability($pipeline, $rc->data);
+ break;
+
+ case 'PARSE':
+ /* Only throw error on NO/BAD. Message is human readable. */
+ switch ($ob->status) {
+ case Horde_Imap_Client_Interaction_Server::BAD:
+ case Horde_Imap_Client_Interaction_Server::NO:
+ throw new Horde_Imap_Client_Exception_ServerResponse(
+ sprintf(Horde_Imap_Client_Translation::t("The mail server was unable to parse the contents of the mail message: %s"), strval($ob->token)),
+ Horde_Imap_Client_Exception::PARSEERROR,
+ $ob,
+ $pipeline
+ );
+ }
+ break;
+
+ case 'READ-ONLY':
+ $this->_mode = Horde_Imap_Client::OPEN_READONLY;
+ break;
+
+ case 'READ-WRITE':
+ $this->_mode = Horde_Imap_Client::OPEN_READWRITE;
+ break;
+
+ case 'TRYCREATE':
+ // RFC 3501 [7.1]
+ $pipeline->data['trycreate'] = true;
+ break;
+
+ case 'PERMANENTFLAGS':
+ $this->_mailboxOb()->setStatus(Horde_Imap_Client::STATUS_PERMFLAGS, array_map('strtolower', $rc->data[0]));
+ break;
+
+ case 'UIDNEXT':
+ $this->_mailboxOb()->setStatus(Horde_Imap_Client::STATUS_UIDNEXT, $rc->data[0]);
+ break;
+
+ case 'UIDVALIDITY':
+ $this->_mailboxOb()->setStatus(Horde_Imap_Client::STATUS_UIDVALIDITY, $rc->data[0]);
+ break;
+
+ case 'UNSEEN':
+ /* This is different from the STATUS UNSEEN response - this item,
+ * if defined, returns the first UNSEEN message in the mailbox. */
+ $this->_mailboxOb()->setStatus(Horde_Imap_Client::STATUS_FIRSTUNSEEN, $rc->data[0]);
+ break;
+
+ case 'REFERRAL':
+ // Defined by RFC 2221
+ $pipeline->data['referral'] = new Horde_Imap_Client_Url($rc->data[0]);
+ break;
+
+ case 'UNKNOWN-CTE':
+ // Defined by RFC 3516
+ throw new Horde_Imap_Client_Exception_ServerResponse(
+ Horde_Imap_Client_Translation::t("The mail server was unable to parse the contents of the mail message."),
+ Horde_Imap_Client_Exception::UNKNOWNCTE,
+ $ob,
+ $pipeline
+ );
+
+ case 'APPENDUID':
+ // Defined by RFC 4315
+ // APPENDUID: [0] = UIDVALIDITY, [1] = UID(s)
+ $pipeline->data['appenduid'] = $this->getIdsOb($rc->data[1]);
+ break;
+
+ case 'COPYUID':
+ // Defined by RFC 4315
+ // COPYUID: [0] = UIDVALIDITY, [1] = UIDFROM, [2] = UIDTO
+ $pipeline->data['copyuid'] = array_combine(
+ $this->getIdsOb($rc->data[1])->ids,
+ $this->getIdsOb($rc->data[2])->ids
+ );
+
+ /* Use UIDPLUS information to move cached data to new mailbox (see
+ * RFC 4549 [4.2.2.1]). Need to move now, because a MOVE might
+ * EXPUNGE immediately afterwards. */
+ $this->_moveCache($pipeline->data['copydest'], $pipeline->data['copyuid'], $rc->data[0]);
+ break;
+
+ case 'UIDNOTSTICKY':
+ // Defined by RFC 4315 [3]
+ $this->_mailboxOb()->setStatus(Horde_Imap_Client::STATUS_UIDNOTSTICKY, true);
+ break;
+
+ case 'BADURL':
+ // Defined by RFC 4469 [4.1]
+ throw new Horde_Imap_Client_Exception_ServerResponse(
+ Horde_Imap_Client_Translation::t("Could not save message on server."),
+ Horde_Imap_Client_Exception::CATENATE_BADURL,
+ $ob,
+ $pipeline
+ );
+
+ case 'TOOBIG':
+ // Defined by RFC 4469 [4.2]
+ throw new Horde_Imap_Client_Exception_ServerResponse(
+ Horde_Imap_Client_Translation::t("Could not save message data because it is too large."),
+ Horde_Imap_Client_Exception::CATENATE_TOOBIG,
+ $ob,
+ $pipeline
+ );
+
+ case 'HIGHESTMODSEQ':
+ // Defined by RFC 4551 [3.1.1]
+ $pipeline->data['modseqs'][] = $rc->data[0];
+ break;
+
+ case 'NOMODSEQ':
+ // Defined by RFC 4551 [3.1.2]
+ $pipeline->data['modseqs'][] = 0;
+ break;
+
+ case 'MODIFIED':
+ // Defined by RFC 4551 [3.2]
+ $pipeline->data['modified']->add($rc->data[0]);
+ break;
+
+ case 'CLOSED':
+ // Defined by RFC 5162 [3.7]
+ if (isset($pipeline->data['qresyncmbox'])) {
+ /* If there is any pending FETCH cache entries, flush them
+ * now before changing mailboxes. */
+ $this->_updateCache($pipeline->fetch);
+ $pipeline->fetch->clear();
+
+ $this->_changeSelected(
+ $pipeline->data['qresyncmbox'][0],
+ $pipeline->data['qresyncmbox'][1]
+ );
+ unset($pipeline->data['qresyncmbox']);
+ }
+ break;
+
+ case 'NOTSAVED':
+ // Defined by RFC 5182 [2.5]
+ $pipeline->data['searchnotsaved'] = true;
+ break;
+
+ case 'BADCOMPARATOR':
+ // Defined by RFC 5255 [4.9]
+ throw new Horde_Imap_Client_Exception_ServerResponse(
+ Horde_Imap_Client_Translation::t("The comparison algorithm was not recognized by the server."),
+ Horde_Imap_Client_Exception::BADCOMPARATOR,
+ $ob,
+ $pipeline
+ );
+
+ case 'METADATA':
+ $md = $rc->data[0];
+
+ switch ($md[0]) {
+ case 'LONGENTRIES':
+ // Defined by RFC 5464 [4.2.1]
+ $pipeline->data['metadata']['*longentries'] = intval($md[1]);
+ break;
+
+ case 'MAXSIZE':
+ // Defined by RFC 5464 [4.3]
+ throw new Horde_Imap_Client_Exception_ServerResponse(
+ Horde_Imap_Client_Translation::t("The metadata item could not be saved because it is too large."),
+ Horde_Imap_Client_Exception::METADATA_MAXSIZE,
+ $ob,
+ $pipeline
+ );
+
+ case 'NOPRIVATE':
+ // Defined by RFC 5464 [4.3]
+ throw new Horde_Imap_Client_Exception_ServerResponse(
+ Horde_Imap_Client_Translation::t("The metadata item could not be saved because the server does not support private annotations."),
+ Horde_Imap_Client_Exception::METADATA_NOPRIVATE,
+ $ob,
+ $pipeline
+ );
+
+ case 'TOOMANY':
+ // Defined by RFC 5464 [4.3]
+ throw new Horde_Imap_Client_Exception_ServerResponse(
+ Horde_Imap_Client_Translation::t("The metadata item could not be saved because the maximum number of annotations has been exceeded."),
+ Horde_Imap_Client_Exception::METADATA_TOOMANY,
+ $ob,
+ $pipeline
+ );
+ }
+ break;
+
+ case 'UNAVAILABLE':
+ // Defined by RFC 5530 [3]
+ $pipeline->data['loginerr'] = new Horde_Imap_Client_Exception(
+ Horde_Imap_Client_Translation::t("Remote server is temporarily unavailable."),
+ Horde_Imap_Client_Exception::LOGIN_UNAVAILABLE
+ );
+ break;
+
+ case 'AUTHENTICATIONFAILED':
+ // Defined by RFC 5530 [3]
+ $pipeline->data['loginerr'] = new Horde_Imap_Client_Exception(
+ Horde_Imap_Client_Translation::t("Authentication failed."),
+ Horde_Imap_Client_Exception::LOGIN_AUTHENTICATIONFAILED
+ );
+ break;
+
+ case 'AUTHORIZATIONFAILED':
+ // Defined by RFC 5530 [3]
+ $pipeline->data['loginerr'] = new Horde_Imap_Client_Exception(
+ Horde_Imap_Client_Translation::t("Authentication was successful, but authorization failed."),
+ Horde_Imap_Client_Exception::LOGIN_AUTHORIZATIONFAILED
+ );
+ break;
+
+ case 'EXPIRED':
+ // Defined by RFC 5530 [3]
+ $pipeline->data['loginerr'] = new Horde_Imap_Client_Exception(
+ Horde_Imap_Client_Translation::t("Authentication credentials have expired."),
+ Horde_Imap_Client_Exception::LOGIN_EXPIRED
+ );
+ break;
+
+ case 'PRIVACYREQUIRED':
+ // Defined by RFC 5530 [3]
+ $pipeline->data['loginerr'] = new Horde_Imap_Client_Exception(
+ Horde_Imap_Client_Translation::t("Operation failed due to a lack of a secure connection."),
+ Horde_Imap_Client_Exception::LOGIN_PRIVACYREQUIRED
+ );
+ break;
+
+ case 'NOPERM':
+ // Defined by RFC 5530 [3]
+ throw new Horde_Imap_Client_Exception_ServerResponse(
+ Horde_Imap_Client_Translation::t("You do not have adequate permissions to carry out this operation."),
+ Horde_Imap_Client_Exception::NOPERM,
+ $ob,
+ $pipeline
+ );
+
+ case 'INUSE':
+ // Defined by RFC 5530 [3]
+ throw new Horde_Imap_Client_Exception_ServerResponse(
+ Horde_Imap_Client_Translation::t("There was a temporary issue when attempting this operation. Please try again later."),
+ Horde_Imap_Client_Exception::INUSE,
+ $ob,
+ $pipeline
+ );
+
+ case 'EXPUNGEISSUED':
+ // Defined by RFC 5530 [3]
+ $pipeline->data['expungeissued'] = true;
+ break;
+
+ case 'CORRUPTION':
+ // Defined by RFC 5530 [3]
+ throw new Horde_Imap_Client_Exception_ServerResponse(
+ Horde_Imap_Client_Translation::t("The mail server is reporting corrupt data in your mailbox."),
+ Horde_Imap_Client_Exception::CORRUPTION,
+ $ob,
+ $pipeline
+ );
+
+ case 'SERVERBUG':
+ case 'CLIENTBUG':
+ case 'CANNOT':
+ // Defined by RFC 5530 [3]
+ $this->_debug->info("ERROR: mail server explicitly reporting an error.");
+ break;
+
+ case 'LIMIT':
+ // Defined by RFC 5530 [3]
+ throw new Horde_Imap_Client_Exception_ServerResponse(
+ Horde_Imap_Client_Translation::t("The mail server has denied the request."),
+ Horde_Imap_Client_Exception::LIMIT,
+ $ob,
+ $pipeline
+ );
+
+ case 'OVERQUOTA':
+ // Defined by RFC 5530 [3]
+ throw new Horde_Imap_Client_Exception_ServerResponse(
+ Horde_Imap_Client_Translation::t("The operation failed because the quota has been exceeded on the mail server."),
+ Horde_Imap_Client_Exception::OVERQUOTA,
+ $ob,
+ $pipeline
+ );
+
+ case 'ALREADYEXISTS':
+ // Defined by RFC 5530 [3]
+ throw new Horde_Imap_Client_Exception_ServerResponse(
+ Horde_Imap_Client_Translation::t("The object could not be created because it already exists."),
+ Horde_Imap_Client_Exception::ALREADYEXISTS,
+ $ob,
+ $pipeline
+ );
+
+ case 'NONEXISTENT':
+ // Defined by RFC 5530 [3]
+ throw new Horde_Imap_Client_Exception_ServerResponse(
+ Horde_Imap_Client_Translation::t("The object could not be deleted because it does not exist."),
+ Horde_Imap_Client_Exception::NONEXISTENT,
+ $ob,
+ $pipeline
+ );
+
+ case 'USEATTR':
+ // Defined by RFC 6154 [3]
+ throw new Horde_Imap_Client_Exception_ServerResponse(
+ Horde_Imap_Client_Translation::t("The special-use attribute requested for the mailbox is not supported."),
+ Horde_Imap_Client_Exception::USEATTR,
+ $ob,
+ $pipeline
+ );
+
+ case 'DOWNGRADED':
+ // Defined by RFC 6858 [3]
+ $downgraded = $this->getIdsOb($rc->data[0]);
+ foreach ($pipeline->fetch as $val) {
+ if (in_array($val->getUid(), $downgraded)) {
+ $val->setDowngraded(true);
+ }
+ }
+ break;
+
+ case 'XPROXYREUSE':
+ // The proxy connection was reused, so no need to do login tasks.
+ $pipeline->data['proxyreuse'] = true;
+ break;
+
+ default:
+ // Unknown response codes SHOULD be ignored - RFC 3501 [7.1]
+ break;
+ }
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientTokenizephp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Tokenize.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Tokenize.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Tokenize.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,310 @@
</span><ins>+<?php
+/**
+ * Copyright 2012-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * Tokenization of an IMAP data stream.
+ *
+ * NOTE: This class is NOT intended to be accessed outside of this package.
+ * There is NO guarantees that the API of this class will not change across
+ * versions.
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @internal
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ *
+ * @property-read boolean $eos Has the end of the stream been reached?
+ */
+class Horde_Imap_Client_Tokenize implements Iterator
+{
+ /**
+ * Current data.
+ *
+ * @var mixed
+ */
+ protected $_current = false;
+
+ /**
+ * Current key.
+ *
+ * @var integer
+ */
+ protected $_key = false;
+
+ /**
+ * Sublevel.
+ *
+ * @var integer
+ */
+ protected $_level = false;
+
+ /**
+ * Data stream.
+ *
+ * @var Horde_Stream
+ */
+ protected $_stream;
+
+ /**
+ * Constructor.
+ *
+ * @param mixed $data Data to add (string, resource, or Horde_Stream
+ * object).
+ */
+ public function __construct($data = null)
+ {
+ $this->_stream = new Horde_Stream_Temp();
+
+ if (!is_null($data)) {
+ $this->add($data);
+ }
+ }
+
+ /**
+ */
+ public function __get($name)
+ {
+ switch ($name) {
+ case 'eos':
+ return feof($this->_stream->stream);
+ }
+ }
+
+ /**
+ */
+ public function __sleep()
+ {
+ throw new LogicException('Object can not be serialized.');
+ }
+
+ /**
+ */
+ public function __toString()
+ {
+ $pos = ftell($this->_stream->stream);
+ $out = $this->_current . ' ' . $this->_stream->getString();
+ fseek($this->_stream->stream, $pos);
+ return $out;
+ }
+
+ /**
+ * Add data to buffer.
+ *
+ * @param mixed $data Data to add (string, resource, or Horde_Stream
+ * object).
+ */
+ public function add($data)
+ {
+ $this->_stream->add($data);
+ }
+
+ /**
+ * Flush the remaining entries left in the iterator.
+ *
+ * @param boolean $return If true, return entries. Only returns entries
+ * on the current level.
+ * @param boolean $sublevel Only flush items in current sublevel?
+ *
+ * @return array The entries if $return is true.
+ */
+ public function flushIterator($return = true, $sublevel = true)
+ {
+ $out = array();
+
+ if ($return) {
+ $level = $sublevel ? $this->_level : 0;
+ do {
+ $curr = $this->next();
+ if ($this->_level < $level) {
+ break;
+ }
+
+ if (!is_bool($curr) && ($level == $this->_level)) {
+ $out[] = $curr;
+ }
+ } while (($curr !== false) || $this->_level || !$this->eos);
+ } elseif ($sublevel && $this->_level) {
+ $level = $this->_level;
+ while ($level <= $this->_level) {
+ $this->next();
+ }
+ } else {
+ fseek($this->_stream->stream, 0, SEEK_END);
+ fgetc($this->_stream->stream);
+ $this->_current = $this->_key = $this->_level = false;
+ }
+
+ return $out;
+ }
+
+ /**
+ * Return literal length data located at the end of the stream.
+ *
+ * @return mixed Null if no literal data found, or an array with these
+ * keys:
+ * - binary: (boolean) True if this is a literal8.
+ * - length: (integer) Length of the literal.
+ */
+ public function getLiteralLength()
+ {
+ fseek($this->_stream->stream, -1, SEEK_END);
+ if ($this->_stream->peek() == '}') {
+ $literal_data = $this->_stream->getString($this->_stream->search('{', true) - 1);
+ $literal_len = substr($literal_data, 2, -1);
+
+ if (is_numeric($literal_len)) {
+ return array(
+ 'binary' => ($literal_data[0] == '~'),
+ 'length' => intval($literal_len)
+ );
+ }
+ }
+
+ return null;
+ }
+
+ /* Iterator methods. */
+
+ /**
+ */
+ public function current()
+ {
+ return $this->_current;
+ }
+
+ /**
+ */
+ public function key()
+ {
+ return $this->_key;
+ }
+
+ /**
+ * @return mixed Either a string, boolean (true for open paren, false for
+ * close paren/EOS), or null.
+ */
+ public function next()
+ {
+ if ((($this->_current = $this->_parseStream()) === false) &&
+ $this->eos) {
+ $this->_key = $this->_level = false;
+ } else {
+ ++$this->_key;
+ }
+
+ return $this->_current;
+ }
+
+ /**
+ */
+ public function rewind()
+ {
+ fseek($this->_stream->stream, 0);
+ $this->_current = false;
+ $this->_key = -1;
+ $this->_level = 0;
+ }
+
+ /**
+ */
+ public function valid()
+ {
+ return ($this->_level !== false);
+ }
+
+ /**
+ * Returns the next token and increments the internal stream pointer.
+ *
+ * @see next()
+ */
+ protected function _parseStream()
+ {
+ $in_quote = false;
+ $stream = $this->_stream->stream;
+ $text = '';
+
+ while (($c = fgetc($stream)) !== false) {
+ switch ($c) {
+ case '\\':
+ $text .= $in_quote
+ ? fgetc($stream)
+ : $c;
+ break;
+
+ case '"':
+ if ($in_quote) {
+ return $text;
+ } else {
+ $in_quote = true;
+ }
+ break;
+
+ default:
+ if ($in_quote) {
+ $text .= $c;
+ break;
+ }
+
+ switch ($c) {
+ case '(':
+ ++$this->_level;
+ return true;
+
+ case ')':
+ if (strlen($text)) {
+ fseek($stream, -1, SEEK_CUR);
+ break 3;
+ }
+ --$this->_level;
+ return false;
+
+ case '~':
+ // Ignore binary string identifier. PHP strings are
+ // binary-safe.
+ break;
+
+ case '{':
+ return stream_get_contents($stream, $this->_stream->getToChar('}'));
+
+ case ' ':
+ if (strlen($text)) {
+ break 3;
+ }
+ break;
+
+ default:
+ $text .= $c;
+ break;
+ }
+ break;
+ }
+ }
+
+ switch (strlen($text)) {
+ case 0:
+ return false;
+
+ case 3:
+ if (strcasecmp($text, 'NIL') === 0) {
+ return null;
+ }
+ // Fall-through
+
+ default:
+ return $text;
+ }
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientUrlphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Url.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Url.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Url.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,285 @@
</span><ins>+<?php
+/**
+ * Copyright 2008-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2008-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * Object representation of a a POP3 (RFC 2384) or IMAP (RFC 5092/5593) URL.
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2008-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ *
+ * @property-read boolean $relative Is this a relative URL?
+ */
+class Horde_Imap_Client_Url implements Serializable
+{
+ /**
+ * The authentication method to use.
+ *
+ * @var string
+ */
+ public $auth = null;
+
+ /**
+ * The remote server (not present for relative URLs).
+ *
+ * @var string
+ */
+ public $hostspec = null;
+
+ /**
+ * The IMAP mailbox
+ *
+ * @var string
+ */
+ public $mailbox = null;
+
+ /**
+ * A byte range for use with IMAP FETCH.
+ *
+ * @var string
+ */
+ public $partial = null;
+
+ /**
+ * The remote port (not present for relative URLs).
+ *
+ * @var integer
+ */
+ public $port = null;
+
+ /**
+ * The protocol type. Either 'imap' or 'pop' (not present for relative
+ * URLs).
+ *
+ * @var string
+ */
+ public $protocol = null;
+
+ /**
+ * A search query to be run with IMAP SEARCH.
+ *
+ * @var string
+ */
+ public $search = null;
+
+ /**
+ * A MIME part ID.
+ *
+ * @var string
+ */
+ public $section = null;
+
+ /**
+ * The username to use on the remote server.
+ *
+ * @var string
+ */
+ public $username = null;
+
+ /**
+ * The IMAP UID.
+ *
+ * @var string
+ */
+ public $uid = null;
+
+ /**
+ * The IMAP UIDVALIDITY for the given mailbox.
+ *
+ * @var integer
+ */
+ public $uidvalidity = null;
+
+ /**
+ * URLAUTH info (not parsed).
+ *
+ * @var string
+ */
+ public $urlauth = null;
+
+ /**
+ * Constructor.
+ *
+ * Absolute IMAP URLs takes one of the following forms:
+ * - imap://<iserver>[/]
+ * - imap://<iserver>/<enc-mailbox>[<uidvalidity>][?<enc-search>]
+ * - imap://<iserver>/<enc-mailbox>[<uidvalidity>]<iuid>[<isection>][<ipartial>][<iurlauth>]
+ *
+ * POP URLs take one of the following forms:
+ * - pop://<user>;auth=<auth>@<host>:<port>
+ *
+ * @param string $url A URL string.
+ */
+ public function __construct($url = null)
+ {
+ if (!is_null($url)) {
+ $this->_parse($url);
+ }
+ }
+
+ /**
+ * Create a POP3 (RFC 2384) or IMAP (RFC 5092/5593) URL.
+ *
+ * @return string A URL string.
+ */
+ public function __toString()
+ {
+ $url = '';
+
+ if (!is_null($this->protocol)) {
+ $url = $this->protocol . '://';
+
+ if (!is_null($this->username)) {
+ $url .= $this->username;
+ }
+
+ if (!is_null($this->auth)) {
+ $url .= ';AUTH=' . $this->auth . '@';
+ } elseif (!is_null($this->username)) {
+ $url .= '@';
+ }
+
+ $url .= $this->hostspec;
+
+ if (!is_null($this->port) && ($this->port != 143)) {
+ $url .= ':' . $this->port;
+ }
+ }
+
+ $url .= '/';
+
+ if (is_null($this->protocol) || ($this->protocol == 'imap')) {
+ $url .= urlencode($this->mailbox);
+
+ if (!empty($this->uidvalidity)) {
+ $url .= ';UIDVALIDITY=' . $this->uidvalidity;
+ }
+
+ if (!is_null($this->search)) {
+ $url .= '?' . urlencode($this->search);
+ } else {
+ if (!is_null($this->uid)) {
+ $url .= '/;UID=' . $this->uid;
+ }
+
+ if (!is_null($this->section)) {
+ $url .= '/;SECTION=' . $this->section;
+ }
+
+ if (!is_null($this->partial)) {
+ $url .= '/;PARTIAL=' . $this->partial;
+ }
+
+ if (!is_null($this->urlauth)) {
+ $url .= '/;URLAUTH=' . $this->urlauth;
+ }
+ }
+ }
+
+ return $url;
+ }
+
+ /**
+ */
+ public function __get($name)
+ {
+ switch ($name) {
+ case 'relative':
+ return (is_null($this->hostspec) &&
+ is_null($this->port) &&
+ is_null($this->protocol));
+ }
+ }
+
+ /**
+ */
+ protected function _parse($url)
+ {
+ $data = parse_url(trim($url));
+
+ if (isset($data['scheme'])) {
+ $protocol = strtolower($data['scheme']);
+ if (!in_array($protocol, array('imap', 'pop'))) {
+ return;
+ }
+
+ if (isset($data['host'])) {
+ $this->hostspec = $data['host'];
+ }
+ $this->port = isset($data['port'])
+ ? $data['port']
+ : 143;
+ $this->protocol = $protocol;
+ }
+
+ /* Check for username/auth information. */
+ if (isset($data['user'])) {
+ if (($pos = stripos($data['user'], ';AUTH=')) !== false) {
+ $auth = substr($data['user'], $pos + 6);
+ if ($auth != '*') {
+ $this->auth = $auth;
+ }
+ $data['user'] = substr($data['user'], 0, $pos);
+ }
+
+ if (strlen($data['user'])) {
+ $this->username = $data['user'];
+ }
+ }
+
+ /* IMAP-only information. */
+ if (is_null($this->protocol) || ($this->protocol == 'imap')) {
+ if (isset($data['path'])) {
+ $data['path'] = ltrim($data['path'], '/');
+ $parts = explode('/;', $data['path']);
+
+ $mbox = array_shift($parts);
+ if (($pos = stripos($mbox, ';UIDVALIDITY=')) !== false) {
+ $this->uidvalidity = intval(substr($mbox, $pos + 13));
+ $mbox = substr($mbox, 0, $pos);
+ }
+ $this->mailbox = urldecode($mbox);
+
+ }
+
+ if (count($parts)) {
+ foreach ($parts as $val) {
+ list($k, $v) = explode('=', $val);
+ $property = strtolower($k);
+ $this->$property = $v;
+ }
+ } elseif (isset($data['query'])) {
+ $this->search = urldecode($data['query']);
+ }
+ }
+ }
+
+ /* Serializable methods. */
+
+ /**
+ */
+ public function serialize()
+ {
+ return strval($this);
+ }
+
+ /**
+ */
+ public function unserialize($data)
+ {
+ $this->_parse($data);
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientUtf7imapphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client/Utf7imap.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client/Utf7imap.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client/Utf7imap.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,302 @@
</span><ins>+<?php
+/**
+ * Originally based on code:
+ *
+ * Copyright (C) 2000 Edmund Grimley Evans <edmundo@rano.org>
+ * Released under the GPL (version 2)
+ *
+ * Translated from C to PHP by Thomas Bruederli <roundcube@gmail.com>
+ * Code extracted from the RoundCube Webmail (http://roundcube.net) project,
+ * SVN revision 1757
+ * The RoundCube project is released under the GPL (version 2)
+ *
+ * Copyright 2008-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2000 Edmund Grimley Evans <edmundo@rano.org>
+ * @copyright 2008-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * Allows conversions between UTF-8 and UTF7-IMAP (RFC 3501 [5.1.3]).
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2000 Edmund Grimley Evans <edmundo@rano.org>
+ * @copyright 2008-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+class Horde_Imap_Client_Utf7imap
+{
+ /**
+ * Lookup table for conversion.
+ *
+ * @var array
+ */
+ private static $_index64 = array(
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, 63, -1, -1, -1,
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
+ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
+ -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1
+ );
+
+ /**
+ * Lookup table for conversion.
+ *
+ * @var array
+ */
+ private static $_base64 = array(
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
+ 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b',
+ 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
+ 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3',
+ '4', '5', '6', '7', '8', '9', '+', ','
+ );
+
+ /**
+ * Is mbstring extension available?
+ *
+ * @var array
+ */
+ private static $_mbstring = null;
+
+ /**
+ * Convert a string from UTF7-IMAP to UTF-8.
+ *
+ * @param string $str The UTF7-IMAP string.
+ *
+ * @return string The converted UTF-8 string.
+ * @throws Horde_Imap_Client_Exception
+ */
+ public static function Utf7ImapToUtf8($str)
+ {
+ if ($str instanceof Horde_Imap_Client_Mailbox) {
+ return $str->utf8;
+ }
+
+ $str = strval($str);
+
+ /* Try mbstring, if available, which should be faster. Don't use the
+ * IMAP utf7_* functions because they are known to be buggy. */
+ if (is_null(self::$_mbstring)) {
+ self::$_mbstring = extension_loaded('mbstring');
+ }
+ if (self::$_mbstring) {
+ return @mb_convert_encoding($str, 'UTF-8', 'UTF7-IMAP');
+ }
+
+ $p = '';
+ $ptr = &self::$_index64;
+
+ for ($i = 0, $u7len = strlen($str); $u7len > 0; ++$i, --$u7len) {
+ $u7 = $str[$i];
+ if ($u7 == '&') {
+ $u7 = $str[++$i];
+ if (--$u7len && ($u7 == '-')) {
+ $p .= '&';
+ continue;
+ }
+
+ $ch = 0;
+ $k = 10;
+ for (; $u7len > 0; ++$i, --$u7len) {
+ $u7 = $str[$i];
+
+ if ((ord($u7) & 0x80) || ($b = $ptr[ord($u7)]) == -1) {
+ break;
+ }
+
+ if ($k > 0) {
+ $ch |= $b << $k;
+ $k -= 6;
+ } else {
+ $ch |= $b >> (-$k);
+ if ($ch < 0x80) {
+ /* Printable US-ASCII */
+ if ((0x20 <= $ch) && ($ch < 0x7f)) {
+ throw new Horde_Imap_Client_Exception(Horde_Imap_Client_Translation::t("Error converting UTF7-IMAP string."), Horde_Imap_Client_Exception::UTF7IMAP_CONVERSION);
+ }
+ $p .= chr($ch);
+ } else if ($ch < 0x800) {
+ $p .= chr(0xc0 | ($ch >> 6)) .
+ chr(0x80 | ($ch & 0x3f));
+ } else {
+ $p .= chr(0xe0 | ($ch >> 12)) .
+ chr(0x80 | (($ch >> 6) & 0x3f)) .
+ chr(0x80 | ($ch & 0x3f));
+ }
+
+ $ch = ($b << (16 + $k)) & 0xffff;
+ $k += 10;
+ }
+ }
+
+ /* Non-zero or too many extra bits -OR-
+ * Base64 not properly terminated -OR-
+ * Adjacent Base64 sections. */
+ if (($ch || ($k < 6)) ||
+ (!$u7len || $u7 != '-') ||
+ (($u7len > 2) &&
+ ($str[$i + 1] == '&') &&
+ ($str[$i + 2] != '-'))) {
+ throw new Horde_Imap_Client_Exception(Horde_Imap_Client_Translation::t("Error converting UTF7-IMAP string."), Horde_Imap_Client_Exception::UTF7IMAP_CONVERSION);
+ }
+ } elseif ((ord($u7) < 0x20) || (ord($u7) >= 0x7f)) {
+ /* Not printable US-ASCII */
+ throw new Horde_Imap_Client_Exception(Horde_Imap_Client_Translation::t("Error converting UTF7-IMAP string."), Horde_Imap_Client_Exception::UTF7IMAP_CONVERSION);
+ } else {
+ $p .= $u7;
+ }
+ }
+
+ return $p;
+ }
+
+ /**
+ * Convert a string from UTF-8 to UTF7-IMAP.
+ *
+ * @param string $str The UTF-8 string.
+ * @param boolean $force Assume $str is UTF-8 (no-autodetection)? If
+ * false, attempts to auto-detect if string is
+ * already in UTF7-IMAP.
+ *
+ * @return string The converted UTF7-IMAP string.
+ * @throws Horde_Imap_Client_Exception
+ */
+ public static function Utf8ToUtf7Imap($str, $force = true)
+ {
+ if ($str instanceof Horde_Imap_Client_Mailbox) {
+ return $str->utf7imap;
+ }
+
+ $str = strval($str);
+
+ /* No need to do conversion if all chars are in US-ASCII range or if
+ * no ampersand is present. But will assume that an already encoded
+ * ampersand means string is in UTF7-IMAP already. */
+ if (!$force &&
+ !preg_match('/[\x80-\xff]|&$|&(?![,+A-Za-z0-9]*-)/', $str)) {
+ return $str;
+ }
+
+ /* Try mbstring, if available, which should be faster. Don't use the
+ * IMAP utf7_* functions because they are known to be buggy. */
+ if (is_null(self::$_mbstring)) {
+ self::$_mbstring = extension_loaded('mbstring');
+ }
+ if (self::$_mbstring) {
+ return @mb_convert_encoding($str, 'UTF7-IMAP', 'UTF-8');
+ }
+
+ $u8len = strlen($str);
+ $i = 0;
+ $base64 = false;
+ $p = '';
+ $ptr = &self::$_base64;
+
+ while ($u8len) {
+ $u8 = $str[$i];
+ $c = ord($u8);
+
+ if ($c < 0x80) {
+ $ch = $c;
+ $n = 0;
+ } elseif ($c < 0xc2) {
+ throw new Horde_Imap_Client_Exception(Horde_Imap_Client_Translation::t("Error converting UTF7-IMAP string."), Horde_Imap_Client_Exception::UTF7IMAP_CONVERSION);
+ } elseif ($c < 0xe0) {
+ $ch = $c & 0x1f;
+ $n = 1;
+ } elseif ($c < 0xf0) {
+ $ch = $c & 0x0f;
+ $n = 2;
+ } elseif ($c < 0xf8) {
+ $ch = $c & 0x07;
+ $n = 3;
+ } elseif ($c < 0xfc) {
+ $ch = $c & 0x03;
+ $n = 4;
+ } elseif ($c < 0xfe) {
+ $ch = $c & 0x01;
+ $n = 5;
+ } else {
+ throw new Horde_Imap_Client_Exception(Horde_Imap_Client_Translation::t("Error converting UTF7-IMAP string."), Horde_Imap_Client_Exception::UTF7IMAP_CONVERSION);
+ }
+
+ if ($n > --$u8len) {
+ throw new Horde_Imap_Client_Exception(Horde_Imap_Client_Translation::t("Error converting UTF7-IMAP string."), Horde_Imap_Client_Exception::UTF7IMAP_CONVERSION);
+ }
+
+ ++$i;
+
+ for ($j = 0; $j < $n; ++$j) {
+ $o = ord($str[$i + $j]);
+ if (($o & 0xc0) != 0x80) {
+ throw new Horde_Imap_Client_Exception(Horde_Imap_Client_Translation::t("Error converting UTF7-IMAP string."), Horde_Imap_Client_Exception::UTF7IMAP_CONVERSION);
+ }
+ $ch = ($ch << 6) | ($o & 0x3f);
+ }
+
+ if (($n > 1) && !($ch >> ($n * 5 + 1))) {
+ throw new Horde_Imap_Client_Exception(Horde_Imap_Client_Translation::t("Error converting UTF7-IMAP string."), Horde_Imap_Client_Exception::UTF7IMAP_CONVERSION);
+ }
+
+ $i += $n;
+ $u8len -= $n;
+
+ if (($ch < 0x20) || ($ch >= 0x7f)) {
+ if (!$base64) {
+ $p .= '&';
+ $base64 = true;
+ $b = 0;
+ $k = 10;
+ }
+
+ if ($ch & ~0xffff) {
+ $ch = 0xfffe;
+ }
+
+ $p .= $ptr[($b | $ch >> $k)];
+ $k -= 6;
+ for (; $k >= 0; $k -= 6) {
+ $p .= $ptr[(($ch >> $k) & 0x3f)];
+ }
+
+ $b = ($ch << (-$k)) & 0x3f;
+ $k += 16;
+ } else {
+ if ($base64) {
+ if ($k > 10) {
+ $p .= $ptr[$b];
+ }
+ $p .= '-';
+ $base64 = false;
+ }
+
+ $p .= chr($ch);
+ if (chr($ch) == '&') {
+ $p .= '-';
+ }
+ }
+ }
+
+ if ($base64) {
+ if ($k > 10) {
+ $p .= $ptr[$b];
+ }
+ $p .= '-';
+ }
+
+ return $p;
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeClientphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Client.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Client.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Client.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,197 @@
</span><ins>+<?php
+/**
+ * Copyright 2008-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2008-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+
+/**
+ * Base class for Horde_Imap_Client package. Defines common constants for use
+ * in the package.
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2008-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Imap_Client
+ */
+class Horde_Imap_Client
+{
+ /* Constants for openMailbox() */
+ const OPEN_READONLY = 1;
+ const OPEN_READWRITE = 2;
+ const OPEN_AUTO = 3;
+
+ /* Constants for listMailboxes() */
+ const MBOX_SUBSCRIBED = 1;
+ const MBOX_SUBSCRIBED_EXISTS = 2;
+ const MBOX_UNSUBSCRIBED = 3;
+ const MBOX_ALL = 4;
+
+ /* Constants for status() */
+ const STATUS_MESSAGES = 1;
+ const STATUS_RECENT = 2;
+ const STATUS_UIDNEXT = 4;
+ const STATUS_UIDVALIDITY = 8;
+ const STATUS_UNSEEN = 16;
+ const STATUS_ALL = 32;
+ const STATUS_FIRSTUNSEEN = 64;
+ const STATUS_FLAGS = 128;
+ const STATUS_PERMFLAGS = 256;
+ const STATUS_HIGHESTMODSEQ = 512;
+ const STATUS_SYNCMODSEQ = 1024;
+ const STATUS_SYNCFLAGUIDS = 2048;
+ const STATUS_UIDNOTSTICKY = 4096;
+ const STATUS_UIDNEXT_FORCE = 8192;
+ const STATUS_SYNCVANISHED = 16384;
+ /* @since 2.12.0 */
+ const STATUS_RECENT_TOTAL = 32768;
+
+ /* Constants for search() */
+ const SORT_ARRIVAL = 1;
+ const SORT_CC = 2;
+ const SORT_DATE = 3;
+ const SORT_FROM = 4;
+ const SORT_REVERSE = 5;
+ const SORT_SIZE = 6;
+ const SORT_SUBJECT = 7;
+ const SORT_TO = 8;
+ /* SORT_THREAD provided for completeness - it is not a valid sort criteria
+ * for search() (use thread() instead). */
+ const SORT_THREAD = 9;
+ /* Sort criteria defined in RFC 5957 */
+ const SORT_DISPLAYFROM = 10;
+ const SORT_DISPLAYTO = 11;
+ /* SORT_SEQUENCE does a simple numerical sort on the returned
+ * UIDs/sequence numbers. */
+ const SORT_SEQUENCE = 12;
+ /* Fuzzy sort criteria defined in RFC 6203 */
+ const SORT_RELEVANCY = 13;
+ /* @since 2.4.0 */
+ const SORT_DISPLAYFROM_FALLBACK = 14;
+ /* @since 2.4.0 */
+ const SORT_DISPLAYTO_FALLBACK = 15;
+
+ /* Search results constants */
+ const SEARCH_RESULTS_COUNT = 1;
+ const SEARCH_RESULTS_MATCH = 2;
+ const SEARCH_RESULTS_MAX = 3;
+ const SEARCH_RESULTS_MIN = 4;
+ const SEARCH_RESULTS_SAVE = 5;
+ /* Fuzzy sort criteria defined in RFC 6203 */
+ const SEARCH_RESULTS_RELEVANCY = 6;
+
+ /* Constants for thread() */
+ const THREAD_ORDEREDSUBJECT = 1;
+ const THREAD_REFERENCES = 2;
+ const THREAD_REFS = 3;
+
+ /* Fetch criteria constants. */
+ const FETCH_STRUCTURE = 1;
+ const FETCH_FULLMSG = 2;
+ const FETCH_HEADERTEXT = 3;
+ const FETCH_BODYTEXT = 4;
+ const FETCH_MIMEHEADER = 5;
+ const FETCH_BODYPART = 6;
+ const FETCH_BODYPARTSIZE = 7;
+ const FETCH_HEADERS = 8;
+ const FETCH_ENVELOPE = 9;
+ const FETCH_FLAGS = 10;
+ const FETCH_IMAPDATE = 11;
+ const FETCH_SIZE = 12;
+ const FETCH_UID = 13;
+ const FETCH_SEQ = 14;
+ const FETCH_MODSEQ = 15;
+ /* @since 2.11.0 */
+ const FETCH_DOWNGRADED = 16;
+
+ /* Namespace constants. */
+ const NS_PERSONAL = 1;
+ const NS_OTHER = 2;
+ const NS_SHARED = 3;
+
+ /* ACL constants (RFC 4314 [2.1]). */
+ const ACL_LOOKUP = 'l';
+ const ACL_READ = 'r';
+ const ACL_SEEN = 's';
+ const ACL_WRITE = 'w';
+ const ACL_INSERT = 'i';
+ const ACL_POST = 'p';
+ const ACL_CREATEMBOX = 'k';
+ const ACL_DELETEMBOX = 'x';
+ const ACL_DELETEMSGS = 't';
+ const ACL_EXPUNGE = 'e';
+ const ACL_ADMINISTER = 'a';
+ // Old constants (RFC 2086 [3]; RFC 4314 [2.1.1])
+ const ACL_CREATE = 'c';
+ const ACL_DELETE = 'd';
+
+ /* System flags. */
+ // RFC 3501 [2.3.2]
+ const FLAG_ANSWERED = '\\answered';
+ const FLAG_DELETED = '\\deleted';
+ const FLAG_DRAFT = '\\draft';
+ const FLAG_FLAGGED = '\\flagged';
+ const FLAG_RECENT = '\\recent';
+ const FLAG_SEEN = '\\seen';
+ // RFC 3503 [3.3]
+ const FLAG_MDNSENT = '$mdnsent';
+ // RFC 5550 [2.8]
+ const FLAG_FORWARDED = '$forwarded';
+ // RFC 5788 registered keywords:
+ // http://www.ietf.org/mail-archive/web/morg/current/msg00441.html
+ const FLAG_JUNK = '$junk';
+ const FLAG_NOTJUNK = '$notjunk';
+
+ /* Special-use mailbox attributes (RFC 6154 [2]). */
+ const SPECIALUSE_ALL = '\\All';
+ const SPECIALUSE_ARCHIVE = '\\Archive';
+ const SPECIALUSE_DRAFTS = '\\Drafts';
+ const SPECIALUSE_FLAGGED = '\\Flagged';
+ const SPECIALUSE_JUNK = '\\Junk';
+ const SPECIALUSE_SENT = '\\Sent';
+ const SPECIALUSE_TRASH = '\\Trash';
+
+ /* Constants for sync(). */
+ const SYNC_UIDVALIDITY = 0;
+ const SYNC_FLAGS = 1;
+ const SYNC_FLAGSUIDS = 2;
+ const SYNC_NEWMSGS = 4;
+ const SYNC_NEWMSGSUIDS = 8;
+ const SYNC_VANISHED = 16;
+ const SYNC_VANISHEDUIDS = 32;
+ const SYNC_ALL = 64;
+
+ /**
+ * Capability dependencies.
+ *
+ * @var array
+ */
+ static public $capability_deps = array(
+ // RFC 5162 [1]
+ 'QRESYNC' => array(
+ // QRESYNC requires CONDSTORE, but the latter is implied and is
+ // not required to be listed.
+ 'ENABLE'
+ ),
+ // RFC 5182 [2.1]
+ 'SEARCHRES' => array(
+ 'ESEARCH'
+ ),
+ // RFC 5255 [3.1]
+ 'LANGUAGE' => array(
+ 'NAMESPACE'
+ ),
+ // RFC 5957 [1]
+ 'SORT=DISPLAY' => array(
+ 'SORT'
+ )
+ );
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeExceptionWrappedphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Exception-Wrapped.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Exception-Wrapped.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Exception-Wrapped.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,46 @@
</span><ins>+<?php
+/**
+ * Horde exception class that can wrap and set its details from PEAR_Error,
+ * Exception, and other objects with similar interfaces.
+ *
+ * Copyright 2008-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @package Exception
+ */
+class Horde_Exception_Wrapped extends Horde_Exception
+{
+ /**
+ * Exception constructor.
+ *
+ * @param mixed $message The exception message, a PEAR_Error
+ * object, or an Exception object.
+ * @param int $code A numeric error code.
+ */
+ public function __construct($message = null, $code = 0)
+ {
+ $previous = null;
+ if (is_object($message) &&
+ method_exists($message, 'getMessage')) {
+ if (empty($code) &&
+ method_exists($message, 'getCode')) {
+ $code = (int)$message->getCode();
+ }
+ if ($message instanceof Exception) {
+ $previous = $message;
+ }
+ if (method_exists($message, 'getUserinfo') &&
+ $details = $message->getUserinfo()) {
+ $this->details = $details;
+ } elseif (!empty($message->details)) {
+ $this->details = $message->details;
+ }
+ $message = (string)$message->getMessage();
+ }
+
+ parent::__construct($message, $code, $previous);
+ }
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeExceptionphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Exception.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Exception.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Exception.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,64 @@
</span><ins>+<?php
+/**
+ * Horde base exception class.
+ *
+ * Copyright 2008-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @license http://www.horde.org/licenses/lgpl21 LGPL-2.1
+ * @package Exception
+ */
+class Horde_Exception extends Exception
+{
+ /**
+ * Error details that should not be part of the main exception message,
+ * e.g. any additional debugging information.
+ *
+ * @var string
+ */
+ public $details;
+
+ /**
+ * Has this exception been logged?
+ *
+ * @var boolean
+ */
+ public $logged = false;
+
+ /**
+ * The log level to use. A Horde_Log constant.
+ *
+ * @var integer
+ */
+ protected $_logLevel = 0;
+
+ /**
+ * Get the log level.
+ *
+ * @return integer The Horde_Log constant for the log level.
+ */
+ public function getLogLevel()
+ {
+ return $this->_logLevel;
+ }
+
+ /**
+ * Sets the log level.
+ *
+ * @param mixed $level The log level.
+ */
+ public function setLogLevel($level = 0)
+ {
+ if (is_string($level)) {
+ $level = defined('Horde_Log::' . $level)
+ ? constant('Horde_Log::' . $level)
+ : 0;
+ }
+
+ $this->_logLevel = $level;
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeHordeUtilphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Horde-Util.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Horde-Util.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Horde-Util.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,564 @@
</span><ins>+<?php
+/**
+ * The Horde_Util:: class provides generally useful methods.
+ *
+ * Copyright 1999-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @author Chuck Hagenbuch <chuck@horde.org>
+ * @author Jon Parise <jon@horde.org>
+ * @category Horde
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Util
+ */
+class Horde_Util
+{
+ /**
+ * A list of random patterns to use for overwriting purposes.
+ * See http://www.cs.auckland.ac.nz/~pgut001/pubs/secure_del.html.
+ * We save the random overwrites for efficiency reasons.
+ *
+ * @var array
+ */
+ static public $patterns = array(
+ "\x55", "\xaa", "\x92\x49\x24", "\x49\x24\x92", "\x24\x92\x49",
+ "\x00", "\x11", "\x22", "\x33", "\x44", "\x55", "\x66", "\x77",
+ "\x88", "\x99", "\xaa", "\xbb", "\xcc", "\xdd", "\xee", "\xff",
+ "\x92\x49\x24", "\x49\x24\x92", "\x24\x92\x49", "\x6d\xb6\xdb",
+ "\xb6\xdb\x6d", "\xdb\x6d\xb6"
+ );
+
+ /**
+ * Are magic quotes in use?
+ *
+ * @var boolean
+ */
+ static protected $_magicquotes = null;
+
+ /**
+ * TODO
+ *
+ * @var array
+ */
+ static protected $_shutdowndata = array(
+ 'dirs' => array(),
+ 'files' => array(),
+ 'secure' => array()
+ );
+
+ /**
+ * Has the shutdown method been registered?
+ *
+ * @var boolean
+ */
+ static protected $_shutdownreg = false;
+
+ /**
+ * Cache for extensionExists().
+ *
+ * @var array
+ */
+ static protected $_cache = array();
+
+ /**
+ * Checks to see if a value has been set by the script and not by GET,
+ * POST, or cookie input. The value being checked MUST be in the global
+ * scope.
+ *
+ * @param string $varname The variable name to check.
+ * @param mixed $default Default value if the variable isn't present
+ * or was specified by the user. Defaults to null.
+ *
+ * @return mixed $default if the var is in user input or not present,
+ * the variable value otherwise.
+ */
+ static public function nonInputVar($varname, $default = null)
+ {
+ return (isset($_GET[$varname]) || isset($_POST[$varname]) || isset($_COOKIE[$varname]))
+ ? $default
+ : (isset($GLOBALS[$varname]) ? $GLOBALS[$varname] : $default);
+ }
+
+ /**
+ * Returns a hidden form input containing the session name and id.
+ *
+ * @param boolean $append_session 0 = only if needed, 1 = always.
+ *
+ * @return string The hidden form input, if needed/requested.
+ */
+ static public function formInput($append_session = 0)
+ {
+ return (($append_session == 1) || !isset($_COOKIE[session_name()]))
+ ? '<input type="hidden" name="' . htmlspecialchars(session_name()) . '" value="' . htmlspecialchars(session_id()) . "\" />\n"
+ : '';
+ }
+
+ /**
+ * Prints a hidden form input containing the session name and id.
+ *
+ * @param boolean $append_session 0 = only if needed, 1 = always.
+ */
+ static public function pformInput($append_session = 0)
+ {
+ echo self::formInput($append_session);
+ }
+
+ /**
+ * If magic_quotes_gpc is in use, run stripslashes() on $var.
+ *
+ * @param mixed $var The string, or an array of strings, to un-quote.
+ *
+ * @return mixed $var, minus any magic quotes.
+ */
+ static public function dispelMagicQuotes($var)
+ {
+ if (is_null(self::$_magicquotes)) {
+ self::$_magicquotes = get_magic_quotes_gpc();
+ }
+
+ if (self::$_magicquotes) {
+ $var = is_array($var)
+ ? array_map(array(__CLASS__, 'dispelMagicQuotes'), $var)
+ : stripslashes($var);
+ }
+
+ return $var;
+ }
+
+ /**
+ * Gets a form variable from GET or POST data, stripped of magic quotes if
+ * necessary. If the variable is somehow set in both the GET data and the
+ * POST data, the value from the POST data will be returned and the GET
+ * value will be ignored.
+ *
+ * @param string $var The name of the form variable to look for.
+ * @param string $default The value to return if the variable is not
+ * there.
+ *
+ * @return string The cleaned form variable, or $default.
+ */
+ static public function getFormData($var, $default = null)
+ {
+ return (($val = self::getPost($var)) !== null)
+ ? $val
+ : self::getGet($var, $default);
+ }
+
+ /**
+ * Gets a form variable from GET data, stripped of magic quotes if
+ * necessary. This function will NOT return a POST variable.
+ *
+ * @param string $var The name of the form variable to look for.
+ * @param string $default The value to return if the variable is not
+ * there.
+ *
+ * @return string The cleaned form variable, or $default.
+ */
+ static public function getGet($var, $default = null)
+ {
+ return (isset($_GET[$var]))
+ ? self::dispelMagicQuotes($_GET[$var])
+ : $default;
+ }
+
+ /**
+ * Gets a form variable from POST data, stripped of magic quotes if
+ * necessary. This function will NOT return a GET variable.
+ *
+ * @param string $var The name of the form variable to look for.
+ * @param string $default The value to return if the variable is not
+ * there.
+ *
+ * @return string The cleaned form variable, or $default.
+ */
+ static public function getPost($var, $default = null)
+ {
+ return (isset($_POST[$var]))
+ ? self::dispelMagicQuotes($_POST[$var])
+ : $default;
+ }
+
+ /**
+ * Creates a temporary filename for the lifetime of the script, and
+ * (optionally) registers it to be deleted at request shutdown.
+ *
+ * @param string $prefix Prefix to make the temporary name more
+ * recognizable.
+ * @param boolean $delete Delete the file at the end of the request?
+ * @param string $dir Directory to create the temporary file in.
+ * @param boolean $secure If deleting the file, should we securely delete
+ * the file by overwriting it with random data?
+ *
+ * @return string Returns the full path-name to the temporary file.
+ * Returns false if a temp file could not be created.
+ */
+ static public function getTempFile($prefix = '', $delete = true, $dir = '',
+ $secure = false)
+ {
+ $tempDir = (empty($dir) || !is_dir($dir))
+ ? sys_get_temp_dir()
+ : $dir;
+
+ $tempFile = tempnam($tempDir, $prefix);
+
+ // If the file was created, then register it for deletion and return.
+ if (empty($tempFile)) {
+ return false;
+ }
+
+ if ($delete) {
+ self::deleteAtShutdown($tempFile, true, $secure);
+ }
+
+ return $tempFile;
+ }
+
+ /**
+ * Creates a temporary filename with a specific extension for the lifetime
+ * of the script, and (optionally) registers it to be deleted at request
+ * shutdown.
+ *
+ * @param string $extension The file extension to use.
+ * @param string $prefix Prefix to make the temporary name more
+ * recognizable.
+ * @param boolean $delete Delete the file at the end of the request?
+ * @param string $dir Directory to create the temporary file in.
+ * @param boolean $secure If deleting file, should we securely delete
+ * the file by overwriting it with random data?
+ *
+ * @return string Returns the full path-name to the temporary file.
+ * Returns false if a temporary file could not be created.
+ */
+ static public function getTempFileWithExtension($extension = '.tmp',
+ $prefix = '',
+ $delete = true, $dir = '',
+ $secure = false)
+ {
+ $tempDir = (empty($dir) || !is_dir($dir))
+ ? sys_get_temp_dir()
+ : $dir;
+
+ if (empty($tempDir)) {
+ return false;
+ }
+
+ $windows = substr(PHP_OS, 0, 3) == 'WIN';
+ $tries = 1;
+ do {
+ // Get a known, unique temporary file name.
+ $sysFileName = tempnam($tempDir, $prefix);
+ if ($sysFileName === false) {
+ return false;
+ }
+
+ // tack on the extension
+ $tmpFileName = $sysFileName . $extension;
+ if ($sysFileName == $tmpFileName) {
+ return $sysFileName;
+ }
+
+ // Move or point the created temporary file to the full filename
+ // with extension. These calls fail if the new name already
+ // exists.
+ $fileCreated = ($windows ? @rename($sysFileName, $tmpFileName) : @link($sysFileName, $tmpFileName));
+ if ($fileCreated) {
+ if (!$windows) {
+ unlink($sysFileName);
+ }
+
+ if ($delete) {
+ self::deleteAtShutdown($tmpFileName, true, $secure);
+ }
+
+ return $tmpFileName;
+ }
+
+ unlink($sysFileName);
+ } while (++$tries <= 5);
+
+ return false;
+ }
+
+ /**
+ * Creates a temporary directory in the system's temporary directory.
+ *
+ * @param boolean $delete Delete the temporary directory at the end of
+ * the request?
+ * @param string $temp_dir Use this temporary directory as the directory
+ * where the temporary directory will be created.
+ *
+ * @return string The pathname to the new temporary directory.
+ * Returns false if directory not created.
+ */
+ static public function createTempDir($delete = true, $temp_dir = null)
+ {
+ if (is_null($temp_dir)) {
+ $temp_dir = sys_get_temp_dir();
+ }
+
+ if (empty($temp_dir)) {
+ return false;
+ }
+
+ /* Get the first 8 characters of a random string to use as a temporary
+ directory name. */
+ do {
+ $new_dir = $temp_dir . '/' . substr(base_convert(uniqid(mt_rand()), 10, 36), 0, 8);
+ } while (file_exists($new_dir));
+
+ $old_umask = umask(0000);
+ if (!mkdir($new_dir, 0700)) {
+ $new_dir = false;
+ } elseif ($delete) {
+ self::deleteAtShutdown($new_dir);
+ }
+ umask($old_umask);
+
+ return $new_dir;
+ }
+
+ /**
+ * Returns the canonical path of the string. Like PHP's built-in
+ * realpath() except the directory need not exist on the local server.
+ *
+ * Algorithim loosely based on code from the Perl File::Spec::Unix module
+ * (version 1.5).
+ *
+ * @param string $path A file path.
+ *
+ * @return string The canonicalized file path.
+ */
+ static public function realPath($path)
+ {
+ /* Standardize on UNIX directory separators. */
+ if (!strncasecmp(PHP_OS, 'WIN', 3)) {
+ $path = str_replace('\\', '/', $path);
+ }
+
+ /* xx////xx -> xx/xx
+ * xx/././xx -> xx/xx */
+ $path = preg_replace(array("|/+|", "@(/\.)+(/|\Z(?!\n))@"), array('/', '/'), $path);
+
+ /* ./xx -> xx */
+ if ($path != './') {
+ $path = preg_replace("|^(\./)+|", '', $path);
+ }
+
+ /* /../../xx -> xx */
+ $path = preg_replace("|^/(\.\./?)+|", '/', $path);
+
+ /* xx/ -> xx */
+ if ($path != '/') {
+ $path = preg_replace("|/\Z(?!\n)|", '', $path);
+ }
+
+ /* /xx/.. -> / */
+ while (strpos($path, '/..') !== false) {
+ $path = preg_replace("|/[^/]+/\.\.|", '', $path);
+ }
+
+ return empty($path) ? '/' : $path;
+ }
+
+ /**
+ * Removes given elements at request shutdown.
+ *
+ * If called with a filename will delete that file at request shutdown; if
+ * called with a directory will remove that directory and all files in that
+ * directory at request shutdown.
+ *
+ * If called with no arguments, return all elements to be deleted (this
+ * should only be done by Horde_Util::_deleteAtShutdown()).
+ *
+ * The first time it is called, it initializes the array and registers
+ * Horde_Util::_deleteAtShutdown() as a shutdown function - no need to do
+ * so manually.
+ *
+ * The second parameter allows the unregistering of previously registered
+ * elements.
+ *
+ * @param string $filename The filename to be deleted at the end of the
+ * request.
+ * @param boolean $register If true, then register the element for
+ * deletion, otherwise, unregister it.
+ * @param boolean $secure If deleting file, should we securely delete
+ * the file?
+ */
+ static public function deleteAtShutdown($filename, $register = true,
+ $secure = false)
+ {
+ /* Initialization of variables and shutdown functions. */
+ if (!self::$_shutdownreg) {
+ register_shutdown_function(array(__CLASS__, 'shutdown'));
+ self::$_shutdownreg = true;
+ }
+
+ $ptr = &self::$_shutdowndata;
+ if ($register) {
+ if (@is_dir($filename)) {
+ $ptr['dirs'][$filename] = true;
+ } else {
+ $ptr['files'][$filename] = true;
+ }
+
+ if ($secure) {
+ $ptr['secure'][$filename] = true;
+ }
+ } else {
+ unset($ptr['dirs'][$filename], $ptr['files'][$filename], $ptr['secure'][$filename]);
+ }
+ }
+
+ /**
+ * Deletes registered files at request shutdown.
+ *
+ * This function should never be called manually; it is registered as a
+ * shutdown function by Horde_Util::deleteAtShutdown() and called
+ * automatically at the end of the request.
+ *
+ * Contains code from gpg_functions.php.
+ * Copyright 2002-2003 Braverock Ventures
+ */
+ static public function shutdown()
+ {
+ $ptr = &self::$_shutdowndata;
+
+ foreach ($ptr['files'] as $file => $val) {
+ /* Delete files */
+ if ($val && file_exists($file)) {
+ /* Should we securely delete the file by overwriting the data
+ with a random string? */
+ if (isset($ptr['secure'][$file])) {
+ $filesize = filesize($file);
+ $fp = fopen($file, 'r+');
+ foreach (self::$patterns as $pattern) {
+ $pattern = substr(str_repeat($pattern, floor($filesize / strlen($pattern)) + 1), 0, $filesize);
+ fwrite($fp, $pattern);
+ fseek($fp, 0);
+ }
+ fclose($fp);
+ }
+ @unlink($file);
+ }
+ }
+
+ foreach ($ptr['dirs'] as $dir => $val) {
+ /* Delete directories */
+ if ($val && file_exists($dir)) {
+ /* Make sure directory is empty. */
+ $dir_class = dir($dir);
+ while (false !== ($entry = $dir_class->read())) {
+ if ($entry != '.' && $entry != '..') {
+ @unlink($dir . '/' . $entry);
+ }
+ }
+ $dir_class->close();
+ @rmdir($dir);
+ }
+ }
+ }
+
+ /**
+ * Caches the result of extension_loaded() calls.
+ *
+ * @param string $ext The extension name.
+ *
+ * @return boolean Is the extension loaded?
+ */
+ static public function extensionExists($ext)
+ {
+ if (!isset(self::$_cache[$ext])) {
+ self::$_cache[$ext] = extension_loaded($ext);
+ }
+
+ return self::$_cache[$ext];
+ }
+
+ /**
+ * Tries to load a PHP extension, behaving correctly for all operating
+ * systems.
+ *
+ * @param string $ext The extension to load.
+ *
+ * @return boolean True if the extension is now loaded, false if not.
+ * True can mean that the extension was already loaded,
+ * OR was loaded dynamically.
+ */
+ static public function loadExtension($ext)
+ {
+ /* If $ext is already loaded, our work is done. */
+ if (self::extensionExists($ext)) {
+ return true;
+ }
+
+ /* See if we can call dl() at all, by the current ini settings.
+ * dl() has been removed in some PHP 5.3 SAPIs. */
+ if ((ini_get('enable_dl') != 1) ||
+ (ini_get('safe_mode') == 1) ||
+ !function_exists('dl')) {
+ return false;
+ }
+
+ if (!strncasecmp(PHP_OS, 'WIN', 3)) {
+ $suffix = 'dll';
+ } else {
+ switch (PHP_OS) {
+ case 'HP-UX':
+ $suffix = 'sl';
+ break;
+
+ case 'AIX':
+ $suffix = 'a';
+ break;
+
+ case 'OSX':
+ $suffix = 'bundle';
+ break;
+
+ default:
+ $suffix = 'so';
+ }
+ }
+
+ return dl($ext . '.' . $suffix) || dl('php_' . $ext . '.' . $suffix);
+ }
+
+ /**
+ * Utility function to obtain PATH_INFO information.
+ *
+ * @return string The PATH_INFO string.
+ */
+ static public function getPathInfo()
+ {
+ if (isset($_SERVER['PATH_INFO']) &&
+ (strpos($_SERVER['SERVER_SOFTWARE'], 'lighttpd') === false)) {
+ return $_SERVER['PATH_INFO'];
+ } elseif (isset($_SERVER['REQUEST_URI']) &&
+ isset($_SERVER['SCRIPT_NAME'])) {
+ $search = Horde_String::common($_SERVER['SCRIPT_NAME'], $_SERVER['REQUEST_URI']);
+ if (substr($search, -1) == '/') {
+ $search = substr($search, 0, -1);
+ }
+ $search = array($search);
+ if (!empty($_SERVER['QUERY_STRING'])) {
+ // We can't use QUERY_STRING directly because URL rewriting
+ // might add more parameters to the query string than those
+ // from the request URI.
+ $url = parse_url($_SERVER['REQUEST_URI']);
+ if (!empty($url['query'])) {
+ $search[] = '?' . $url['query'];
+ }
+ }
+ $path = str_replace($search, '', $_SERVER['REQUEST_URI']);
+ if ($path == '/') {
+ $path = '';
+ }
+ return $path;
+ }
+
+ return '';
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeMailRfc822Addressphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Mail-Rfc822-Address.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Mail-Rfc822-Address.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Mail-Rfc822-Address.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,220 @@
</span><ins>+<?php
+/**
+ * Copyright 2012-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (BSD). If you
+ * did not receive this file, see http://www.horde.org/licenses/bsd.
+ *
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/bsd New BSD License
+ * @package Mail
+ */
+
+/**
+ * Object representation of a RFC 822 e-mail address.
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/bsd New BSD License
+ * @package Mail
+ *
+ * @property-read string $bare_address The bare mailbox@host address.
+ * @property-read string $encoded The full MIME/IDN encoded address (UTF-8).
+ * @property string $host Returns the host part (UTF-8).
+ * @property-read string $host_idn Returns the IDN encoded host part.
+ * @property-read string $label The shorthand label for this address.
+ * @property string $personal The personal part (UTF-8).
+ * @property-read string $personal_encoded The MIME encoded personal part
+ * (UTF-8).
+ * @property-read boolean $valid Returns true if there is enough information
+ * in object to create a valid address.
+ */
+class Horde_Mail_Rfc822_Address extends Horde_Mail_Rfc822_Object
+{
+ /**
+ * Comments associated with the personal phrase.
+ *
+ * @var array
+ */
+ public $comment = array();
+
+ /**
+ * Local-part of the address.
+ *
+ * @var string
+ */
+ public $mailbox = null;
+
+ /**
+ * Hostname of the address.
+ *
+ * @var string
+ */
+ protected $_host = null;
+
+ /**
+ * Personal part of the address.
+ *
+ * @var string
+ */
+ protected $_personal = null;
+
+ /**
+ * Constructor.
+ *
+ * @param string $address If set, address is parsed and used as the
+ * object address. Address is not validated;
+ * first e-mail address parsed is used.
+ */
+ public function __construct($address = null)
+ {
+ if (!is_null($address)) {
+ $rfc822 = new Horde_Mail_Rfc822();
+ $addr = $rfc822->parseAddressList($address);
+ if (count($addr)) {
+ foreach ($addr[0] as $key => $val) {
+ $this->$key = $val;
+ }
+ }
+ }
+ }
+
+ /**
+ */
+ public function __set($name, $value)
+ {
+ switch ($name) {
+ case 'host':
+ $value = ltrim($value, '@');
+ $this->_host = function_exists('idn_to_utf8')
+ ? strtolower(idn_to_utf8($value))
+ : strtolower($value);
+ break;
+
+ case 'personal':
+ $this->_personal = strlen($value)
+ ? Horde_Mime::decode($value)
+ : null;
+ break;
+ }
+ }
+
+ /**
+ */
+ public function __get($name)
+ {
+ switch ($name) {
+ case 'bare_address':
+ return is_null($this->host)
+ ? $this->mailbox
+ : $this->mailbox . '@' . $this->host;
+
+ case 'encoded':
+ return $this->writeAddress(true);
+
+ case 'host':
+ return $this->_host;
+
+ case 'host_idn':
+ return function_exists('idn_to_ascii')
+ ? idn_to_ascii($this->_host)
+ : $this->host;
+
+ case 'label':
+ return is_null($this->_personal)
+ ? $this->bare_address
+ : $this->_personal;
+
+ case 'personal':
+ return $this->_personal;
+
+ case 'personal_encoded':
+ return Horde_Mime::encode($this->personal);
+
+ case 'valid':
+ return (bool)strlen($this->mailbox);
+
+ default:
+ return null;
+ }
+ }
+
+ /**
+ */
+ protected function _writeAddress($opts)
+ {
+ $rfc822 = new Horde_Mail_Rfc822();
+
+ $address = $rfc822->encode($this->mailbox, 'address');
+ $host = empty($opts['idn']) ? $this->host : $this->host_idn;
+ if (strlen($host)) {
+ $address .= '@' . $host;
+ }
+ $personal = $this->personal;
+ if (strlen($personal)) {
+ if (!empty($opts['encode'])) {
+ $personal = Horde_Mime::encode($this->personal, $opts['encode']);
+ }
+ $personal = $rfc822->encode($personal, 'personal');
+ }
+
+ return (strlen($personal) && ($personal != $address))
+ ? $personal . ' <' . $address . '>'
+ : $address;
+ }
+
+ /**
+ */
+ public function match($ob)
+ {
+ if (!($ob instanceof Horde_Mail_Rfc822_Address)) {
+ $ob = new Horde_Mail_Rfc822_Address($ob);
+ }
+
+ return ($this->bare_address == $ob->bare_address);
+ }
+
+ /**
+ * Do a case-insensitive match on the address. Per RFC 822/2822/5322,
+ * although the host portion of an address is case-insensitive, the
+ * mailbox portion is platform dependent.
+ *
+ * @param mixed $ob Address data.
+ *
+ * @return boolean True if the data reflects the same case-insensitive
+ * address.
+ */
+ public function matchInsensitive($ob)
+ {
+ if (!($ob instanceof Horde_Mail_Rfc822_Address)) {
+ $ob = new Horde_Mail_Rfc822_Address($ob);
+ }
+
+ return (Horde_String::lower($this->bare_address) == Horde_String::lower($ob->bare_address));
+ }
+
+ /**
+ * Do a case-insensitive match on the address for a given domain.
+ * Matches as many parts of the subdomain in the address as is given in
+ * the input.
+ *
+ * @param string $domain Domain to match.
+ *
+ * @return boolean True if the address matches the given domain.
+ */
+ public function matchDomain($domain)
+ {
+ $host = $this->host;
+ if (is_null($host)) {
+ return false;
+ }
+
+ $match_domain = explode('.', $domain);
+ $match_host = array_slice(explode('.', $host), count($match_domain) * -1);
+
+ return (strcasecmp($domain, implode('.', $match_host)) === 0);
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeMailRfc822Listphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Mail-Rfc822-List.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Mail-Rfc822-List.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Mail-Rfc822-List.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,493 @@
</span><ins>+<?php
+/**
+ * Copyright 2012-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (BSD). If you
+ * did not receive this file, see http://www.horde.org/licenses/bsd.
+ *
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/bsd New BSD License
+ * @package Mail
+ */
+
+/**
+ * Container object for a collection of RFC 822 elements.
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/bsd New BSD License
+ * @package Mail
+ *
+ * @property-read array $addresses The list of all addresses (address
+ * w/personal parts).
+ * @property-read array $bare_addresses The list of all addresses (mail@host).
+ * @property-read array $base_addresses The list of ONLY base addresses
+ * (Address objects).
+ * @property-read array $raw_addresses The list of all addresses (Address
+ * objects).
+ */
+class Horde_Mail_Rfc822_List extends Horde_Mail_Rfc822_Object implements ArrayAccess, Countable, SeekableIterator, Serializable
+{
+ /** Filter masks. */
+ const HIDE_GROUPS = 1;
+ const BASE_ELEMENTS = 2;
+
+ /**
+ * List data.
+ *
+ * @var array
+ */
+ protected $_data = array();
+
+ /**
+ * Current Iterator filter.
+ *
+ * @var array
+ */
+ protected $_filter = array();
+
+ /**
+ * Current Iterator pointer.
+ *
+ * @var array
+ */
+ protected $_ptr;
+
+ /**
+ * Constructor.
+ *
+ * @param mixed $obs Address data to store in this object.
+ */
+ public function __construct($obs = null)
+ {
+ if (!is_null($obs)) {
+ $this->add($obs);
+ }
+ }
+
+ /**
+ */
+ public function __get($name)
+ {
+ switch ($name) {
+ case 'addresses':
+ case 'bare_addresses':
+ case 'base_addresses':
+ case 'raw_addresses':
+ $old = $this->_filter;
+ $mask = ($name == 'base_addresses')
+ ? self::BASE_ELEMENTS
+ : self::HIDE_GROUPS;
+ $this->setIteratorFilter($mask, empty($old['filter']) ? null : $old['filter']);
+
+ $out = array();
+ foreach ($this as $val) {
+ switch ($name) {
+ case 'addresses':
+ $out[] = strval($val);
+ break;
+
+ case 'bare_addresses':
+ $out[] = $val->bare_address;
+ break;
+
+ case 'base_addresses':
+ case 'raw_addresses':
+ $out[] = clone $val;
+ break;
+ }
+ }
+
+ $this->_filter = $old;
+ return $out;
+ }
+ }
+
+ /**
+ * Add objects to the container.
+ *
+ * @param mixed $obs Address data to store in this object.
+ */
+ public function add($obs)
+ {
+ foreach ($this->_normalize($obs) as $val) {
+ $this->_data[] = $val;
+ }
+ }
+
+ /**
+ * Remove addresses from the container. This method ignores Group objects.
+ *
+ * @param mixed $obs Addresses to remove.
+ */
+ public function remove($obs)
+ {
+ $old = $this->_filter;
+ $this->setIteratorFilter(self::HIDE_GROUPS | self::BASE_ELEMENTS);
+
+ foreach ($this->_normalize($obs) as $val) {
+ $remove = array();
+
+ foreach ($this as $key => $val2) {
+ if ($val2->match($val)) {
+ $remove[] = $key;
+ }
+ }
+
+ foreach (array_reverse($remove) as $key) {
+ unset($this[$key]);
+ }
+ }
+
+ $this->_filter = $old;
+ }
+
+ /**
+ * Removes duplicate addresses from list. This method ignores Group
+ * objects.
+ */
+ public function unique()
+ {
+ $exist = $remove = array();
+
+ $old = $this->_filter;
+ $this->setIteratorFilter(self::HIDE_GROUPS | self::BASE_ELEMENTS);
+
+ // For duplicates, we use the first address that contains personal
+ // information.
+ foreach ($this as $key => $val) {
+ $bare = $val->bare_address;
+ if (isset($exist[$bare])) {
+ if (($exist[$bare] == -1) || is_null($val->personal)) {
+ $remove[] = $key;
+ } else {
+ $remove[] = $exist[$bare];
+ $exist[$bare] = -1;
+ }
+ } else {
+ $exist[$bare] = is_null($val->personal)
+ ? $key
+ : -1;
+ }
+ }
+
+ foreach (array_reverse($remove) as $key) {
+ unset($this[$key]);
+ }
+
+ $this->_filter = $old;
+ }
+
+ /**
+ * Group count.
+ *
+ * @return integer The number of groups in the list.
+ */
+ public function groupCount()
+ {
+ $ret = 0;
+
+ foreach ($this->_data as $val) {
+ if ($val instanceof Horde_Mail_Rfc822_Group) {
+ ++$ret;
+ }
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Set the Iterator filter.
+ *
+ * @param integer $mask Filter masks.
+ * @param mixed $filter An e-mail, or as list of e-mails, to filter by.
+ */
+ public function setIteratorFilter($mask = 0, $filter = null)
+ {
+ $this->_filter = array();
+
+ if ($mask) {
+ $this->_filter['mask'] = $mask;
+ }
+
+ if (!is_null($filter)) {
+ $rfc822 = new Horde_Mail_Rfc822();
+ $this->_filter['filter'] = $rfc822->parseAddressList($filter);
+ }
+ }
+
+ /**
+ */
+ protected function _writeAddress($opts)
+ {
+ $out = array();
+
+ foreach ($this->_data as $val) {
+ $out[] = $val->writeAddress($opts);
+ }
+
+ return implode(', ', $out);
+ }
+
+ /**
+ */
+ public function match($ob)
+ {
+ if (!($ob instanceof Horde_Mail_Rfc822_List)) {
+ $ob = new Horde_Mail_Rfc822_List($ob);
+ }
+
+ $a = $this->bare_addresses;
+ sort($a);
+ $b = $ob->bare_addresses;
+ sort($b);
+
+ return ($a == $b);
+ }
+
+ /**
+ * Does this list contain the given e-mail address?
+ *
+ * @param mixed $address An e-mail address.
+ *
+ * @return boolean True of the e-mail address is contained in the list.
+ */
+ public function contains($address)
+ {
+ $ob = new Horde_Mail_Rfc822_Address($address);
+
+ foreach ($this->raw_addresses as $val) {
+ if ($val->match($ob)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Normalize objects to add to list.
+ *
+ * @param mixed $obs Address data to store in this object.
+ *
+ * @return array Entries to add.
+ */
+ protected function _normalize($obs)
+ {
+ $add = array();
+
+ if (!($obs instanceof Horde_Mail_Rfc822_List) &&
+ !is_array($obs)) {
+ $obs = array($obs);
+ }
+
+ foreach ($obs as $val) {
+ if (is_string($val)) {
+ $rfc822 = new Horde_Mail_Rfc822();
+ $val = $rfc822->parseAddressList($val);
+ }
+
+ if ($val instanceof Horde_Mail_Rfc822_List) {
+ $val->setIteratorFilter(self::BASE_ELEMENTS);
+ foreach ($val as $val2) {
+ $add[] = $val2;
+ }
+ } elseif ($val instanceof Horde_Mail_Rfc822_Object) {
+ $add[] = $val;
+ }
+ }
+
+ return $add;
+ }
+
+ /* ArrayAccess methods. */
+
+ /**
+ */
+ public function offsetExists($offset)
+ {
+ return !is_null($this[$offset]);
+ }
+
+ /**
+ */
+ public function offsetGet($offset)
+ {
+ try {
+ $this->seek($offset);
+ return $this->current();
+ } catch (OutOfBoundsException $e) {
+ return null;
+ }
+ }
+
+ /**
+ */
+ public function offsetSet($offset, $value)
+ {
+ if ($ob = $this[$offset]) {
+ if (is_null($this->_ptr['subidx'])) {
+ $tmp = $this->_normalize($value);
+ if (isset($tmp[0])) {
+ $this->_data[$this->_ptr['idx']] = $tmp[0];
+ }
+ } else {
+ $ob[$offset] = $value;
+ }
+ $this->_ptr = null;
+ }
+ }
+
+ /**
+ */
+ public function offsetUnset($offset)
+ {
+ if ($ob = $this[$offset]) {
+ if (is_null($this->_ptr['subidx'])) {
+ unset($this->_data[$this->_ptr['idx']]);
+ $this->_data = array_values($this->_data);
+ } else {
+ unset($ob->addresses[$this->_ptr['subidx']]);
+ }
+ $this->_ptr = null;
+ }
+ }
+
+ /* Countable methods. */
+
+ /**
+ * Address count.
+ *
+ * @return integer The number of addresses.
+ */
+ public function count()
+ {
+ return count($this->addresses);
+ }
+
+ /* Iterator methods. */
+
+ public function current()
+ {
+ if (!$this->valid()) {
+ return null;
+ }
+
+ $ob = $this->_data[$this->_ptr['idx']];
+
+ return is_null($this->_ptr['subidx'])
+ ? $ob
+ : $ob->addresses[$this->_ptr['subidx']];
+ }
+
+ public function key()
+ {
+ return $this->_ptr['key'];
+ }
+
+ public function next()
+ {
+ if (is_null($this->_ptr['subidx'])) {
+ $curr = $this->current();
+ if (($curr instanceof Horde_Mail_Rfc822_Group) && count($curr)) {
+ $this->_ptr['subidx'] = 0;
+ } else {
+ ++$this->_ptr['idx'];
+ }
+ $curr = $this->current();
+ } elseif (!($curr = $this->_data[$this->_ptr['idx']]->addresses[++$this->_ptr['subidx']])) {
+ $this->_ptr['subidx'] = null;
+ ++$this->_ptr['idx'];
+ $curr = $this->current();
+ }
+
+ if (!is_null($curr)) {
+ if (!empty($this->_filter) && $this->_iteratorFilter($curr)) {
+ $this->next();
+ } else {
+ ++$this->_ptr['key'];
+ }
+ }
+ }
+
+ public function rewind()
+ {
+ $this->_ptr = array(
+ 'idx' => 0,
+ 'key' => 0,
+ 'subidx' => null
+ );
+
+ if ($this->valid() &&
+ !empty($this->_filter) &&
+ $this->_iteratorFilter($this->current())) {
+ $this->next();
+ $this->_ptr['key'] = 0;
+ }
+ }
+
+ public function valid()
+ {
+ return (!empty($this->_ptr) && isset($this->_data[$this->_ptr['idx']]));
+ }
+
+ public function seek($position)
+ {
+ if (!$this->valid() ||
+ ($position < $this->_ptr['key'])) {
+ $this->rewind();
+ }
+
+ for ($i = $this->_ptr['key']; ; ++$i) {
+ if ($i == $position) {
+ return;
+ }
+
+ $this->next();
+ if (!$this->valid()) {
+ throw new OutOfBoundsException('Position not found.');
+ }
+ }
+ }
+
+ protected function _iteratorFilter($ob)
+ {
+ if (!empty($this->_filter['mask'])) {
+ if (($this->_filter['mask'] & self::HIDE_GROUPS) &&
+ ($ob instanceof Horde_Mail_Rfc822_Group)) {
+ return true;
+ }
+
+ if (($this->_filter['mask'] & self::BASE_ELEMENTS) &&
+ !is_null($this->_ptr['subidx'])) {
+ return true;
+ }
+ }
+
+ if (!empty($this->_filter['filter']) &&
+ ($ob instanceof Horde_Mail_Rfc822_Address)) {
+ foreach ($this->_filter['filter'] as $val) {
+ if ($ob->match($val)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /* Serializable methods. */
+
+ public function serialize()
+ {
+ return serialize($this->_data);
+ }
+
+ public function unserialize($data)
+ {
+ $this->_data = unserialize($data);
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeMailRfc822Objectphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Mail-Rfc822-Object.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Mail-Rfc822-Object.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Mail-Rfc822-Object.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,86 @@
</span><ins>+<?php
+/**
+ * Copyright 2012-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (BSD). If you
+ * did not receive this file, see http://www.horde.org/licenses/bsd.
+ *
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/bsd New BSD License
+ * @package Mail
+ */
+
+/**
+ * Object representation of an RFC 822 element.
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2012-2013 Horde LLC
+ * @license http://www.horde.org/licenses/bsd New BSD License
+ * @package Mail
+ */
+abstract class Horde_Mail_Rfc822_Object
+{
+ /**
+ * String representation of object.
+ *
+ * @return string Returns the full e-mail address.
+ */
+ public function __toString()
+ {
+ return $this->writeAddress();
+ }
+
+ /**
+ * Write an address given information in this part.
+ *
+ * @param mixed $opts If boolean true, is equivalent to passing true for
+ * both 'encode' and 'idn'. If an array, these
+ * keys are supported:
+ * - encode: (mixed) MIME encode the personal/groupname parts?
+ * If boolean true, encodes in 'UTF-8'.
+ * If a string, encodes using this charset.
+ * DEFAULT: false
+ * - idn: (boolean) If true, encodes IDN domain names
+ * (Punycode/RFC 3490).
+ * Requires the idn or intl PHP module.
+ * DEFAULT: false
+ *
+ * @return string The correctly escaped/quoted address.
+ */
+ public function writeAddress($opts = array())
+ {
+ if ($opts === true) {
+ $opts = array(
+ 'encode' => 'UTF-8',
+ 'idn' => true
+ );
+ } elseif (!empty($opts['encode']) && ($opts['encode'] === true)) {
+ $opts['encode'] = 'UTF-8';
+ }
+
+ return $this->_writeAddress($opts);
+ }
+
+ /**
+ * Class-specific implementation of writeAddress().
+ *
+ * @see writeAddress()
+ *
+ * @param array $opts See writeAddress().
+ *
+ * @return string The correctly escaped/quoted address.
+ */
+ abstract protected function _writeAddress($opts);
+
+ /**
+ * Compare this object against other data.
+ *
+ * @param mixed $ob Address data.
+ *
+ * @return boolean True if the data reflects the same canonical address.
+ */
+ abstract public function match($ob);
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeMailRfc822php"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Mail-Rfc822.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Mail-Rfc822.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Mail-Rfc822.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,839 @@
</span><ins>+<?php
+/**
+ * Copyright (c) 2001-2010, Richard Heyes
+ * Copyright 2011-2013 Horde LLC (http://www.horde.org/)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * o Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * o Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * o The names of the authors may not be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * RFC822 parsing code adapted from message-address.c and rfc822-parser.c
+ * (Dovecot 2.1rc5)
+ * Original code released under LGPL-2.1
+ * Copyright (c) 2002-2011 Timo Sirainen <tss@iki.fi>
+ *
+ * @category Horde
+ * @copyright 2001-2010 Richard Heyes
+ * @copyright 2002-2011 Timo Sirainen
+ * @copyright 2011-2013 Horde LLC
+ * @license http://www.horde.org/licenses/bsd New BSD License
+ * @package Mail
+ */
+
+/**
+ * RFC 822/2822/3490/5322 Email parser/validator.
+ *
+ * @author Richard Heyes <richard@phpguru.org>
+ * @author Chuck Hagenbuch <chuck@horde.org>
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @author Timo Sirainen <tss@iki.fi>
+ * @category Horde
+ * @copyright 2001-2010 Richard Heyes
+ * @copyright 2002-2011 Timo Sirainen
+ * @copyright 2011-2013 Horde LLC
+ * @license http://www.horde.org/licenses/bsd New BSD License
+ * @package Mail
+ */
+class Horde_Mail_Rfc822
+{
+ /**
+ * Valid atext characters.
+ *
+ * @since 2.0.3
+ */
+ const ATEXT = '!#$%&\'*+-./0123456789=?ABCDEFGHIJKLMNOPQRSTUVWXYZ^_`abcdefghijklmnopqrstuvwxyz{|}~';
+
+ /**
+ * Excluded (in ASCII): 0-8, 10-31, 34, 40-41, 44, 58-60, 62, 64,
+ * 91-93, 127
+ *
+ * @since 2.0.3
+ */
+ const ENCODE_FILTER = "\0\1\2\3\4\5\6\7\10\12\13\14\15\16\17\20\21\22\23\24\25\26\27\30\31\32\33\34\35\36\37\"(),:;<>@[\\]\177";
+
+ /**
+ * The address string to parse.
+ *
+ * @var string
+ */
+ protected $_data;
+
+ /**
+ * Length of the address string.
+ *
+ * @var integer
+ */
+ protected $_datalen;
+
+ /**
+ * Comment cache.
+ *
+ * @var string
+ */
+ protected $_comments = array();
+
+ /**
+ * List object to return in parseAddressList().
+ *
+ * @var Horde_Mail_Rfc822_List
+ */
+ protected $_listob;
+
+ /**
+ * Configuration parameters.
+ *
+ * @var array
+ */
+ protected $_params = array();
+
+ /**
+ * Data pointer.
+ *
+ * @var integer
+ */
+ protected $_ptr;
+
+ /**
+ * Starts the whole process.
+ *
+ * @param mixed $address The address(es) to validate. Either a string,
+ * a Horde_Mail_Rfc822_Object, or an array of
+ * strings and/or Horde_Mail_Rfc822_Objects.
+ * @param array $params Optional parameters:
+ * - default_domain: (string) Default domain/host.
+ * DEFAULT: None
+ * - group: (boolean) Return a GroupList object instead of a List object?
+ * DEFAULT: false
+ * - limit: (integer) Stop processing after this many addresses.
+ * DEFAULT: No limit (0)
+ * - validate: (boolean) Strict validation of personal part data? If
+ * true, throws an Exception on error. If false, attempts
+ * to allow non-ASCII characters and non-quoted strings in
+ * the personal data, and will silently abort if an
+ * unparseable address is found.
+ * DEFAULT: false
+ *
+ * @return Horde_Mail_Rfc822_List A list object.
+ *
+ * @throws Horde_Mail_Exception
+ */
+ public function parseAddressList($address, array $params = array())
+ {
+ if ($address instanceof Horde_Mail_Rfc822_List) {
+ return $address;
+ }
+
+ if (empty($params['limit'])) {
+ $params['limit'] = null;
+ }
+
+ $this->_params = array_merge(array(
+ 'default_domain' => null,
+ 'validate' => false
+ ), $params);
+
+ $this->_listob = empty($this->_params['group'])
+ ? new Horde_Mail_Rfc822_List()
+ : new Horde_Mail_Rfc822_GroupList();
+
+ if (!is_array($address)) {
+ $address = array($address);
+ }
+
+ $tmp = array();
+ foreach ($address as $val) {
+ if ($val instanceof Horde_Mail_Rfc822_Object) {
+ $this->_listob->add($val);
+ } else {
+ $tmp[] = rtrim(trim($val), ',');
+ }
+ }
+
+ if (!empty($tmp)) {
+ $this->_data = implode(',', $tmp);
+ $this->_datalen = strlen($this->_data);
+ $this->_ptr = 0;
+
+ $this->_parseAddressList();
+ }
+
+ return $this->_listob;
+ }
+
+ /**
+ * Quotes and escapes the given string if necessary using rules contained
+ * in RFC 2822 [3.2.5].
+ *
+ * @param string $str The string to be quoted and escaped.
+ * @param string $type Either 'address', or 'personal'.
+ *
+ * @return string The correctly quoted and escaped string.
+ */
+ public function encode($str, $type = 'address')
+ {
+ switch ($type) {
+ case 'personal':
+ // RFC 2822 [3.4]: Period not allowed in display name
+ $filter = '.';
+ break;
+
+ case 'address':
+ default:
+ // RFC 2822 [3.4.1]: (HTAB, SPACE) not allowed in address
+ $filter = "\11\40";
+ break;
+ }
+
+ // Strip double quotes if they are around the string already.
+ // If quoted, we know that the contents are already escaped, so
+ // unescape now.
+ $str = trim($str);
+ if ($str && ($str[0] == '"') && (substr($str, -1) == '"')) {
+ $str = stripslashes(substr($str, 1, -1));
+ }
+
+ return (strcspn($str, self::ENCODE_FILTER . $filter) != strlen($str))
+ ? '"' . addcslashes($str, '\\"') . '"'
+ : $str;
+ }
+
+ /**
+ * If an email address has no personal information, get rid of any angle
+ * brackets (<>) around it.
+ *
+ * @param string $address The address to trim.
+ *
+ * @return string The trimmed address.
+ */
+ public function trimAddress($address)
+ {
+ $address = trim($address);
+
+ return (($address[0] == '<') && (substr($address, -1) == '>'))
+ ? substr($address, 1, -1)
+ : $address;
+ }
+
+ /* RFC 822 parsing methods. */
+
+ /**
+ * address-list = (address *("," address)) / obs-addr-list
+ */
+ protected function _parseAddressList()
+ {
+ $limit = $this->_params['limit'];
+
+ while (($this->_curr() !== false) &&
+ (is_null($limit) || ($limit > 0))) {
+ try {
+ $this->_parseAddress();
+ } catch (Horde_Mail_Exception $e) {
+ if ($this->_params['validate']) {
+ throw $e;
+ }
+ ++$this->_ptr;
+ }
+
+ switch ($this->_curr()) {
+ case ',':
+ $this->_rfc822SkipLwsp(true);
+ break;
+
+ case false:
+ // No-op
+ break;
+
+ default:
+ if ($this->_params['validate']) {
+ throw new Horde_Mail_Exception('Error when parsing address list.');
+ }
+ break;
+ }
+ $limit--;
+ }
+ }
+
+ /**
+ * address = mailbox / group
+ */
+ protected function _parseAddress()
+ {
+ $start = $this->_ptr;
+ if (!$this->_parseGroup()) {
+ $this->_ptr = $start;
+ if ($mbox = $this->_parseMailbox()) {
+ $this->_listob->add($mbox);
+ }
+ }
+ }
+
+ /**
+ * group = display-name ":" [mailbox-list / CFWS] ";" [CFWS]
+ * display-name = phrase
+ *
+ * @return boolean True if a group was parsed.
+ *
+ * @throws Horde_Mail_Exception
+ */
+ protected function _parseGroup()
+ {
+ $this->_rfc822ParsePhrase($groupname);
+
+ if ($this->_curr(true) != ':') {
+ return false;
+ }
+
+ $addresses = new Horde_Mail_Rfc822_GroupList();
+
+ $this->_rfc822SkipLwsp();
+
+ while (($chr = $this->_curr()) !== false) {
+ if ($chr == ';') {
+ ++$this->_ptr;
+
+ if (count($addresses)) {
+ $this->_listob->add(new Horde_Mail_Rfc822_Group($groupname, $addresses));
+ }
+
+ return true;
+ }
+
+ /* mailbox-list = (mailbox *("," mailbox)) / obs-mbox-list */
+ $addresses->add($this->_parseMailbox());
+
+ switch ($this->_curr()) {
+ case ',':
+ $this->_rfc822SkipLwsp(true);
+ break;
+
+ case ';':
+ // No-op
+ break;
+
+ default:
+ break 2;
+ }
+ }
+
+ throw new Horde_Mail_Exception('Error when parsing group.');
+ }
+
+ /**
+ * mailbox = name-addr / addr-spec
+ *
+ * @return mixed Mailbox object if mailbox was parsed, or false.
+ */
+ protected function _parseMailbox()
+ {
+ $this->_comments = array();
+ $start = $this->_ptr;
+
+ if (!($ob = $this->_parseNameAddr())) {
+ $this->_comments = array();
+ $this->_ptr = $start;
+ $ob = $this->_parseAddrSpec();
+ }
+
+ if ($ob) {
+ $ob->comment = $this->_comments;
+ }
+
+ return $ob;
+ }
+
+ /**
+ * name-addr = [display-name] angle-addr
+ * display-name = phrase
+ *
+ * @return mixed Mailbox object, or false.
+ */
+ protected function _parseNameAddr()
+ {
+ $this->_rfc822ParsePhrase($personal);
+
+ if ($ob = $this->_parseAngleAddr()) {
+ $ob->personal = $personal;
+ return $ob;
+ }
+
+ return false;
+ }
+
+ /**
+ * addr-spec = local-part "@" domain
+ *
+ * @return mixed Mailbox object.
+ *
+ * @throws Horde_Mail_Exception
+ */
+ protected function _parseAddrSpec()
+ {
+ $ob = new Horde_Mail_Rfc822_Address();
+ $ob->mailbox = $this->_parseLocalPart();
+
+ if ($this->_curr() == '@') {
+ $this->_rfc822ParseDomain($host);
+ if (strlen($host)) {
+ $ob->host = $host;
+ }
+ }
+
+ if (is_null($ob->host) &&
+ !is_null($this->_params['default_domain'])) {
+ $ob->host = $this->_params['default_domain'];
+ }
+
+ return $ob;
+ }
+
+ /**
+ * local-part = dot-atom / quoted-string / obs-local-part
+ * obs-local-part = word *("." word)
+ *
+ * @return string The local part.
+ *
+ * @throws Horde_Mail_Exception
+ */
+ protected function _parseLocalPart()
+ {
+ if (($curr = $this->_curr()) === false) {
+ throw new Horde_Mail_Exception('Error when parsing local part.');
+ }
+
+ if ($curr == '"') {
+ $this->_rfc822ParseQuotedString($str);
+ } else {
+ $this->_rfc822ParseDotAtom($str, ',;@');
+ }
+
+ return $str;
+ }
+
+ /**
+ * "<" [ "@" route ":" ] local-part "@" domain ">"
+ *
+ * @return mixed Mailbox object, or false.
+ *
+ * @throws Horde_Mail_Exception
+ */
+ protected function _parseAngleAddr()
+ {
+ if ($this->_curr() != '<') {
+ return false;
+ }
+
+ $this->_rfc822SkipLwsp(true);
+
+ if ($this->_curr() == '@') {
+ // Route information is ignored.
+ $this->_parseDomainList();
+ if ($this->_curr() != ':') {
+ throw new Horde_Mail_Exception('Invalid route.');
+ }
+
+ $this->_rfc822SkipLwsp(true);
+ }
+
+ $ob = $this->_parseAddrSpec();
+
+ if ($this->_curr() != '>') {
+ throw new Horde_Mail_Exception('Error when parsing angle address.');
+ }
+
+ $this->_rfc822SkipLwsp(true);
+
+ return $ob;
+ }
+
+ /**
+ * obs-domain-list = "@" domain *(*(CFWS / "," ) [CFWS] "@" domain)
+ *
+ * @return array Routes.
+ *
+ * @throws Horde_Mail_Exception
+ */
+ protected function _parseDomainList()
+ {
+ $route = array();
+
+ while ($this->_curr() !== false) {
+ $this->_rfc822ParseDomain($str);
+ $route[] = '@' . $str;
+
+ $this->_rfc822SkipLwsp();
+ if ($this->_curr() != ',') {
+ return $route;
+ }
+ ++$this->_ptr;
+ }
+
+ throw new Horde_Mail_Exception('Invalid domain list.');
+ }
+
+ /* RFC 822 parsing methods. */
+
+ /**
+ * phrase = 1*word / obs-phrase
+ * word = atom / quoted-string
+ * obs-phrase = word *(word / "." / CFWS)
+ *
+ * @param string &$phrase The phrase data.
+ *
+ * @throws Horde_Mail_Exception
+ */
+ protected function _rfc822ParsePhrase(&$phrase)
+ {
+ $curr = $this->_curr();
+ if (($curr === false) || ($curr == '.')) {
+ throw new Horde_Mail_Exception('Error when parsing a group.');
+ }
+
+ while (($curr = $this->_curr()) !== false) {
+ if ($curr == '"') {
+ $this->_rfc822ParseQuotedString($phrase);
+ } else {
+ $this->_rfc822ParseAtomOrDot($phrase);
+ }
+
+ $chr = $this->_curr();
+ if (!$this->_rfc822IsAtext($chr) &&
+ ($chr != '"') &&
+ ($chr != '.')) {
+ break;
+ }
+
+ $phrase .= ' ';
+ }
+
+ $this->_rfc822SkipLwsp();
+ }
+
+ /**
+ * @param string &$phrase The quoted string data.
+ *
+ * @throws Horde_Mail_Exception
+ */
+ protected function _rfc822ParseQuotedString(&$str)
+ {
+ if ($this->_curr(true) != '"') {
+ throw new Horde_Mail_Exception('Error when parsing a quoted string.');
+ }
+
+ while (($chr = $this->_curr(true)) !== false) {
+ switch ($chr) {
+ case '"':
+ $this->_rfc822SkipLwsp();
+ return;
+
+ case "\n":
+ /* Folding whitespace, remove the (CR)LF. */
+ if ($str[strlen($str) - 1] == "\r") {
+ $str = substr($str, 0, -1);
+ }
+ continue;
+
+ case '\\':
+ if (($chr = $this->_curr(true)) === false) {
+ break 2;
+ }
+ break;
+ }
+
+ $str .= $chr;
+ }
+
+ /* Missing trailing '"', or partial quoted character. */
+ throw new Horde_Mail_Exception('Error when parsing a quoted string.');
+ }
+
+ /**
+ * dot-atom = [CFWS] dot-atom-text [CFWS]
+ * dot-atom-text = 1*atext *("." 1*atext)
+ *
+ * atext = ; Any character except controls, SP, and specials.
+ *
+ * For RFC-822 compatibility allow LWSP around '.'
+ *
+ * @param string &$str The atom/dot data.
+ * @param string $validate Use these characters as delimiter.
+ *
+ * @throws Horde_Mail_Exception
+ */
+ protected function _rfc822ParseDotAtom(&$str, $validate = null)
+ {
+ $curr = $this->_curr();
+ if (($curr === false) || !$this->_rfc822IsAtext($curr, $validate)) {
+ throw new Horde_Mail_Exception('Error when parsing dot-atom.');
+ }
+
+ while (($chr = $this->_curr()) !== false) {
+ if ($this->_rfc822IsAtext($chr, $validate)) {
+ $str .= $chr;
+ ++$this->_ptr;
+ } else {
+ $this->_rfc822SkipLwsp();
+
+ if ($this->_curr() != '.') {
+ return;
+ }
+ $str .= '.';
+
+ $this->_rfc822SkipLwsp(true);
+ }
+ }
+ }
+
+ /**
+ * atom = [CFWS] 1*atext [CFWS]
+ * atext = ; Any character except controls, SP, and specials.
+ *
+ * This method doesn't just silently skip over WS.
+ *
+ * @param string &$str The atom/dot data.
+ *
+ * @throws Horde_Mail_Exception
+ */
+ protected function _rfc822ParseAtomOrDot(&$str)
+ {
+ while (($chr = $this->_curr()) !== false) {
+ if (($chr != '.') && !$this->_rfc822IsAtext($chr, ',<:')) {
+ $this->_rfc822SkipLwsp();
+ if (!$this->_params['validate']) {
+ $str = trim($str);
+ }
+ return;
+ }
+
+ $str .= $chr;
+ ++$this->_ptr;
+ }
+ }
+
+ /**
+ * domain = dot-atom / domain-literal / obs-domain
+ * domain-literal = [CFWS] "[" *([FWS] dcontent) [FWS] "]" [CFWS]
+ * obs-domain = atom *("." atom)
+ *
+ * @param string &$str The domain string.
+ *
+ * @throws Horde_Mail_Exception
+ */
+ protected function _rfc822ParseDomain(&$str)
+ {
+ if ($this->_curr(true) != '@') {
+ throw new Horde_Mail_Exception('Error when parsing domain.');
+ }
+
+ $this->_rfc822SkipLwsp();
+
+ if ($this->_curr() == '[') {
+ $this->_rfc822ParseDomainLiteral($str);
+ } else {
+ $this->_rfc822ParseDotAtom($str, ';,> ');
+ }
+ }
+
+ /**
+ * domain-literal = [CFWS] "[" *([FWS] dcontent) [FWS] "]" [CFWS]
+ * dcontent = dtext / quoted-pair
+ * dtext = NO-WS-CTL / ; Non white space controls
+ * %d33-90 / ; The rest of the US-ASCII
+ * %d94-126 ; characters not including "[",
+ * ; "]", or "\"
+ *
+ * @param string &$str The domain string.
+ *
+ * @throws Horde_Mail_Exception
+ */
+ protected function _rfc822ParseDomainLiteral(&$str)
+ {
+ if ($this->_curr(true) != '[') {
+ throw new Horde_Mail_Exception('Error parsing domain literal.');
+ }
+
+ while (($chr = $this->_curr(true)) !== false) {
+ switch ($chr) {
+ case '\\':
+ if (($chr = $this->_curr(true)) === false) {
+ break 2;
+ }
+ break;
+
+ case ']':
+ $this->_rfc822SkipLwsp();
+ return;
+ }
+
+ $str .= $chr;
+ }
+
+ throw new Horde_Mail_Exception('Error parsing domain literal.');
+ }
+
+ /**
+ * @param boolean $advance Advance cursor?
+ *
+ * @throws Horde_Mail_Exception
+ */
+ protected function _rfc822SkipLwsp($advance = false)
+ {
+ if ($advance) {
+ ++$this->_ptr;
+ }
+
+ while (($chr = $this->_curr()) !== false) {
+ switch ($chr) {
+ case ' ':
+ case "\n":
+ case "\r":
+ case "\t":
+ ++$this->_ptr;
+ continue;
+
+ case '(':
+ $this->_rfc822SkipComment();
+ break;
+
+ default:
+ return;
+ }
+ }
+ }
+
+ /**
+ * @throws Horde_Mail_Exception
+ */
+ protected function _rfc822SkipComment()
+ {
+ if ($this->_curr(true) != '(') {
+ throw new Horde_Mail_Exception('Error when parsing a comment.');
+ }
+
+ $comment = '';
+ $level = 1;
+
+ while (($chr = $this->_curr(true)) !== false) {
+ switch ($chr) {
+ case '(':
+ ++$level;
+ continue;
+
+ case ')':
+ if (--$level == 0) {
+ $this->_comments[] = $comment;
+ return;
+ }
+ break;
+
+ case '\\':
+ if (($chr = $this->_curr(true)) === false) {
+ break 2;
+ }
+ break;
+ }
+
+ $comment .= $chr;
+ }
+
+ throw new Horde_Mail_Exception('Error when parsing a comment.');
+ }
+
+ /**
+ * Check if data is an atom.
+ *
+ * @param string $chr The character to check.
+ * @param string $validate If in non-validate mode, use these characters
+ * as the non-atom delimiters.
+ *
+ * @return boolean True if an atom.
+ */
+ protected function _rfc822IsAtext($chr, $validate = null)
+ {
+ if (is_null($chr)) {
+ return false;
+ }
+
+ return ($this->_params['validate'] || is_null($validate))
+ ? !strcspn($chr, self::ATEXT)
+ : strcspn($chr, $validate);
+ }
+
+ /* Helper methods. */
+
+ /**
+ * Return current character.
+ *
+ * @param boolean $advance If true, advance the cursor.
+ *
+ * @return string The current character (false if EOF reached).
+ */
+ protected function _curr($advance = false)
+ {
+ return ($this->_ptr >= $this->_datalen)
+ ? false
+ : $this->_data[$advance ? $this->_ptr++ : $this->_ptr];
+ }
+
+ /* Other public methods. */
+
+ /**
+ * Returns an approximate count of how many addresses are in the string.
+ * This is APPROXIMATE as it only splits based on a comma which has no
+ * preceding backslash.
+ *
+ * @param string $data Addresses to count.
+ *
+ * @return integer Approximate count.
+ */
+ public function approximateCount($data)
+ {
+ return count(preg_split('/(?<!\\\\),/', $data));
+ }
+
+ /**
+ * Validates whether an email is of the common internet form:
+ * <user>@<domain>. This can be sufficient for most people.
+ *
+ * Optional stricter mode can be utilized which restricts mailbox
+ * characters allowed to: alphanumeric, full stop, hyphen, and underscore.
+ *
+ * @param string $data Address to check.
+ * @param boolean $strict Strict check?
+ *
+ * @return mixed False if it fails, an indexed array username/domain if
+ * it matches.
+ */
+ public function isValidInetAddress($data, $strict = false)
+ {
+ $regex = $strict
+ ? '/^([.0-9a-z_+-]+)@(([0-9a-z-]+\.)+[0-9a-z]{2,})$/i'
+ : '/^([*+!.&#$|\'\\%\/0-9a-z^_`{}=?~:-]+)@(([0-9a-z-]+\.)+[0-9a-z]{2,})$/i';
+
+ return preg_match($regex, trim($data), $matches)
+ ? array($matches[1], $matches[2])
+ : false;
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeMimeHeadersphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Mime-Headers.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Mime-Headers.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Mime-Headers.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,740 @@
</span><ins>+<?php
+/**
+ * This class contains functions related to handling the headers of MIME data.
+ *
+ * Copyright 2002-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Mime
+ */
+class Horde_Mime_Headers implements Serializable
+{
+ /* Serialized version. */
+ const VERSION = 2;
+
+ /* Constants for getValue(). */
+ const VALUE_STRING = 1;
+ const VALUE_BASE = 2;
+ const VALUE_PARAMS = 3;
+
+ /**
+ * The default charset to use when parsing text parts with no charset
+ * information.
+ *
+ * @var string
+ */
+ static public $defaultCharset = 'us-ascii';
+
+ /**
+ * The internal headers array.
+ *
+ * Keys are the lowercase header name.
+ * Values are:
+ * - h: The case-sensitive header name.
+ * - p: Parameters for this header.
+ * - v: The value of the header. Values are stored in UTF-8.
+ *
+ * @var array
+ */
+ protected $_headers = array();
+
+ /**
+ * The sequence to use as EOL for the headers.
+ * The default is currently to output the EOL sequence internally as
+ * just "\n" instead of the canonical "\r\n" required in RFC 822 & 2045.
+ * To be RFC complaint, the full <CR><LF> EOL combination should be used
+ * when sending a message.
+ *
+ * @var string
+ */
+ protected $_eol = "\n";
+
+ /**
+ * The User-Agent string to use.
+ *
+ * @var string
+ */
+ protected $_agent = null;
+
+ /**
+ * List of single header fields.
+ *
+ * @var array
+ */
+ protected $_singleFields = array(
+ // Mail: RFC 5322
+ 'to', 'from', 'cc', 'bcc', 'date', 'sender', 'reply-to',
+ 'message-id', 'in-reply-to', 'references', 'subject', 'x-priority',
+ // MIME: RFC 1864
+ 'content-md5',
+ // MIME: RFC 2045
+ 'mime-version', 'content-type', 'content-transfer-encoding',
+ 'content-id', 'content-description',
+ // MIME: RFC 2110
+ 'content-base',
+ // MIME: RFC 2183
+ 'content-disposition',
+ // MIME: RFC 2424
+ 'content-duration',
+ // MIME: RFC 2557
+ 'content-location',
+ // MIME: RFC 2912 [3]
+ 'content-features',
+ // MIME: RFC 3282
+ 'content-language',
+ // MIME: RFC 3297
+ 'content-alternative'
+ );
+
+ /**
+ * Returns the internal header array in array format.
+ *
+ * @param array $opts Optional parameters:
+ * - canonical: (boolean) Use canonical (RFC 822/2045) line endings?
+ * DEFAULT: Uses $this->_eol
+ * - charset: (string) Encodes the headers using this charset. If empty,
+ * encodes using internal charset (UTF-8).
+ * DEFAULT: No encoding.
+ * - defserver: (string) The default domain to append to mailboxes.
+ * DEFAULT: No default name.
+ * - nowrap: (integer) Don't wrap the headers.
+ * DEFAULT: Headers are wrapped.
+ *
+ * @return array The headers in array format.
+ */
+ public function toArray(array $opts = array())
+ {
+ $address_keys = $this->addressFields();
+ $charset = array_key_exists('charset', $opts)
+ ? (empty($opts['charset']) ? 'UTF-8' : $opts['charset'])
+ : null;
+ $eol = empty($opts['canonical'])
+ ? $this->_eol
+ : "\r\n";
+ $mime = $this->mimeParamFields();
+ $ret = array();
+
+ foreach ($this->_headers as $header => $ob) {
+ $val = is_array($ob['v']) ? $ob['v'] : array($ob['v']);
+
+ foreach (array_keys($val) as $key) {
+ if (in_array($header, $address_keys) ) {
+ /* Address encoded headers. */
+ $rfc822 = new Horde_Mail_Rfc822();
+ $text = $rfc822->parseAddressList($val[$key], array(
+ 'default_domain' => empty($opts['defserver']) ? null : $opts['defserver']
+ ))->writeAddress(array(
+ 'encode' => $charset,
+ 'idn' => true
+ ));
+ } elseif (in_array($header, $mime) && !empty($ob['p'])) {
+ /* MIME encoded headers (RFC 2231). */
+ $text = $val[$key];
+ foreach ($ob['p'] as $name => $param) {
+ foreach (Horde_Mime::encodeParam($name, $param, array('charset' => $charset, 'escape' => true)) as $name2 => $param2) {
+ $text .= '; ' . $name2 . '=' . $param2;
+ }
+ }
+ } else {
+ $text = is_null($charset)
+ ? $val[$key]
+ : Horde_Mime::encode($val[$key], $charset);
+ }
+
+ if (empty($opts['nowrap'])) {
+ /* Remove any existing linebreaks and wrap the line. */
+ $header_text = $ob['h'] . ': ';
+ $text = ltrim(substr(wordwrap($header_text . strtr(trim($text), array("\r" => '', "\n" => '')), 76, $eol . ' '), strlen($header_text)));
+ }
+
+ $val[$key] = $text;
+ }
+
+ $ret[$ob['h']] = (count($val) == 1) ? reset($val) : $val;
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Returns the internal header array in string format.
+ *
+ * @param array $opts Optional parameters:
+ * - canonical: (boolean) Use canonical (RFC 822/2045) line endings?
+ * DEFAULT: Uses $this->_eol
+ * - charset: (string) Encodes the headers using this charset.
+ * DEFAULT: No encoding.
+ * - defserver: (string) The default domain to append to mailboxes.
+ * DEFAULT: No default name.
+ * - nowrap: (integer) Don't wrap the headers.
+ * DEFAULT: Headers are wrapped.
+ *
+ * @return string The headers in string format.
+ */
+ public function toString(array $opts = array())
+ {
+ $eol = empty($opts['canonical'])
+ ? $this->_eol
+ : "\r\n";
+ $text = '';
+
+ foreach ($this->toArray($opts) as $key => $val) {
+ if (!is_array($val)) {
+ $val = array($val);
+ }
+ foreach ($val as $entry) {
+ $text .= $key . ': ' . $entry . $eol;
+ }
+ }
+
+ return $text . $eol;
+ }
+
+ /**
+ * Generate the 'Received' header for the Web browser->Horde hop
+ * (attempts to conform to guidelines in RFC 5321 [4.4]).
+ *
+ * @param array $opts Additional opts:
+ * - dns: (Net_DNS2_Resolver) Use the DNS resolver object to lookup
+ * hostnames.
+ * DEFAULT: Use gethostbyaddr() function.
+ * - server: (string) Use this server name.
+ * DEFAULT: Auto-detect using current PHP values.
+ */
+ public function addReceivedHeader(array $opts = array())
+ {
+ $old_error = error_reporting(0);
+ if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
+ /* This indicates the user is connecting through a proxy. */
+ $remote_path = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
+ $remote_addr = $remote_path[0];
+ if (!empty($opts['dns'])) {
+ $remote = $remote_addr;
+ try {
+ if ($response = $opts['dns']->query($remote_addr, 'PTR')) {
+ foreach ($response->answer as $val) {
+ if (isset($val->ptrdname)) {
+ $remote = $val->ptrdname;
+ break;
+ }
+ }
+ }
+ } catch (Net_DNS2_Exception $e) {}
+ } else {
+ $remote = gethostbyaddr($remote_addr);
+ }
+ } else {
+ $remote_addr = $_SERVER['REMOTE_ADDR'];
+ if (empty($_SERVER['REMOTE_HOST'])) {
+ if (!empty($opts['dns'])) {
+ $remote = $remote_addr;
+ try {
+ if ($response = $opts['dns']->query($remote_addr, 'PTR')) {
+ foreach ($response->answer as $val) {
+ if (isset($val->ptrdname)) {
+ $remote = $val->ptrdname;
+ break;
+ }
+ }
+ }
+ } catch (Net_DNS2_Exception $e) {}
+ } else {
+ $remote = gethostbyaddr($remote_addr);
+ }
+ } else {
+ $remote = $_SERVER['REMOTE_HOST'];
+ }
+ }
+ error_reporting($old_error);
+
+ if (!empty($_SERVER['REMOTE_IDENT'])) {
+ $remote_ident = $_SERVER['REMOTE_IDENT'] . '@' . $remote . ' ';
+ } elseif ($remote != $_SERVER['REMOTE_ADDR']) {
+ $remote_ident = $remote . ' ';
+ } else {
+ $remote_ident = '';
+ }
+
+ if (!empty($opts['server'])) {
+ $server_name = $opts['server'];
+ } elseif (!empty($_SERVER['SERVER_NAME'])) {
+ $server_name = $_SERVER['SERVER_NAME'];
+ } elseif (!empty($_SERVER['HTTP_HOST'])) {
+ $server_name = $_SERVER['HTTP_HOST'];
+ } else {
+ $server_name = 'unknown';
+ }
+
+ $received = 'from ' . $remote . ' (' . $remote_ident .
+ '[' . $remote_addr . ']) ' .
+ 'by ' . $server_name . ' (Horde Framework) with HTTP; ' .
+ date('r');
+
+ $this->addHeader('Received', $received);
+ }
+
+ /**
+ * Generate the 'Message-ID' header.
+ */
+ public function addMessageIdHeader()
+ {
+ $this->addHeader('Message-ID', Horde_Mime::generateMessageId());
+ }
+
+ /**
+ * Generate the user agent description header.
+ */
+ public function addUserAgentHeader()
+ {
+ $this->addHeader('User-Agent', $this->getUserAgent());
+ }
+
+ /**
+ * Returns the user agent description header.
+ *
+ * @return string The user agent header.
+ */
+ public function getUserAgent()
+ {
+ if (is_null($this->_agent)) {
+ $this->_agent = 'Horde Application Framework 4';
+ }
+ return $this->_agent;
+ }
+
+ /**
+ * Explicitly sets the User-Agent string.
+ *
+ * @param string $agent The User-Agent string to use.
+ */
+ public function setUserAgent($agent)
+ {
+ $this->_agent = $agent;
+ }
+
+ /**
+ * Add a header to the header array.
+ *
+ * @param string $header The header name.
+ * @param string $value The header value (UTF-8).
+ * @param array $opts Additional options:
+ * - params: (array) MIME parameters for Content-Type or
+ * Content-Disposition.
+ * DEFAULT: None
+ * - sanity_check: (boolean) Do sanity-checking on header value?
+ * DEFAULT: false
+ */
+ public function addHeader($header, $value, array $opts = array())
+ {
+ $header = trim($header);
+ $lcHeader = Horde_String::lower($header);
+
+ if (!isset($this->_headers[$lcHeader])) {
+ $this->_headers[$lcHeader] = array(
+ 'h' => $header
+ );
+ }
+ $ptr = &$this->_headers[$lcHeader];
+
+ if (!empty($opts['sanity_check'])) {
+ $value = $this->_sanityCheck($value);
+ }
+
+ // Fields defined in RFC 2822 that contain address information
+ if (in_array($lcHeader, $this->addressFields())) {
+ $rfc822 = new Horde_Mail_Rfc822();
+ $addr_list = $rfc822->parseAddressList($value);
+
+ switch ($lcHeader) {
+ case 'bcc':
+ case 'cc':
+ case 'from':
+ case 'to':
+ /* Catch malformed undisclosed-recipients entries. */
+ if ((count($addr_list) == 1) &&
+ preg_match("/^\s*undisclosed-recipients:?\s*$/i", $addr_list[0]->bare_address)) {
+ $addr_list = new Horde_Mail_Rfc822_List('undisclosed-recipients:;');
+ }
+ break;
+ }
+ $value = strval($addr_list);
+ } else {
+ $value = Horde_Mime::decode($value);
+ }
+
+ if (isset($ptr['v'])) {
+ if (!is_array($ptr['v'])) {
+ $ptr['v'] = array($ptr['v']);
+ }
+ $ptr['v'][] = $value;
+ } else {
+ $ptr['v'] = $value;
+ }
+
+ if (!empty($opts['params'])) {
+ $ptr['p'] = $opts['params'];
+ }
+ }
+
+ /**
+ * Remove a header from the header array.
+ *
+ * @param string $header The header name.
+ */
+ public function removeHeader($header)
+ {
+ unset($this->_headers[Horde_String::lower(trim($header))]);
+ }
+
+ /**
+ * Replace a value of a header.
+ *
+ * @param string $header The header name.
+ * @param string $value The header value.
+ * @param array $opts Additional options:
+ * - params: (array) MIME parameters for Content-Type or
+ * Content-Disposition.
+ * DEFAULT: None
+ * - sanity_check: (boolean) Do sanity-checking on header value?
+ * DEFAULT: false
+ */
+ public function replaceHeader($header, $value, array $opts = array())
+ {
+ $this->removeHeader($header);
+ $this->addHeader($header, $value, $opts);
+ }
+
+ /**
+ * Attempts to return the header in the correct case.
+ *
+ * @param string $header The header to search for.
+ *
+ * @return string The value for the given header.
+ * If the header is not found, returns null.
+ */
+ public function getString($header)
+ {
+ $lcHeader = Horde_String::lower($header);
+ return (isset($this->_headers[$lcHeader]))
+ ? $this->_headers[$lcHeader]['h']
+ : null;
+ }
+
+ /**
+ * Attempt to return the value for a given header.
+ * The following header fields can only have 1 entry, so if duplicate
+ * entries exist, the first value will be used:
+ * * To, From, Cc, Bcc, Date, Sender, Reply-to, Message-ID, In-Reply-To,
+ * References, Subject (RFC 2822 [3.6])
+ * * All List Headers (RFC 2369 [3])
+ * The values are not MIME encoded.
+ *
+ * @param string $header The header to search for.
+ * @param integer $type The type of return:
+ * - VALUE_STRING: Returns a string representation of the entire header.
+ * - VALUE_BASE: Returns a string representation of the base value of
+ * the header. If this is not a header that allows
+ * parameters, this will be equivalent to VALUE_STRING.
+ * - VALUE_PARAMS: Returns the list of parameters for this header. If
+ * this is not a header that allows parameters, this
+ * will be an empty array.
+ *
+ * @return mixed The value for the given header.
+ * If the header is not found, returns null.
+ */
+ public function getValue($header, $type = self::VALUE_STRING)
+ {
+ $header = Horde_String::lower($header);
+
+ if (!isset($this->_headers[$header])) {
+ return null;
+ }
+
+ $ptr = &$this->_headers[$header];
+ if (is_array($ptr['v']) &&
+ in_array($header, $this->singleFields(true))) {
+ if (in_array($header, $this->addressFields())) {
+ $base = str_replace(';,', ';', implode(', ', $ptr['v']));
+ } else {
+ $base = $ptr['v'][0];
+ }
+ } else {
+ $base = $ptr['v'];
+ }
+ $params = isset($ptr['p']) ? $ptr['p'] : array();
+
+ switch ($type) {
+ case self::VALUE_BASE:
+ return $base;
+
+ case self::VALUE_PARAMS:
+ return $params;
+
+ case self::VALUE_STRING:
+ foreach ($params as $key => $val) {
+ $base .= '; ' . $key . '=' . $val;
+ }
+ return $base;
+ }
+ }
+
+ /**
+ * Returns the list of RFC defined header fields that contain address
+ * info.
+ *
+ * @return array The list of headers, in lowercase.
+ */
+ static public function addressFields()
+ {
+ return array(
+ 'from', 'to', 'cc', 'bcc', 'reply-to', 'resent-to', 'resent-cc',
+ 'resent-bcc', 'resent-from', 'sender'
+ );
+ }
+
+ /**
+ * Returns the list of RFC defined header fields that can only contain
+ * a single value.
+ *
+ * @param boolean $list Return list-related headers also?
+ *
+ * @return array The list of headers, in lowercase.
+ */
+ public function singleFields($list = true)
+ {
+ return $list
+ ? array_merge($this->_singleFields, array_keys($this->listHeaders()))
+ : $this->_singleFields;
+ }
+
+ /**
+ * Returns the list of RFC defined MIME header fields that may contain
+ * parameter info.
+ *
+ * @return array The list of headers, in lowercase.
+ */
+ static public function mimeParamFields()
+ {
+ return array('content-type', 'content-disposition');
+ }
+
+ /**
+ * Returns the list of valid mailing list headers.
+ *
+ * @return array The list of valid mailing list headers.
+ */
+ static public function listHeaders()
+ {
+ return array(
+ /* RFC 2369 */
+ 'list-help' => Horde_Mime_Translation::t("List-Help"),
+ 'list-unsubscribe' => Horde_Mime_Translation::t("List-Unsubscribe"),
+ 'list-subscribe' => Horde_Mime_Translation::t("List-Subscribe"),
+ 'list-owner' => Horde_Mime_Translation::t("List-Owner"),
+ 'list-post' => Horde_Mime_Translation::t("List-Post"),
+ 'list-archive' => Horde_Mime_Translation::t("List-Archive"),
+ /* RFC 2919 */
+ 'list-id' => Horde_Mime_Translation::t("List-Id")
+ );
+ }
+
+ /**
+ * Do any mailing list headers exist?
+ *
+ * @return boolean True if any mailing list headers exist.
+ */
+ public function listHeadersExist()
+ {
+ return (bool)count(array_intersect(array_keys($this->listHeaders()), array_keys($this->_headers)));
+ }
+
+ /**
+ * Sets a new string to use for EOLs.
+ *
+ * @param string $eol The string to use for EOLs.
+ */
+ public function setEOL($eol)
+ {
+ $this->_eol = $eol;
+ }
+
+ /**
+ * Get the string to use for EOLs.
+ *
+ * @return string The string to use for EOLs.
+ */
+ public function getEOL()
+ {
+ return $this->_eol;
+ }
+
+ /**
+ * Returns an address object for a header.
+ *
+ * @param string $field The header to return as an object.
+ *
+ * @return Horde_Mail_Rfc822_List The object for the requested field.
+ * Returns null if field doesn't exist.
+ */
+ public function getOb($field)
+ {
+ if (($value = $this->getValue($field)) === null) {
+ return null;
+ }
+
+ $rfc822 = new Horde_Mail_Rfc822();
+ return $rfc822->parseAddressList($value);
+ }
+
+ /**
+ * Perform sanity checking on a raw header (e.g. handle 8-bit characters).
+ *
+ * @param string $data The header data.
+ *
+ * @return string The cleaned header data.
+ */
+ protected function _sanityCheck($data)
+ {
+ $charset_test = array(
+ 'windows-1252',
+ self::$defaultCharset
+ );
+
+ if (!Horde_String::validUtf8($data)) {
+ /* Appears to be a PHP error with the internal String structure
+ * which prevents accurate manipulation of the string. Copying
+ * the data to a new variable fixes things. */
+ $data = substr($data, 0);
+
+ /* Assumption: broken charset in headers is generally either
+ * UTF-8 or ISO-8859-1/Windows-1252. Test these charsets
+ * first before using default charset. This may be a
+ * Western-centric approach, but it's better than nothing. */
+ foreach ($charset_test as $charset) {
+ $tmp = Horde_String::convertCharset($data, $charset, 'UTF-8');
+ if (Horde_String::validUtf8($tmp)) {
+ return $tmp;
+ }
+ }
+ }
+
+ return $data;
+ }
+
+ /* Static methods. */
+
+ /**
+ * Builds a Horde_Mime_Headers object from header text.
+ * This function can be called statically:
+ * $headers = Horde_Mime_Headers::parseHeaders().
+ *
+ * @param string $text A text string containing the headers.
+ *
+ * @return Horde_Mime_Headers A new Horde_Mime_Headers object.
+ */
+ static public function parseHeaders($text)
+ {
+ $currheader = $currtext = null;
+ $mime = self::mimeParamFields();
+ $to_process = array();
+
+ foreach (explode("\n", $text) as $val) {
+ $val = rtrim($val, "\r\n");
+ if (empty($val)) {
+ break;
+ }
+
+ if (($val[0] == ' ') || ($val[0] == "\t")) {
+ $currtext .= ' ' . ltrim($val);
+ } else {
+ if (!is_null($currheader)) {
+ $to_process[] = array($currheader, rtrim($currtext));
+ }
+
+ $pos = strpos($val, ':');
+ $currheader = substr($val, 0, $pos);
+ $currtext = ltrim(substr($val, $pos + 1));
+ }
+ }
+
+ if (!is_null($currheader)) {
+ $to_process[] = array($currheader, $currtext);
+ }
+
+ $headers = new Horde_Mime_Headers();
+
+ reset($to_process);
+ while (list(,$val) = each($to_process)) {
+ /* Ignore empty headers. */
+ if (!strlen($val[1])) {
+ continue;
+ }
+
+ if (in_array(Horde_String::lower($val[0]), $mime)) {
+ $res = Horde_Mime::decodeParam($val[0], $val[1]);
+ $headers->addHeader($val[0], $res['val'], array(
+ 'params' => $res['params'],
+ 'sanity_check' => true
+ ));
+ } else {
+ $headers->addHeader($val[0], $val[1], array(
+ 'sanity_check' => true
+ ));
+ }
+ }
+
+ return $headers;
+ }
+
+ /* Serializable methods. */
+
+ /**
+ * Serialization.
+ *
+ * @return string Serialized data.
+ */
+ public function serialize()
+ {
+ $data = array(
+ // Serialized data ID.
+ self::VERSION,
+ $this->_headers,
+ $this->_eol
+ );
+
+ if (!is_null($this->_agent)) {
+ $data[] = $this->_agent;
+ }
+
+ return serialize($data);
+ }
+
+ /**
+ * Unserialization.
+ *
+ * @param string $data Serialized data.
+ *
+ * @throws Exception
+ */
+ public function unserialize($data)
+ {
+ $data = @unserialize($data);
+ if (!is_array($data) ||
+ !isset($data[0]) ||
+ ($data[0] != self::VERSION)) {
+ throw new Horde_Mime_Exception('Cache version change');
+ }
+
+ $this->_headers = $data[1];
+ $this->_eol = $data[2];
+ if (isset($data[3])) {
+ $this->_agent = $data[3];
+ }
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeMimePartphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Mime-Part.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Mime-Part.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Mime-Part.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,2358 @@
</span><ins>+<?php
+/**
+ * This class provides an object-oriented representation of a MIME part
+ * (defined by RFC 2045).
+ *
+ * Copyright 1999-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @author Chuck Hagenbuch <chuck@horde.org>
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Mime
+ */
+class Horde_Mime_Part implements ArrayAccess, Countable, Serializable
+{
+ /* Serialized version. */
+ const VERSION = 1;
+
+ /* The character(s) used internally for EOLs. */
+ const EOL = "\n";
+
+ /* The character string designated by RFC 2045 to designate EOLs in MIME
+ * messages. */
+ const RFC_EOL = "\r\n";
+
+ /* The default encoding. */
+ const DEFAULT_ENCODING = 'binary';
+
+ /* Constants indicating the valid transfer encoding allowed. */
+ const ENCODE_7BIT = 1;
+ const ENCODE_8BIT = 2;
+ const ENCODE_BINARY = 4;
+
+ /* Unknown types. */
+ const UNKNOWN = 'x-unknown';
+
+ /* MIME nesting limit. */
+ const NESTING_LIMIT = 100;
+
+ /**
+ * The default charset to use when parsing text parts with no charset
+ * information.
+ *
+ * @var string
+ */
+ static public $defaultCharset = 'us-ascii';
+
+ /**
+ * Valid encoding types.
+ *
+ * @var array
+ */
+ static public $encodingTypes = array(
+ '7bit', '8bit', 'base64', 'binary', 'quoted-printable',
+ // Non-RFC types, but old mailers may still use
+ 'uuencode', 'x-uuencode', 'x-uue'
+ );
+
+ /**
+ * The memory limit for use with the PHP temp stream.
+ *
+ * @var integer
+ */
+ static public $memoryLimit = 2097152;
+
+ /**
+ * Valid MIME types.
+ *
+ * @var array
+ */
+ static public $mimeTypes = array(
+ 'text', 'multipart', 'message', 'application', 'audio', 'image',
+ 'video', 'model'
+ );
+
+ /**
+ * The type (ex.: text) of this part.
+ * Per RFC 2045, the default is 'application'.
+ *
+ * @var string
+ */
+ protected $_type = 'application';
+
+ /**
+ * The subtype (ex.: plain) of this part.
+ * Per RFC 2045, the default is 'octet-stream'.
+ *
+ * @var string
+ */
+ protected $_subtype = 'octet-stream';
+
+ /**
+ * The body of the part. Always stored in binary format.
+ *
+ * @var resource
+ */
+ protected $_contents;
+
+ /**
+ * The desired transfer encoding of this part.
+ *
+ * @var string
+ */
+ protected $_transferEncoding = self::DEFAULT_ENCODING;
+
+ /**
+ * The language(s) of this part.
+ *
+ * @var array
+ */
+ protected $_language = array();
+
+ /**
+ * The description of this part.
+ *
+ * @var string
+ */
+ protected $_description = '';
+
+ /**
+ * The disposition of this part (inline or attachment).
+ *
+ * @var string
+ */
+ protected $_disposition = '';
+
+ /**
+ * The disposition parameters of this part.
+ *
+ * @var array
+ */
+ protected $_dispParams = array();
+
+ /**
+ * The content type parameters of this part.
+ *
+ * @var Horde_Support_CaseInsensitiveArray
+ */
+ protected $_contentTypeParams;
+
+ /**
+ * The subparts of this part.
+ *
+ * @var array
+ */
+ protected $_parts = array();
+
+ /**
+ * The MIME ID of this part.
+ *
+ * @var string
+ */
+ protected $_mimeid = null;
+
+ /**
+ * The sequence to use as EOL for this part.
+ * The default is currently to output the EOL sequence internally as
+ * just "\n" instead of the canonical "\r\n" required in RFC 822 & 2045.
+ * To be RFC complaint, the full <CR><LF> EOL combination should be used
+ * when sending a message.
+ * It is not crucial here since the PHP/PEAR mailing functions will handle
+ * the EOL details.
+ *
+ * @var string
+ */
+ protected $_eol = self::EOL;
+
+ /**
+ * Internal temp array.
+ *
+ * @var array
+ */
+ protected $_temp = array();
+
+ /**
+ * Metadata.
+ *
+ * @var array
+ */
+ protected $_metadata = array();
+
+ /**
+ * Unique Horde_Mime_Part boundary string.
+ *
+ * @var string
+ */
+ protected $_boundary = null;
+
+ /**
+ * Default value for this Part's size.
+ *
+ * @var integer
+ */
+ protected $_bytes;
+
+ /**
+ * The content-ID for this part.
+ *
+ * @var string
+ */
+ protected $_contentid = null;
+
+ /**
+ * The duration of this part's media data (RFC 3803).
+ *
+ * @var integer
+ */
+ protected $_duration;
+
+ /**
+ * Do we need to reindex the current part?
+ *
+ * @var boolean
+ */
+ protected $_reindex = false;
+
+ /**
+ * Is this the base MIME part?
+ *
+ * @var boolean
+ */
+ protected $_basepart = false;
+
+ /**
+ * The charset to output the headers in.
+ *
+ * @var string
+ */
+ protected $_hdrCharset = null;
+
+ /**
+ * The list of member variables to serialize.
+ *
+ * @var array
+ */
+ protected $_serializedVars = array(
+ '_type',
+ '_subtype',
+ '_transferEncoding',
+ '_language',
+ '_description',
+ '_disposition',
+ '_dispParams',
+ '_contentTypeParams',
+ '_parts',
+ '_mimeid',
+ '_eol',
+ '_metadata',
+ '_boundary',
+ '_bytes',
+ '_contentid',
+ '_duration',
+ '_reindex',
+ '_basepart',
+ '_hdrCharset'
+ );
+
+ /**
+ * Constructor.
+ */
+ public function __construct()
+ {
+ $this->_init();
+ }
+
+ /**
+ * Initialization tasks.
+ */
+ protected function _init()
+ {
+ $this->_contentTypeParams = new Horde_Support_CaseInsensitiveArray();
+ }
+
+ /**
+ * Function to run on clone.
+ */
+ public function __clone()
+ {
+ reset($this->_parts);
+ while (list($k, $v) = each($this->_parts)) {
+ $this->_parts[$k] = clone $v;
+ }
+
+ $this->_contentTypeParams = clone $this->_contentTypeParams;
+ }
+
+ /**
+ * Set the content-disposition of this part.
+ *
+ * @param string $disposition The content-disposition to set ('inline',
+ * 'attachment', or an empty value).
+ */
+ public function setDisposition($disposition = null)
+ {
+ if (empty($disposition)) {
+ $this->_disposition = '';
+ } else {
+ $disposition = Horde_String::lower($disposition);
+ if (in_array($disposition, array('inline', 'attachment'))) {
+ $this->_disposition = $disposition;
+ }
+ }
+ }
+
+ /**
+ * Get the content-disposition of this part.
+ *
+ * @return string The part's content-disposition. An empty string means
+ * no desired disposition has been set for this part.
+ */
+ public function getDisposition()
+ {
+ return $this->_disposition;
+ }
+
+ /**
+ * Add a disposition parameter to this part.
+ *
+ * @param string $label The disposition parameter label.
+ * @param string $data The disposition parameter data.
+ */
+ public function setDispositionParameter($label, $data)
+ {
+ $this->_dispParams[$label] = $data;
+
+ switch ($label) {
+ case 'size':
+ // RFC 2183 [2.7] - size parameter
+ $this->_bytes = intval($data);
+ break;
+ }
+ }
+
+ /**
+ * Get a disposition parameter from this part.
+ *
+ * @param string $label The disposition parameter label.
+ *
+ * @return string The data requested.
+ * Returns null if $label is not set.
+ */
+ public function getDispositionParameter($label)
+ {
+ return (isset($this->_dispParams[$label]))
+ ? $this->_dispParams[$label]
+ : null;
+ }
+
+ /**
+ * Get all parameters from the Content-Disposition header.
+ *
+ * @return array An array of all the parameters
+ * Returns the empty array if no parameters set.
+ */
+ public function getAllDispositionParameters()
+ {
+ return $this->_dispParams;
+ }
+
+ /**
+ * Set the name of this part.
+ *
+ * @param string $name The name to set.
+ */
+ public function setName($name)
+ {
+ $this->setDispositionParameter('filename', $name);
+ $this->setContentTypeParameter('name', $name);
+ }
+
+ /**
+ * Get the name of this part.
+ *
+ * @param boolean $default If the name parameter doesn't exist, should we
+ * use the default name from the description
+ * parameter?
+ *
+ * @return string The name of the part.
+ */
+ public function getName($default = false)
+ {
+ if (!($name = $this->getDispositionParameter('filename')) &&
+ !($name = $this->getContentTypeParameter('name')) &&
+ $default) {
+ $name = preg_replace('|\W|', '_', $this->getDescription(false));
+ }
+
+ return $name;
+ }
+
+ /**
+ * Set the body contents of this part.
+ *
+ * @param mixed $contents The part body. Either a string or a stream
+ * resource, or an array containing both.
+ * @param array $options Additional options:
+ * - encoding: (string) The encoding of $contents.
+ * DEFAULT: Current transfer encoding value.
+ * - usestream: (boolean) If $contents is a stream, should we directly
+ * use that stream?
+ * DEFAULT: $contents copied to a new stream.
+ */
+ public function setContents($contents, $options = array())
+ {
+ $this->clearContents();
+ if (empty($options['encoding'])) {
+ $options['encoding'] = $this->_transferEncoding;
+ }
+
+ $fp = (empty($options['usestream']) || !is_resource($contents))
+ ? $this->_writeStream($contents)
+ : $contents;
+
+ $this->setTransferEncoding($options['encoding']);
+ $this->_contents = $this->_transferDecode($fp, $options['encoding']);
+ }
+
+ /**
+ * Add to the body contents of this part.
+ *
+ * @param mixed $contents The part body. Either a string or a stream
+ * resource, or an array containing both.
+ * - encoding: (string) The encoding of $contents.
+ * DEFAULT: Current transfer encoding value.
+ * - usestream: (boolean) If $contents is a stream, should we directly
+ * use that stream?
+ * DEFAULT: $contents copied to a new stream.
+ */
+ public function appendContents($contents, $options = array())
+ {
+ if (empty($this->_contents)) {
+ $this->setContents($contents, $options);
+ } else {
+ $fp = (empty($options['usestream']) || !is_resource($contents))
+ ? $this->_writeStream($contents)
+ : $contents;
+
+ $this->_writeStream((empty($options['encoding']) || ($options['encoding'] == $this->_transferEncoding)) ? $fp : $this->_transferDecode($fp, $options['encoding']), array('fp' => $this->_contents));
+ unset($this->_temp['sendTransferEncoding']);
+ }
+ }
+
+ /**
+ * Clears the body contents of this part.
+ */
+ public function clearContents()
+ {
+ if (!empty($this->_contents)) {
+ fclose($this->_contents);
+ $this->_contents = null;
+ unset($this->_temp['sendTransferEncoding']);
+ }
+ }
+
+ /**
+ * Return the body of the part.
+ *
+ * @param array $options Additional options:
+ * - canonical: (boolean) Returns the contents in strict RFC 822 &
+ * 2045 output - namely, all newlines end with the
+ * canonical <CR><LF> sequence.
+ * DEFAULT: No
+ * - stream: (boolean) Return the body as a stream resource.
+ * DEFAULT: No
+ *
+ * @return mixed The body text (string) of the part, null if there is no
+ * contents, and a stream resource if 'stream' is true.
+ */
+ public function getContents($options = array())
+ {
+ return empty($options['canonical'])
+ ? (empty($options['stream']) ? $this->_readStream($this->_contents) : $this->_contents)
+ : $this->replaceEOL($this->_contents, self::RFC_EOL, !empty($options['stream']));
+ }
+
+ /**
+ * Decodes the contents of the part to binary encoding.
+ *
+ * @param resource $fp A stream containing the data to decode.
+ * @param string $encoding The original file encoding.
+ *
+ * @return resource A new file resource with the decoded data.
+ */
+ protected function _transferDecode($fp, $encoding)
+ {
+ /* If the contents are empty, return now. */
+ fseek($fp, 0, SEEK_END);
+ if (ftell($fp)) {
+ switch ($encoding) {
+ case 'base64':
+ try {
+ return $this->_writeStream($fp, array(
+ 'error' => true,
+ 'filter' => array(
+ 'convert.base64-decode' => array()
+ )
+ ));
+ } catch (ErrorException $e) {}
+
+ rewind($fp);
+ return $this->_writeStream(base64_decode(stream_get_contents($fp)));
+
+ case 'quoted-printable':
+ try {
+ return $this->_writeStream($fp, array(
+ 'error' => true,
+ 'filter' => array(
+ 'convert.quoted-printable-decode' => array()
+ )
+ ));
+ } catch (ErrorException $e) {}
+
+ // Workaround for Horde Bug #8747
+ rewind($fp);
+ return $this->_writeStream(quoted_printable_decode(stream_get_contents($fp)));
+
+ case 'uuencode':
+ case 'x-uuencode':
+ case 'x-uue':
+ /* Support for uuencoded encoding - although not required by
+ * RFCs, some mailers may still encode this way. */
+ $res = Horde_Mime::uudecode($this->_readStream($fp));
+ return $this->_writeStream($res[0]['data']);
+ }
+ }
+
+ return $fp;
+ }
+
+ /**
+ * Encodes the contents of the part as necessary for transport.
+ *
+ * @param resource $fp A stream containing the data to encode.
+ * @param string $encoding The encoding to use.
+ * @param string $eol EOL string.
+ *
+ * @return resource A new file resource with the encoded data.
+ */
+ protected function _transferEncode($fp, $encoding, $eol)
+ {
+ $this->_temp['transferEncodeClose'] = true;
+
+ switch ($encoding) {
+ case 'base64':
+ /* Base64 Encoding: See RFC 2045, section 6.8 */
+ return $this->_writeStream($fp, array(
+ 'filter' => array(
+ 'convert.base64-encode' => array(
+ 'line-break-chars' => $eol,
+ 'line-length' => 76
+ )
+ )
+ ));
+
+ case 'quoted-printable':
+ /* Quoted-Printable Encoding: See RFC 2045, section 6.7 */
+ return $this->_writeStream($fp, array(
+ 'filter' => array(
+ 'convert.quoted-printable-encode' => array(
+ 'line-break-chars' => $eol,
+ 'line-length' => 76
+ )
+ )
+ ));
+
+ default:
+ $this->_temp['transferEncodeClose'] = false;
+ return $fp;
+ }
+ }
+
+ /**
+ * Set the MIME type of this part.
+ *
+ * @param string $type The MIME type to set (ex.: text/plain).
+ */
+ public function setType($type)
+ {
+ /* RFC 2045: Any entity with unrecognized encoding must be treated
+ * as if it has a Content-Type of "application/octet-stream"
+ * regardless of what the Content-Type field actually says. */
+ if (($this->_transferEncoding == self::UNKNOWN) ||
+ (strpos($type, '/') === false)) {
+ return;
+ }
+
+ list($this->_type, $this->_subtype) = explode('/', Horde_String::lower($type));
+
+ if (in_array($this->_type, self::$mimeTypes)) {
+ /* Set the boundary string for 'multipart/*' parts. */
+ if ($this->_type == 'multipart') {
+ if (!$this->getContentTypeParameter('boundary')) {
+ $this->setContentTypeParameter('boundary', $this->_generateBoundary());
+ }
+ } else {
+ $this->clearContentTypeParameter('boundary');
+ }
+ } else {
+ $this->_type = self::UNKNOWN;
+ $this->clearContentTypeParameter('boundary');
+ }
+ }
+
+ /**
+ * Get the full MIME Content-Type of this part.
+ *
+ * @param boolean $charset Append character set information to the end
+ * of the content type if this is a text/* part?
+ *`
+ * @return string The mimetype of this part (ex.: text/plain;
+ * charset=us-ascii) or false.
+ */
+ public function getType($charset = false)
+ {
+ if (empty($this->_type) || empty($this->_subtype)) {
+ return false;
+ }
+
+ $ptype = $this->getPrimaryType();
+ $type = $ptype . '/' . $this->getSubType();
+ if ($charset &&
+ ($ptype == 'text') &&
+ ($charset = $this->getCharset())) {
+ $type .= '; charset=' . $charset;
+ }
+
+ return $type;
+ }
+
+ /**
+ * If the subtype of a MIME part is unrecognized by an application, the
+ * default type should be used instead (See RFC 2046). This method
+ * returns the default subtype for a particular primary MIME type.
+ *
+ * @return string The default MIME type of this part (ex.: text/plain).
+ */
+ public function getDefaultType()
+ {
+ switch ($this->getPrimaryType()) {
+ case 'text':
+ /* RFC 2046 (4.1.4): text parts default to text/plain. */
+ return 'text/plain';
+
+ case 'multipart':
+ /* RFC 2046 (5.1.3): multipart parts default to multipart/mixed. */
+ return 'multipart/mixed';
+
+ default:
+ /* RFC 2046 (4.2, 4.3, 4.4, 4.5.3, 5.2.4): all others default to
+ application/octet-stream. */
+ return 'application/octet-stream';
+ }
+ }
+
+ /**
+ * Get the primary type of this part.
+ *
+ * @return string The primary MIME type of this part.
+ */
+ public function getPrimaryType()
+ {
+ return $this->_type;
+ }
+
+ /**
+ * Get the subtype of this part.
+ *
+ * @return string The MIME subtype of this part.
+ */
+ public function getSubType()
+ {
+ return $this->_subtype;
+ }
+
+ /**
+ * Set the character set of this part.
+ *
+ * @param string $charset The character set of this part.
+ */
+ public function setCharset($charset)
+ {
+ $this->setContentTypeParameter('charset', $charset);
+ }
+
+ /**
+ * Get the character set to use for this part.
+ *
+ * @return string The character set of this part. Returns null if there
+ * is no character set.
+ */
+ public function getCharset()
+ {
+ $charset = $this->getContentTypeParameter('charset');
+ if (is_null($charset) && $this->getPrimaryType() != 'text') {
+ return null;
+ }
+
+ $charset = Horde_String::lower($charset);
+
+ if ($this->getPrimaryType() == 'text') {
+ $d_charset = Horde_String::lower(self::$defaultCharset);
+ if ($d_charset != 'us-ascii' &&
+ (!$charset || $charset == 'us-ascii')) {
+ return $d_charset;
+ }
+ }
+
+ return $charset;
+ }
+
+ /**
+ * Set the character set to use when outputting MIME headers.
+ *
+ * @param string $charset The character set.
+ */
+ public function setHeaderCharset($charset)
+ {
+ $this->_hdrCharset = $charset;
+ }
+
+ /**
+ * Get the character set to use when outputting MIME headers.
+ *
+ * @return string The character set.
+ */
+ public function getHeaderCharset()
+ {
+ return is_null($this->_hdrCharset)
+ ? $this->getCharset()
+ : $this->_hdrCharset;
+ }
+
+ /**
+ * Set the language(s) of this part.
+ *
+ * @param mixed $lang A language string, or an array of language
+ * strings.
+ */
+ public function setLanguage($lang)
+ {
+ $this->_language = is_array($lang)
+ ? $lang
+ : array($lang);
+ }
+
+ /**
+ * Get the language(s) of this part.
+ *
+ * @param array The list of languages.
+ */
+ public function getLanguage()
+ {
+ return $this->_language;
+ }
+
+ /**
+ * Set the content duration of the data contained in this part (see RFC
+ * 3803).
+ *
+ * @param integer $duration The duration of the data, in seconds. If
+ * null, clears the duration information.
+ */
+ public function setDuration($duration)
+ {
+ if (is_null($duration)) {
+ unset($this->_duration);
+ } else {
+ $this->_duration = intval($duration);
+ }
+ }
+
+ /**
+ * Get the content duration of the data contained in this part (see RFC
+ * 3803).
+ *
+ * @return integer The duration of the data, in seconds. Returns null if
+ * there is no duration information.
+ */
+ public function getDuration()
+ {
+ return isset($this->_duration)
+ ? $this->_duration
+ : null;
+ }
+
+ /**
+ * Set the description of this part.
+ *
+ * @param string $description The description of this part.
+ */
+ public function setDescription($description)
+ {
+ $this->_description = $description;
+ }
+
+ /**
+ * Get the description of this part.
+ *
+ * @param boolean $default If the description parameter doesn't exist,
+ * should we use the name of the part?
+ *
+ * @return string The description of this part.
+ */
+ public function getDescription($default = false)
+ {
+ $desc = $this->_description;
+
+ if ($default && empty($desc)) {
+ $desc = $this->getName();
+ }
+
+ return $desc;
+ }
+
+ /**
+ * Set the transfer encoding to use for this part. Only needed in the
+ * following circumstances:
+ * 1.) Indicate what the transfer encoding is if the data has not yet been
+ * set in the object (can only be set if there presently are not
+ * any contents).
+ * 2.) Force the encoding to a certain type on a toString() call (if
+ * 'send' is true).
+ *
+ * @param string $encoding The transfer encoding to use.
+ * @param array $options Additional options:
+ * - send: (boolean) If true, use $encoding as the sending encoding.
+ * DEFAULT: $encoding is used to change the base encoding.
+ */
+ public function setTransferEncoding($encoding, $options = array())
+ {
+ if (empty($encoding) ||
+ (empty($options['send']) && !empty($this->_contents))) {
+ return;
+ }
+
+ $encoding = Horde_String::lower($encoding);
+
+ if (in_array($encoding, self::$encodingTypes)) {
+ if (empty($options['send'])) {
+ $this->_transferEncoding = $encoding;
+ } else {
+ $this->_temp['sendEncoding'] = $encoding;
+ }
+ } elseif (empty($options['send'])) {
+ /* RFC 2045: Any entity with unrecognized encoding must be treated
+ * as if it has a Content-Type of "application/octet-stream"
+ * regardless of what the Content-Type field actually says. */
+ $this->setType('application/octet-stream');
+ $this->_transferEncoding = self::UNKNOWN;
+ }
+ }
+
+ /**
+ * Add a MIME subpart.
+ *
+ * @param Horde_Mime_Part $mime_part Add a subpart to the current object.
+ */
+ public function addPart($mime_part)
+ {
+ $this->_parts[] = $mime_part;
+ $this->_reindex = true;
+ }
+
+ /**
+ * Get a list of all MIME subparts.
+ *
+ * @return array An array of the Horde_Mime_Part subparts.
+ */
+ public function getParts()
+ {
+ return $this->_parts;
+ }
+
+ /**
+ * Retrieve a specific MIME part.
+ *
+ * @param string $id The MIME ID to get.
+ *
+ * @return Horde_Mime_Part The part requested or null if the part doesn't
+ * exist.
+ */
+ public function getPart($id)
+ {
+ return $this->_partAction($id, 'get');
+ }
+
+ /**
+ * Remove a subpart.
+ *
+ * @param string $id The MIME ID to delete.
+ *
+ * @param boolean Success status.
+ */
+ public function removePart($id)
+ {
+ return $this->_partAction($id, 'remove');
+ }
+
+ /**
+ * Alter a current MIME subpart.
+ *
+ * @param string $id The MIME ID to alter.
+ * @param Horde_Mime_Part $mime_part The MIME part to store.
+ *
+ * @param boolean Success status.
+ */
+ public function alterPart($id, $mime_part)
+ {
+ return $this->_partAction($id, 'alter', $mime_part);
+ }
+
+ /**
+ * Function used to find a specific MIME part by ID and perform an action
+ * on it.
+ *
+ * @param string $id The MIME ID.
+ * @param string $action The action to perform ('get',
+ * 'remove', or 'alter').
+ * @param Horde_Mime_Part $mime_part The object to use for 'alter'.
+ *
+ * @return mixed See calling functions.
+ */
+ protected function _partAction($id, $action, $mime_part = null)
+ {
+ $this_id = $this->getMimeId();
+
+ /* Need strcmp() because, e.g., '2.0' == '2'. */
+ if (($action == 'get') && (strcmp($id, $this_id) === 0)) {
+ return $this;
+ }
+
+ if ($this->_reindex) {
+ $this->buildMimeIds(is_null($this_id) ? '1' : $this_id);
+ }
+
+ foreach (array_keys($this->_parts) as $val) {
+ $partid = $this->_parts[$val]->getMimeId();
+ if (strcmp($id, $partid) === 0) {
+ switch ($action) {
+ case 'alter':
+ $mime_part->setMimeId($this->_parts[$val]->getMimeId());
+ $this->_parts[$val] = $mime_part;
+ return true;
+
+ case 'get':
+ return $this->_parts[$val];
+
+ case 'remove':
+ unset($this->_parts[$val]);
+ $this->_reindex = true;
+ return true;
+ }
+ }
+
+ if ((strpos($id, $partid . '.') === 0) ||
+ (strrchr($partid, '.') === '.0')) {
+ return $this->_parts[$val]->_partAction($id, $action, $mime_part);
+ }
+ }
+
+ return ($action == 'get') ? null : false;
+ }
+
+ /**
+ * Add a content type parameter to this part.
+ *
+ * @param string $label The disposition parameter label.
+ * @param string $data The disposition parameter data.
+ */
+ public function setContentTypeParameter($label, $data)
+ {
+ $this->_contentTypeParams[$label] = $data;
+ }
+
+ /**
+ * Clears a content type parameter from this part.
+ *
+ * @param string $label The disposition parameter label.
+ * @param string $data The disposition parameter data.
+ */
+ public function clearContentTypeParameter($label)
+ {
+ unset($this->_contentTypeParams[$label]);
+ }
+
+ /**
+ * Get a content type parameter from this part.
+ *
+ * @param string $label The content type parameter label.
+ *
+ * @return string The data requested.
+ * Returns null if $label is not set.
+ */
+ public function getContentTypeParameter($label)
+ {
+ return isset($this->_contentTypeParams[$label])
+ ? $this->_contentTypeParams[$label]
+ : null;
+ }
+
+ /**
+ * Get all parameters from the Content-Type header.
+ *
+ * @return array An array of all the parameters
+ * Returns the empty array if no parameters set.
+ */
+ public function getAllContentTypeParameters()
+ {
+ return $this->_contentTypeParams->getArrayCopy();
+ }
+
+ /**
+ * Sets a new string to use for EOLs.
+ *
+ * @param string $eol The string to use for EOLs.
+ */
+ public function setEOL($eol)
+ {
+ $this->_eol = $eol;
+ }
+
+ /**
+ * Get the string to use for EOLs.
+ *
+ * @return string The string to use for EOLs.
+ */
+ public function getEOL()
+ {
+ return $this->_eol;
+ }
+
+ /**
+ * Returns a Horde_Mime_Header object containing all MIME headers needed
+ * for the part.
+ *
+ * @param array $options Additional options:
+ * - encode: (integer) A mask of allowable encodings.
+ * DEFAULT: See self::_getTransferEncoding()
+ * - headers: (Horde_Mime_Headers) The object to add the MIME headers
+ * to.
+ * DEFAULT: Add headers to a new object
+ *
+ * @return Horde_Mime_Headers A Horde_Mime_Headers object.
+ */
+ public function addMimeHeaders($options = array())
+ {
+ $headers = empty($options['headers'])
+ ? new Horde_Mime_Headers()
+ : $options['headers'];
+
+ /* Get the Content-Type itself. */
+ $ptype = $this->getPrimaryType();
+ $c_params = $this->getAllContentTypeParameters();
+ if ($ptype != 'text') {
+ unset($c_params['charset']);
+ }
+ $headers->replaceHeader('Content-Type', $this->getType(), array('params' => $c_params));
+
+ /* Add the language(s), if set. (RFC 3282 [2]) */
+ if ($langs = $this->getLanguage()) {
+ $headers->replaceHeader('Content-Language', implode(',', $langs));
+ }
+
+ /* Get the description, if any. */
+ if (($descrip = $this->getDescription())) {
+ $headers->replaceHeader('Content-Description', $descrip);
+ }
+
+ /* Set the duration, if it exists. (RFC 3803) */
+ if (($duration = $this->getDuration()) !== null) {
+ $headers->replaceHeader('Content-Duration', $duration);
+ }
+
+ /* Per RFC 2046 [4], this MUST appear in the base message headers. */
+ if ($this->_basepart) {
+ $headers->replaceHeader('MIME-Version', '1.0');
+ }
+
+ /* message/* parts require no additional header information. */
+ if ($ptype == 'message') {
+ return $headers;
+ }
+
+ /* Don't show Content-Disposition unless a disposition has explicitly
+ * been set or there are parameters.
+ * If there is a name, but no disposition, default to 'attachment'.
+ * RFC 2183 [2] indicates that default is no requested disposition -
+ * the receiving MUA is responsible for display choice. */
+ $disposition = $this->getDisposition();
+ $disp_params = $this->getAllDispositionParameters();
+ $name = $this->getName();
+ if ($disposition || !empty($name) || !empty($disp_params)) {
+ if (!$disposition) {
+ $disposition = 'attachment';
+ }
+ if ($name) {
+ $disp_params['filename'] = $name;
+ }
+ $headers->replaceHeader('Content-Disposition', $disposition, array('params' => $disp_params));
+ } else {
+ $headers->removeHeader('Content-Disposition');
+ }
+
+ /* Add transfer encoding information. RFC 2045 [6.1] indicates that
+ * default is 7bit. No need to send the header in this case. */
+ $encoding = $this->_getTransferEncoding(empty($options['encode']) ? null : $options['encode']);
+ if ($encoding == '7bit') {
+ $headers->removeHeader('Content-Transfer-Encoding');
+ } else {
+ $headers->replaceHeader('Content-Transfer-Encoding', $encoding);
+ }
+
+ /* Add content ID information. */
+ if (!is_null($this->_contentid)) {
+ $headers->replaceHeader('Content-ID', '<' . $this->_contentid . '>');
+ }
+
+ return $headers;
+ }
+
+ /**
+ * Return the entire part in MIME format.
+ *
+ * @param array $options Additional options:
+ * - canonical: (boolean) Returns the encoded part in strict RFC 822 &
+ * 2045 output - namely, all newlines end with the
+ * canonical <CR><LF> sequence.
+ * DEFAULT: false
+ * - defserver: (string) The default server to use when creating the
+ * header string.
+ * DEFAULT: none
+ * - encode: (integer) A mask of allowable encodings.
+ * DEFAULT: self::ENCODE_7BIT
+ * - headers: (mixed) Include the MIME headers? If true, create a new
+ * headers object. If a Horde_Mime_Headers object, add MIME
+ * headers to this object. If a string, use the string
+ * verbatim.
+ * DEFAULT: true
+ * - id: (string) Return only this MIME ID part.
+ * DEFAULT: Returns the base part.
+ * - stream: (boolean) Return a stream resource.
+ * DEFAULT: false
+ *
+ * @return mixed The MIME string (returned as a resource if $stream is
+ * true).
+ */
+ public function toString($options = array())
+ {
+ $eol = $this->getEOL();
+ $isbase = true;
+ $oldbaseptr = null;
+ $parts = $parts_close = array();
+
+ if (isset($options['id'])) {
+ $id = $options['id'];
+ if (!($part = $this->getPart($id))) {
+ return $part;
+ }
+ unset($options['id']);
+ $contents = $part->toString($options);
+
+ $prev_id = Horde_Mime::mimeIdArithmetic($id, 'up', array('norfc822' => true));
+ $prev_part = ($prev_id == $this->getMimeId())
+ ? $this
+ : $this->getPart($prev_id);
+ if (!$prev_part) {
+ return $contents;
+ }
+
+ $boundary = trim($this->getContentTypeParameter('boundary'), '"');
+ $parts = array(
+ $eol . '--' . $boundary . $eol,
+ $contents
+ );
+
+ if (!$this->getPart(Horde_Mime::mimeIdArithmetic($id, 'next'))) {
+ $parts[] = $eol . '--' . $boundary . '--' . $eol;
+ }
+ } else {
+ if ($isbase = empty($options['_notbase'])) {
+ $headers = !empty($options['headers'])
+ ? $options['headers']
+ : false;
+
+ if (empty($options['encode'])) {
+ $options['encode'] = null;
+ }
+ if (empty($options['defserver'])) {
+ $options['defserver'] = null;
+ }
+ $options['headers'] = true;
+ $options['_notbase'] = true;
+ } else {
+ $headers = true;
+ $oldbaseptr = &$options['_baseptr'];
+ }
+
+ $this->_temp['toString'] = '';
+ $options['_baseptr'] = &$this->_temp['toString'];
+
+ /* Any information about a message is embedded in the message
+ * contents themself. Simply output the contents of the part
+ * directly and return. */
+ $ptype = $this->getPrimaryType();
+ if ($ptype == 'message') {
+ $parts[] = $this->_contents;
+ } else {
+ if (!empty($this->_contents)) {
+ $encoding = $this->_getTransferEncoding($options['encode']);
+ switch ($encoding) {
+ case '8bit':
+ if (empty($options['_baseptr'])) {
+ $options['_baseptr'] = '8bit';
+ }
+ break;
+
+ case 'binary':
+ $options['_baseptr'] = 'binary';
+ break;
+ }
+
+ $parts[] = $this->_transferEncode(
+ $this->_contents,
+ $encoding,
+ (empty($options['canonical']) ? $this->getEOL() : self::RFC_EOL)
+ );
+
+ /* If not using $this->_contents, we can close the stream
+ * when finished. */
+ if ($this->_temp['transferEncodeClose']) {
+ $parts_close[] = end($parts);
+ }
+ }
+
+ /* Deal with multipart messages. */
+ if ($ptype == 'multipart') {
+ if (empty($this->_contents)) {
+ $parts[] = 'This message is in MIME format.' . $eol;
+ }
+
+ $boundary = trim($this->getContentTypeParameter('boundary'), '"');
+
+ reset($this->_parts);
+ while (list(,$part) = each($this->_parts)) {
+ $parts[] = $eol . '--' . $boundary . $eol;
+ $tmp = $part->toString($options);
+ if ($part->getEOL() != $eol) {
+ $tmp = $this->replaceEOL($tmp, $eol, !empty($options['stream']));
+ }
+ if (!empty($options['stream'])) {
+ $parts_close[] = $tmp;
+ }
+ $parts[] = $tmp;
+ }
+ $parts[] = $eol . '--' . $boundary . '--' . $eol;
+ }
+ }
+
+ if (is_string($headers)) {
+ array_unshift($parts, $headers);
+ } elseif ($headers) {
+ $hdr_ob = $this->addMimeHeaders(array('encode' => $options['encode'], 'headers' => ($headers === true) ? null : $headers));
+ $hdr_ob->setEOL($eol);
+ if (!empty($this->_temp['toString'])) {
+ $hdr_ob->replaceHeader('Content-Transfer-Encoding', $this->_temp['toString']);
+ }
+ array_unshift($parts, $hdr_ob->toString(array('charset' => $this->getHeaderCharset(), 'defserver' => $options['defserver'])));
+ }
+ }
+
+ $newfp = $this->_writeStream($parts);
+
+ array_map('fclose', $parts_close);
+
+ if (!is_null($oldbaseptr)) {
+ switch ($this->_temp['toString']) {
+ case '8bit':
+ if (empty($oldbaseptr)) {
+ $oldbaseptr = '8bit';
+ }
+ break;
+
+ case 'binary':
+ $oldbaseptr = 'binary';
+ break;
+ }
+ }
+
+ if ($isbase && !empty($options['canonical'])) {
+ return $this->replaceEOL($newfp, self::RFC_EOL, !empty($options['stream']));
+ }
+
+ return empty($options['stream'])
+ ? $this->_readStream($newfp)
+ : $newfp;
+ }
+
+ /**
+ * Get the transfer encoding for the part based on the user requested
+ * transfer encoding and the current contents of the part.
+ *
+ * @param integer $encode A mask of allowable encodings.
+ *
+ * @return string The transfer-encoding of this part.
+ */
+ protected function _getTransferEncoding($encode = self::ENCODE_7BIT)
+ {
+ if (!empty($this->_temp['sendEncoding'])) {
+ return $this->_temp['sendEncoding'];
+ } elseif (!empty($this->_temp['sendTransferEncoding'][$encode])) {
+ return $this->_temp['sendTransferEncoding'][$encode];
+ }
+
+ if (empty($this->_contents)) {
+ $encoding = '7bit';
+ } else {
+ $nobinary = false;
+
+ switch ($this->getPrimaryType()) {
+ case 'message':
+ case 'multipart':
+ /* RFC 2046 [5.2.1] - message/rfc822 messages only allow 7bit,
+ * 8bit, and binary encodings. If the current encoding is
+ * either base64 or q-p, switch it to 8bit instead.
+ * RFC 2046 [5.2.2, 5.2.3, 5.2.4] - All other message/*
+ * messages only allow 7bit encodings.
+ *
+ * TODO: What if message contains 8bit characters and we are
+ * in strict 7bit mode? Not sure there is anything we can do
+ * in that situation, especially for message/rfc822 parts.
+ *
+ * These encoding will be figured out later (via toString()).
+ * They are limited to 7bit, 8bit, and binary. Default to
+ * '7bit' per RFCs. */
+ $encoding = '7bit';
+ $nobinary = true;
+ break;
+
+ case 'text':
+ $eol = $this->getEOL();
+
+ if ($this->_scanStream($this->_contents, '8bit')) {
+ $encoding = ($encode & self::ENCODE_8BIT || $encode & self::ENCODE_BINARY)
+ ? '8bit'
+ : 'quoted-printable';
+ } elseif ($this->_scanStream($this->_contents, 'preg', "/(?:" . $eol . "|^)[^" . $eol . "]{999,}(?:" . $eol . "|$)/")) {
+ /* If the text is longer than 998 characters between
+ * linebreaks, use quoted-printable encoding to ensure the
+ * text will not be chopped (i.e. by sendmail if being
+ * sent as mail text). */
+ $encoding = 'quoted-printable';
+ } else {
+ $encoding = '7bit';
+ }
+ break;
+
+ default:
+ /* If transfer encoding has changed from the default, use that
+ * value. */
+ if ($this->_transferEncoding != self::DEFAULT_ENCODING) {
+ $encoding = $this->_transferEncoding;
+ } else {
+ $encoding = ($encode & self::ENCODE_8BIT || $encode & self::ENCODE_BINARY)
+ ? '8bit'
+ : 'base64';
+ }
+ break;
+ }
+
+ /* Need to do one last check for binary data if encoding is 7bit
+ * or 8bit. If the message contains a NULL character at all, the
+ * message MUST be in binary format. RFC 2046 [2.7, 2.8, 2.9]. Q-P
+ * and base64 can handle binary data fine so no need to switch
+ * those encodings. */
+ if (!$nobinary &&
+ in_array($encoding, array('8bit', '7bit')) &&
+ $this->_scanStream($this->_contents, 'binary')) {
+ $encoding = ($encode & self::ENCODE_BINARY)
+ ? 'binary'
+ : 'base64';
+ }
+ }
+
+ $this->_temp['sendTransferEncoding'][$encode] = $encoding;
+
+ return $encoding;
+ }
+
+ /**
+ * Replace newlines in this part's contents with those specified by either
+ * the given newline sequence or the part's current EOL setting.
+ *
+ * @param mixed $text The text to replace. Either a string or a
+ * stream resource. If a stream, and returning
+ * a string, will close the stream when done.
+ * @param string $eol The EOL sequence to use. If not present, uses
+ * the part's current EOL setting.
+ * @param boolean $stream If true, returns a stream resource.
+ *
+ * @return string The text with the newlines replaced by the desired
+ * newline sequence (returned as a stream resource if
+ * $stream is true).
+ */
+ public function replaceEOL($text, $eol = null, $stream = false)
+ {
+ if (is_null($eol)) {
+ $eol = $this->getEOL();
+ }
+
+ $fp = $this->_writeStream($text);
+
+ stream_filter_register('horde_eol', 'Horde_Stream_Filter_Eol');
+ stream_filter_append($fp, 'horde_eol', STREAM_FILTER_READ, array('eol' => $eol));
+
+ return $stream ? $fp : $this->_readStream($fp, true);
+ }
+
+ /**
+ * Determine the size of this MIME part and its child members.
+ *
+ * @param boolean $approx If true, determines an approximate size for
+ * parts consisting of base64 encoded data.
+ *
+ * @return integer Size of the part, in bytes.
+ */
+ public function getBytes($approx = false)
+ {
+ $bytes = 0;
+
+ if (isset($this->_bytes)) {
+ $bytes = $this->_bytes;
+
+ /* Base64 transfer encoding is approx. 33% larger than original
+ * data size (RFC 2045 [6.8]). */
+ if ($approx && ($this->_transferEncoding == 'base64')) {
+ $bytes *= 0.75;
+ }
+ } elseif ($this->getPrimaryType() == 'multipart') {
+ reset($this->_parts);
+ while (list(,$part) = each($this->_parts)) {
+ $bytes += $part->getBytes($approx);
+ }
+ } elseif ($this->_contents) {
+ fseek($this->_contents, 0, SEEK_END);
+ $bytes = ftell($this->_contents);
+
+ /* Base64 transfer encoding is approx. 33% larger than original
+ * data size (RFC 2045 [6.8]). */
+ if ($approx && ($this->_transferEncoding == 'base64')) {
+ $bytes *= 0.75;
+ }
+ }
+
+ return $bytes;
+ }
+
+ /**
+ * Explicitly set the size (in bytes) of this part. This value will only
+ * be returned (via getBytes()) if there are no contents currently set.
+ * This function is useful for setting the size of the part when the
+ * contents of the part are not fully loaded (i.e. creating a
+ * Horde_Mime_Part object from IMAP header information without loading the
+ * data of the part).
+ *
+ * @param integer $bytes The size of this part in bytes.
+ */
+ public function setBytes($bytes)
+ {
+ $this->setDispositionParameter('size', $bytes);
+ }
+
+ /**
+ * Output the size of this MIME part in KB.
+ *
+ * @param boolean $approx If true, determines an approximate size for
+ * parts consisting of base64 encoded data.
+ *
+ * @return string Size of the part in KB.
+ */
+ public function getSize($approx = false)
+ {
+ if (!($bytes = $this->getBytes($approx))) {
+ return 0;
+ }
+
+ $localeinfo = Horde_Nls::getLocaleInfo();
+
+ // TODO: Workaround broken number_format() prior to PHP 5.4.0.
+ return str_replace(
+ array('X', 'Y'),
+ array($localeinfo['decimal_point'], $localeinfo['thousands_sep']),
+ number_format(ceil($bytes / 1024), 0, 'X', 'Y')
+ );
+ }
+
+ /**
+ * Sets the Content-ID header for this part.
+ *
+ * @param string $cid Use this CID (if not already set). Else, generate
+ * a random CID.
+ *
+ * @return string The Content-ID for this part.
+ */
+ public function setContentId($cid = null)
+ {
+ if (is_null($this->_contentid)) {
+ $this->_contentid = is_null($cid)
+ ? (strval(new Horde_Support_Randomid()) . '@' . $_SERVER['SERVER_NAME'])
+ : trim($cid, '<>');
+ }
+
+ return $this->_contentid;
+ }
+
+ /**
+ * Returns the Content-ID for this part.
+ *
+ * @return string The Content-ID for this part.
+ */
+ public function getContentId()
+ {
+ return $this->_contentid;
+ }
+
+ /**
+ * Alter the MIME ID of this part.
+ *
+ * @param string $mimeid The MIME ID.
+ */
+ public function setMimeId($mimeid)
+ {
+ $this->_mimeid = $mimeid;
+ }
+
+ /**
+ * Returns the MIME ID of this part.
+ *
+ * @return string The MIME ID.
+ */
+ public function getMimeId()
+ {
+ return $this->_mimeid;
+ }
+
+ /**
+ * Build the MIME IDs for this part and all subparts.
+ *
+ * @param string $id The ID of this part.
+ * @param boolean $rfc822 Is this a message/rfc822 part?
+ */
+ public function buildMimeIds($id = null, $rfc822 = false)
+ {
+ if (is_null($id)) {
+ $rfc822 = true;
+ $id = '';
+ }
+
+ if ($rfc822) {
+ if (empty($this->_parts)) {
+ $this->setMimeId($id . '1');
+ } else {
+ if (empty($id) && ($this->getType() == 'message/rfc822')) {
+ $this->setMimeId('1');
+ $id = '1.';
+ } else {
+ $this->setMimeId($id . '0');
+ }
+ $i = 1;
+ foreach (array_keys($this->_parts) as $val) {
+ $this->_parts[$val]->buildMimeIds($id . ($i++));
+ }
+ }
+ } else {
+ $this->setMimeId($id);
+ $id = $id
+ ? $id . '.'
+ : '';
+
+ if ($this->getType() == 'message/rfc822') {
+ if (count($this->_parts)) {
+ reset($this->_parts);
+ $this->_parts[key($this->_parts)]->buildMimeIds($id, true);
+ }
+ } elseif (!empty($this->_parts)) {
+ $i = 1;
+ foreach (array_keys($this->_parts) as $val) {
+ $this->_parts[$val]->buildMimeIds($id . ($i++));
+ }
+ }
+ }
+
+ $this->_reindex = false;
+ }
+
+ /**
+ * Generate the unique boundary string (if not already done).
+ *
+ * @return string The boundary string.
+ */
+ protected function _generateBoundary()
+ {
+ if (is_null($this->_boundary)) {
+ $this->_boundary = '=_' . strval(new Horde_Support_Randomid());
+ }
+ return $this->_boundary;
+ }
+
+ /**
+ * Returns a mapping of all MIME IDs to their content-types.
+ *
+ * @param boolean $sort Sort by MIME ID?
+ *
+ * @return array Keys: MIME ID; values: content type.
+ */
+ public function contentTypeMap($sort = true)
+ {
+ $map = array($this->getMimeId() => $this->getType());
+ foreach ($this->_parts as $val) {
+ $map += $val->contentTypeMap(false);
+ }
+
+ if ($sort) {
+ uksort($map, 'strnatcmp');
+ }
+
+ return $map;
+ }
+
+ /**
+ * Is this the base MIME part?
+ *
+ * @param boolean $base True if this is the base MIME part.
+ */
+ public function isBasePart($base)
+ {
+ $this->_basepart = $base;
+ }
+
+ /**
+ * Set a piece of metadata on this object.
+ *
+ * @param string $key The metadata key.
+ * @param mixed $data The metadata. If null, clears the key.
+ */
+ public function setMetadata($key, $data = null)
+ {
+ if (is_null($data)) {
+ unset($this->_metadata[$key]);
+ } else {
+ $this->_metadata[$key] = $data;
+ }
+ }
+
+ /**
+ * Retrieves metadata from this object.
+ *
+ * @param string $key The metadata key.
+ *
+ * @return mixed The metadata, or null if it doesn't exist.
+ */
+ public function getMetadata($key)
+ {
+ return isset($this->_metadata[$key])
+ ? $this->_metadata[$key]
+ : null;
+ }
+
+ /**
+ * Sends this message.
+ *
+ * @param string $email The address list to send to.
+ * @param Horde_Mime_Headers $headers The Horde_Mime_Headers object
+ * holding this message's headers.
+ * @param Horde_Mail_Transport $mailer A Horde_Mail_Transport object.
+ * @param array $opts Additional options:
+ * - encode: (integer) The encoding to use. A mask of self::ENCODE_*
+ * values.
+ * DEFAULT: Auto-determined based on transport driver.
+ *
+ * @throws Horde_Mime_Exception
+ * @throws InvalidArgumentException
+ */
+ public function send($email, $headers, Horde_Mail_Transport $mailer,
+ array $opts = array())
+ {
+ $old_basepart = $this->_basepart;
+ $this->_basepart = true;
+
+ /* Does the SMTP backend support 8BITMIME (RFC 1652) or
+ * BINARYMIME (RFC 3030) extensions? Requires Net_SMTP version
+ * 1.3+. */
+ $encode = self::ENCODE_7BIT;
+ if (isset($opts['encode'])) {
+ /* Always allow 7bit encoding. */
+ $encode |= $opts['encode'];
+ } elseif ($mailer instanceof Horde_Mail_Transport_Smtp) {
+ try {
+ $smtp_ext = $mailer->getSMTPObject()->getServiceExtensions();
+ if (isset($smtp_ext['8BITMIME'])) {
+ $encode |= self::ENCODE_8BIT;
+ }
+ if (isset($smtp_ext['BINARYMIME'])) {
+ $encode |= self::ENCODE_BINARY;
+ }
+ } catch (Horde_Mail_Exception $e) {}
+ }
+
+ $msg = $this->toString(array(
+ 'canonical' => true,
+ 'encode' => $encode,
+ 'headers' => false,
+ 'stream' => true
+ ));
+
+ /* Make sure the message has a trailing newline. */
+ fseek($msg, -1, SEEK_END);
+ switch (fgetc($msg)) {
+ case "\r":
+ if (fgetc($msg) != "\n") {
+ fputs($msg, "\n");
+ }
+ break;
+
+ default:
+ fputs($msg, "\r\n");
+ break;
+ }
+ rewind($msg);
+
+ /* Add MIME Headers if they don't already exist. */
+ if (!$headers->getValue('MIME-Version')) {
+ $headers = $this->addMimeHeaders(array('encode' => $encode, 'headers' => $headers));
+ }
+
+ if (!empty($this->_temp['toString'])) {
+ $headers->replaceHeader('Content-Transfer-Encoding', $this->_temp['toString']);
+ switch ($this->_temp['toString']) {
+ case 'binary':
+ $mailer->addServiceExtensionParameter('BODY', 'BINARYMIME');
+ break;
+
+ case '8bit':
+ $mailer->addServiceExtensionParameter('BODY', '8BITMIME');
+ break;
+ }
+ }
+
+ $this->_basepart = $old_basepart;
+ $rfc822 = new Horde_Mail_Rfc822();
+ try {
+ $mailer->send($rfc822->parseAddressList($email)->writeAddress(array(
+ 'encode' => $this->getHeaderCharset(),
+ 'idn' => true
+ )), $headers->toArray(array(
+ 'canonical' => true,
+ 'charset' => $this->getHeaderCharset()
+ )), $msg);
+ } catch (Horde_Mail_Exception $e) {
+ throw new Horde_Mime_Exception($e);
+ }
+ }
+
+ /**
+ * Finds the main "body" text part (if any) in a message.
+ * "Body" data is the first text part under this part.
+ *
+ * @param string $subtype Specifically search for this subtype.
+ *
+ * @return mixed The MIME ID of the main body part, or null if a body
+ * part is not found.
+ */
+ public function findBody($subtype = null)
+ {
+ $initial_id = $this->getMimeId();
+ $this->buildMimeIds();
+
+ foreach ($this->contentTypeMap() as $mime_id => $mime_type) {
+ if ((strpos($mime_type, 'text/') === 0) &&
+ (!$initial_id || (intval($mime_id) == 1)) &&
+ (is_null($subtype) || (substr($mime_type, 5) == $subtype)) &&
+ ($part = $this->getPart($mime_id)) &&
+ ($part->getDisposition() != 'attachment')) {
+ return $mime_id;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Write data to a stream.
+ *
+ * @param array $data The data to write. Either a stream resource or
+ * a string.
+ * @param array $options Additional options:
+ * - error: (boolean) Catch errors when writing to the stream. Throw an
+ * ErrorException if an error is found.
+ * DEFAULT: false
+ * - filter: (array) Filter(s) to apply to the string. Keys are the
+ * filter names, values are filter params.
+ * - fp: (resource) Use this stream instead of creating a new one.
+ *
+ * @return resource The stream resource.
+ * @throws ErrorException
+ */
+ protected function _writeStream($data, $options = array())
+ {
+ if (empty($options['fp'])) {
+ $fp = fopen('php://temp/maxmemory:' . self::$memoryLimit, 'r+');
+ } else {
+ $fp = $options['fp'];
+ fseek($fp, 0, SEEK_END);
+ }
+
+ if (!is_array($data)) {
+ $data = array($data);
+ }
+
+ if (!empty($options['filter'])) {
+ $append_filter = array();
+ foreach ($options['filter'] as $key => $val) {
+ $append_filter[] = stream_filter_append($fp, $key, STREAM_FILTER_WRITE, $val);
+ }
+ }
+
+ if (!empty($options['error'])) {
+ set_error_handler(array($this, '_writeStreamErrorHandler'));
+ $error = null;
+ }
+
+ try {
+ reset($data);
+ while (list(,$d) = each($data)) {
+ if (is_resource($d)) {
+ rewind($d);
+ while (!feof($d)) {
+ fwrite($fp, fread($d, 8192));
+ }
+ } else {
+ $len = strlen($d);
+ $i = 0;
+ while ($i < $len) {
+ fwrite($fp, substr($d, $i, 8192));
+ $i += 8192;
+ }
+ }
+ }
+ } catch (ErrorException $e) {
+ $error = $e;
+ }
+
+ if (!empty($options['filter'])) {
+ foreach ($append_filter as $val) {
+ stream_filter_remove($val);
+ }
+ }
+
+ if (!empty($options['error'])) {
+ restore_error_handler();
+ if ($error) {
+ throw $error;
+ }
+ }
+
+ return $fp;
+ }
+
+ /**
+ * Error handler for _writeStream().
+ *
+ * @param integer $errno Error code.
+ * @param string $errstr Error text.
+ *
+ * @throws ErrorException
+ */
+ protected function _writeStreamErrorHandler($errno, $errstr)
+ {
+ throw new ErrorException($errstr, $errno);
+ }
+
+ /**
+ * Read data from a stream.
+ *
+ * @param resource $fp An active stream.
+ * @param boolean $close Close the stream when done reading?
+ *
+ * @return string The data from the stream.
+ */
+ protected function _readStream($fp, $close = false)
+ {
+ $out = '';
+
+ if (!is_resource($fp)) {
+ return $out;
+ }
+
+ rewind($fp);
+ while (!feof($fp)) {
+ $out .= fread($fp, 8192);
+ }
+
+ if ($close) {
+ fclose($fp);
+ }
+
+ return $out;
+ }
+
+ /**
+ * Scans a stream for the requested data.
+ *
+ * @param resource $fp A stream resource.
+ * @param string $type Either '8bit', 'binary', or 'preg'.
+ * @param mixed $data Any additional data needed to do the scan.
+ *
+ * @param boolean The result of the scan.
+ */
+ protected function _scanStream($fp, $type, $data = null)
+ {
+ rewind($fp);
+ while (is_resource($fp) && !feof($fp)) {
+ $line = fread($fp, 8192);
+ switch ($type) {
+ case '8bit':
+ if (Horde_Mime::is8bit($line)) {
+ return true;
+ }
+ break;
+
+ case 'binary':
+ if (strpos($line, "\0") !== false) {
+ return true;
+ }
+ break;
+
+ case 'preg':
+ if (preg_match($data, $line)) {
+ return true;
+ }
+ break;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Attempts to build a Horde_Mime_Part object from message text.
+ * This function can be called statically via:
+ * $mime_part = Horde_Mime_Part::parseMessage();
+ *
+ * @param string $text The text of the MIME message.
+ * @param array $opts Additional options:
+ * - forcemime: (boolean) If true, the message data is assumed to be
+ * MIME data. If not, a MIME-Version header must exist (RFC
+ * 2045 [4]) to be parsed as a MIME message.
+ * DEFAULT: false
+ * - level: (integer) Current nesting level of the MIME data.
+ * DEFAULT: 0
+ * - no_body: (boolean) If true, don't set body contents of parts (since
+ * 2.2.0).
+ * DEFAULT: false
+ *
+ * @return Horde_Mime_Part A MIME Part object.
+ * @throws Horde_Mime_Exception
+ */
+ static public function parseMessage($text, array $opts = array())
+ {
+ /* Find the header. */
+ list($hdr_pos, $eol) = self::_findHeader($text);
+
+ unset($opts['ctype']);
+ $ob = self::_getStructure(substr($text, 0, $hdr_pos), substr($text, $hdr_pos + $eol), $opts);
+ $ob->buildMimeIds();
+ return $ob;
+ }
+
+ /**
+ * Creates a MIME object from the text of one part of a MIME message.
+ *
+ * @param string $header The header text.
+ * @param string $body The body text.
+ * @param array $opts Additional options:
+ * <pre>
+ * - ctype: (string) The default content-type.
+ * - forcemime: (boolean) If true, the message data is assumed to be
+ * MIME data. If not, a MIME-Version header must exist to
+ * be parsed as a MIME message.
+ * - level: (integer) Current nesting level.
+ * - no_body: (boolean) If true, don't set body contents of parts.
+ * </pre>
+ *
+ * @return Horde_Mime_Part The MIME part object.
+ */
+ static protected function _getStructure($header, $body,
+ array $opts = array())
+ {
+ $opts = array_merge(array(
+ 'ctype' => 'application/octet-stream',
+ 'forcemime' => false,
+ 'level' => 0,
+ 'no_body' => false
+ ), $opts);
+
+ /* Parse headers text into a Horde_Mime_Headers object. */
+ $hdrs = Horde_Mime_Headers::parseHeaders($header);
+
+ $ob = new Horde_Mime_Part();
+
+ /* This is not a MIME message. */
+ if (!$opts['forcemime'] && !$hdrs->getValue('mime-version')) {
+ $ob->setType('text/plain');
+
+ if (!$opts['no_body'] && !empty($body)) {
+ $ob->setContents($body);
+ }
+
+ return $ob;
+ }
+
+ /* Content type. */
+ if ($tmp = $hdrs->getValue('content-type', Horde_Mime_Headers::VALUE_BASE)) {
+ $ob->setType($tmp);
+
+ $ctype_params = $hdrs->getValue('content-type', Horde_Mime_Headers::VALUE_PARAMS);
+ foreach ($ctype_params as $key => $val) {
+ $ob->setContentTypeParameter($key, $val);
+ }
+ } else {
+ $ob->setType($opts['ctype']);
+ }
+
+ /* Content transfer encoding. */
+ if ($tmp = $hdrs->getValue('content-transfer-encoding')) {
+ $ob->setTransferEncoding($tmp);
+ }
+
+ /* Content-Description. */
+ if ($tmp = $hdrs->getValue('content-description')) {
+ $ob->setDescription($tmp);
+ }
+
+ /* Content-Disposition. */
+ if ($tmp = $hdrs->getValue('content-disposition', Horde_Mime_Headers::VALUE_BASE)) {
+ $ob->setDisposition($tmp);
+ foreach ($hdrs->getValue('content-disposition', Horde_Mime_Headers::VALUE_PARAMS) as $key => $val) {
+ $ob->setDispositionParameter($key, $val);
+ }
+ }
+
+ /* Content-Duration */
+ if ($tmp = $hdrs->getValue('content-duration')) {
+ $ob->setDuration($tmp);
+ }
+
+ /* Content-ID. */
+ if ($tmp = $hdrs->getValue('content-id')) {
+ $ob->setContentId($tmp);
+ }
+
+ if (!$opts['no_body'] &&
+ !empty($body) &&
+ ($ob->getPrimaryType() != 'multipart')) {
+ $ob->setContents($body);
+ }
+
+ if (++$opts['level'] >= self::NESTING_LIMIT) {
+ return $ob;
+ }
+
+ /* Process subparts. */
+ switch ($ob->getPrimaryType()) {
+ case 'message':
+ if ($ob->getSubType() == 'rfc822') {
+ $ob->addPart(self::parseMessage($body, array('forcemime' => true)));
+ }
+ break;
+
+ case 'multipart':
+ $boundary = $ob->getContentTypeParameter('boundary');
+ if (!is_null($boundary)) {
+ foreach (self::_findBoundary($body, 0, $boundary) as $val) {
+ $subpart = substr($body, $val['start'], $val['length']);
+ list($hdr_pos, $eol) = self::_findHeader($subpart);
+ $ob->addPart(self::_getStructure(substr($subpart, 0, $hdr_pos), substr($subpart, $hdr_pos + $eol), array(
+ 'ctype' => ($ob->getSubType() == 'digest') ? 'message/rfc822' : 'text/plain',
+ 'forcemime' => true,
+ 'level' => $opts['level'],
+ 'no_body' => $opts['no_body']
+ )));
+ }
+ }
+ break;
+ }
+
+ return $ob;
+ }
+
+ /**
+ * Attempts to obtain the raw text of a MIME part.
+ * This function can be called statically via:
+ * $data = Horde_Mime_Part::getRawPartText();
+ *
+ * @param mixed $text The full text of the MIME message. The text is
+ * assumed to be MIME data (no MIME-Version checking
+ * is performed). It can be either a stream or a
+ * string.
+ * @param string $type Either 'header' or 'body'.
+ * @param string $id The MIME ID.
+ *
+ * @return string The raw text.
+ * @throws Horde_Mime_Exception
+ */
+ static public function getRawPartText($text, $type, $id)
+ {
+ /* Mini-hack to get a blank Horde_Mime part so we can call
+ * replaceEOL(). From an API perspective, getRawPartText() should be
+ * static since it is not working on MIME part data. */
+ $part = new Horde_Mime_Part();
+ $rawtext = $part->replaceEOL($text, self::RFC_EOL);
+
+ /* We need to carry around the trailing "\n" because this is needed
+ * to correctly find the boundary string. */
+ list($hdr_pos, $eol) = self::_findHeader($rawtext);
+ $curr_pos = $hdr_pos + $eol - 1;
+
+ if ($id == 0) {
+ switch ($type) {
+ case 'body':
+ return substr($rawtext, $curr_pos + 1);
+
+ case 'header':
+ return trim(substr($rawtext, 0, $hdr_pos));
+ }
+ }
+
+ $hdr_ob = Horde_Mime_Headers::parseHeaders(trim(substr($rawtext, 0, $hdr_pos)));
+
+ /* If this is a message/rfc822, pass the body into the next loop.
+ * Don't decrement the ID here. */
+ if ($hdr_ob->getValue('Content-Type', Horde_Mime_Headers::VALUE_BASE) == 'message/rfc822') {
+ return self::getRawPartText(substr($rawtext, $curr_pos + 1), $type, $id);
+ }
+
+ $base_pos = strpos($id, '.');
+ $orig_id = $id;
+
+ if ($base_pos !== false) {
+ $base_pos = substr($id, 0, $base_pos);
+ $id = substr($id, $base_pos);
+ } else {
+ $base_pos = $id;
+ $id = 0;
+ }
+
+ $params = $hdr_ob->getValue('Content-Type', Horde_Mime_Headers::VALUE_PARAMS);
+ if (!isset($params['boundary'])) {
+ if ($orig_id == '1') {
+ return substr($rawtext, $curr_pos + 1);
+ }
+
+ throw new Horde_Mime_Exception('Could not find MIME part.');
+ }
+
+ $b_find = self::_findBoundary($rawtext, $curr_pos, $params['boundary'], $base_pos);
+
+ if (!isset($b_find[$base_pos])) {
+ throw new Horde_Mime_Exception('Could not find MIME part.');
+ }
+
+ return self::getRawPartText(substr($rawtext, $b_find[$base_pos]['start'], $b_find[$base_pos]['length'] - 1), $type, $id);
+ }
+
+ /**
+ * Find the location of the end of the header text.
+ *
+ * @param string $text The text to search.
+ *
+ * @return array 1st element: Header position, 2nd element: Length of
+ * trailing EOL.
+ */
+ static protected function _findHeader($text)
+ {
+ $hdr_pos = strpos($text, "\r\n\r\n");
+ if ($hdr_pos !== false) {
+ return array($hdr_pos, 4);
+ }
+
+ $hdr_pos = strpos($text, "\n\n");
+ return ($hdr_pos === false)
+ ? array(strlen($text), 0)
+ : array($hdr_pos, 2);
+ }
+
+ /**
+ * Find the location of the next boundary string.
+ *
+ * @param string $text The text to search.
+ * @param integer $pos The current position in $text.
+ * @param string $boundary The boundary string.
+ * @param integer $end If set, return after matching this many
+ * boundaries.
+ *
+ * @return array Keys are the boundary number, values are an array with
+ * two elements: 'start' and 'length'.
+ */
+ static protected function _findBoundary($text, $pos, $boundary,
+ $end = null)
+ {
+ $i = 0;
+ $out = array();
+
+ $search = "--" . $boundary;
+ $search_len = strlen($search);
+
+ while (($pos = strpos($text, $search, $pos)) !== false) {
+ /* Boundary needs to appear at beginning of string or right after
+ * a LF. */
+ if (($pos != 0) && ($text[$pos - 1] != "\n")) {
+ continue;
+ }
+
+ if (isset($out[$i])) {
+ $out[$i]['length'] = $pos - $out[$i]['start'] - 1;
+ }
+
+ if (!is_null($end) && ($end == $i)) {
+ break;
+ }
+
+ $pos += $search_len;
+ if (isset($text[$pos])) {
+ switch ($text[$pos]) {
+ case "\r":
+ $pos += 2;
+ $out[++$i] = array('start' => $pos);
+ break;
+
+ case "\n":
+ $out[++$i] = array('start' => ++$pos);
+ break;
+
+ case '-':
+ return $out;
+ }
+ }
+ }
+
+ return $out;
+ }
+
+ /* ArrayAccess methods. */
+
+ public function offsetExists($offset)
+ {
+ return ($this->getPart($offset) !== null);
+ }
+
+ public function offsetGet($offset)
+ {
+ return $this->getPart($offset);
+ }
+
+ public function offsetSet($offset, $value)
+ {
+ $this->alterPart($offset, $value);
+ }
+
+ public function offsetUnset($offset)
+ {
+ $this->removePart($offset);
+ }
+
+ /* Countable methods. */
+
+ /**
+ * Returns the number of message parts.
+ *
+ * @return integer Number of message parts.
+ */
+ public function count()
+ {
+ return count($this->_parts);
+ }
+
+ /* Serializable methods. */
+
+ /**
+ * Serialization.
+ *
+ * @return string Serialized data.
+ */
+ public function serialize()
+ {
+ $data = array(
+ // Serialized data ID.
+ self::VERSION
+ );
+
+ foreach ($this->_serializedVars as $val) {
+ switch ($val) {
+ case '_contentTypeParams':
+ $data[] = $this->$val->getArrayCopy();
+ break;
+
+ default:
+ $data[] = $this->$val;
+ break;
+ }
+ }
+
+ if (!empty($this->_contents)) {
+ $data[] = $this->_readStream($this->_contents);
+ }
+
+ return serialize($data);
+ }
+
+ /**
+ * Unserialization.
+ *
+ * @param string $data Serialized data.
+ *
+ * @throws Exception
+ */
+ public function unserialize($data)
+ {
+ $data = @unserialize($data);
+ if (!is_array($data) ||
+ !isset($data[0]) ||
+ (array_shift($data) != self::VERSION)) {
+ throw new Exception('Cache version change');
+ }
+
+ $this->_init();
+
+ foreach ($this->_serializedVars as $key => $val) {
+ switch ($val) {
+ case '_contentTypeParams':
+ $this->$val = new Horde_Support_CaseInsensitiveArray($data[$key]);
+ break;
+
+ default:
+ $this->$val = $data[$key];
+ break;
+ }
+ }
+
+ // $key now contains the last index of _serializedVars.
+ if (isset($data[++$key])) {
+ $this->setContents($data[$key]);
+ }
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeMimephp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Mime.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Mime.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Mime.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,711 @@
</span><ins>+<?php
+/**
+ * The Horde_Mime:: class provides methods for dealing with various MIME (see,
+ * e.g., RFC 2045-2049; 2183; 2231) standards.
+ *
+ * -----
+ *
+ * This file contains code adapted from PEAR's Mail_mimeDecode library (v1.5).
+ *
+ * http://pear.php.net/package/Mail_mime
+ *
+ * This code appears in Horde_Mime::decodeParam().
+ *
+ * This code was originally released under this license:
+ *
+ * LICENSE: This LICENSE is in the BSD license style.
+ * Copyright (c) 2002-2003, Richard Heyes <richard@phpguru.org>
+ * Copyright (c) 2003-2006, PEAR <pear-group@php.net>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * - Neither the name of the authors, nor the names of its contributors
+ * may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * -----
+ *
+ * This file contains code adapted from PEAR's PHP_Compat library (v1.6.0a3).
+ *
+ * http://pear.php.net/package/PHP_Compat
+ *
+ * This code appears in Horde_Mime::_uudecode().
+ *
+ * This code was originally released under the LGPL 2.1
+ *
+ * -----
+ *
+ * Copyright 1999-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @author Chuck Hagenbuch <chuck@horde.org>
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Mime
+ */
+class Horde_Mime
+{
+ /**
+ * The RFC defined EOL string.
+ *
+ * @var string
+ */
+ const EOL = "\r\n";
+
+ /**
+ * The list of characters required to be quoted in MIME parameters
+ * (regular expression).
+ *
+ * @since 2.1.0
+ *
+ * @var string
+ */
+ const MIME_PARAM_QUOTED = '/[\x01-\x20\x22\x28\x29\x2c\x2f\x3a-\x40\x5b-\x5d]/';
+
+ /**
+ * Attempt to work around non RFC 2231-compliant MUAs by generating both
+ * a RFC 2047-like parameter name and also the correct RFC 2231
+ * parameter. See:
+ * http://lists.horde.org/archives/dev/Week-of-Mon-20040426/014240.html
+ *
+ * @var boolean
+ */
+ static public $brokenRFC2231 = false;
+
+ /**
+ * Use windows-1252 charset when decoding ISO-8859-1 data?
+ *
+ * @var boolean
+ */
+ static public $decodeWindows1252 = false;
+
+ /**
+ * Determines if a string contains 8-bit (non US-ASCII) characters.
+ *
+ * @param string $string The string to check.
+ * @param string $charset The charset of the string. Defaults to
+ * US-ASCII.
+ *
+ * @return boolean True if string contains non US-ASCII characters.
+ */
+ static public function is8bit($string, $charset = null)
+ {
+ return ($string != Horde_String::convertCharset($string, $charset, 'US-ASCII'));
+ }
+
+ /**
+ * MIME encodes a string (RFC 2047).
+ *
+ * @param string $text The text to encode (UTF-8).
+ * @param string $charset The character set to encode to.
+ *
+ * @return string The MIME encoded string (US-ASCII).
+ */
+ static public function encode($text, $charset = 'UTF-8')
+ {
+ /* The null character is valid US-ASCII, but was removed from the
+ * allowed e-mail header characters in RFC 2822. */
+ if (!self::is8bit($text, 'UTF-8') && (strpos($text, null) === false)) {
+ return $text;
+ }
+
+ $charset = Horde_String::lower($charset);
+ $text = Horde_String::convertCharset($text, 'UTF-8', $charset);
+
+ /* Get the list of elements in the string. */
+ $size = preg_match_all('/([^\s]+)([\s]*)/', $text, $matches, PREG_SET_ORDER);
+
+ $line = '';
+
+ /* Return if nothing needs to be encoded. */
+ foreach ($matches as $key => $val) {
+ if (self::is8bit($val[1], $charset)) {
+ if ((($key + 1) < $size) &&
+ self::is8bit($matches[$key + 1][1], $charset)) {
+ $line .= self::_encode($val[1] . $val[2], $charset) . ' ';
+ } else {
+ $line .= self::_encode($val[1], $charset) . $val[2];
+ }
+ } else {
+ $line .= $val[1] . $val[2];
+ }
+ }
+
+ return rtrim($line);
+ }
+
+ /**
+ * Internal helper function to MIME encode a string.
+ *
+ * @param string $text The text to encode.
+ * @param string $charset The character set of the text.
+ *
+ * @return string The MIME encoded text.
+ */
+ static protected function _encode($text, $charset)
+ {
+ $encoded = trim(base64_encode($text));
+ $c_size = strlen($charset) + 7;
+
+ if ((strlen($encoded) + $c_size) > 75) {
+ $parts = explode(self::EOL, rtrim(chunk_split($encoded, intval((75 - $c_size) / 4) * 4)));
+ } else {
+ $parts[] = $encoded;
+ }
+
+ $p_size = count($parts);
+ $out = '';
+
+ foreach ($parts as $key => $val) {
+ $out .= '=?' . $charset . '?b?' . $val . '?=';
+ if ($p_size > $key + 1) {
+ /* RFC 2047 [2]: no encoded word can be more than 75
+ * characters long. If longer, you must split the word with
+ * CRLF SPACE. */
+ $out .= self::EOL . ' ';
+ }
+ }
+
+ return $out;
+ }
+
+ /**
+ * Encodes a line via quoted-printable encoding.
+ *
+ * @param string $text The text to encode (UTF-8).
+ * @param string $eol The EOL sequence to use.
+ * @param integer $wrap Wrap a line at this many characters.
+ *
+ * @return string The quoted-printable encoded string.
+ */
+ static public function quotedPrintableEncode($text, $eol = self::EOL,
+ $wrap = 76)
+ {
+ $curr_length = 0;
+ $output = '';
+
+ /* We need to go character by character through the data. */
+ for ($i = 0, $length = strlen($text); $i < $length; ++$i) {
+ $char = $text[$i];
+
+ /* If we have reached the end of the line, reset counters. */
+ if ($char == "\n") {
+ $output .= $eol;
+ $curr_length = 0;
+ continue;
+ } elseif ($char == "\r") {
+ continue;
+ }
+
+ /* Spaces or tabs at the end of the line are NOT allowed. Also,
+ * ASCII characters below 32 or above 126 AND 61 must be
+ * encoded. */
+ $ascii = ord($char);
+ if ((($ascii === 32) &&
+ ($i + 1 != $length) &&
+ (($text[$i + 1] == "\n") || ($text[$i + 1] == "\r"))) ||
+ (($ascii < 32) || ($ascii > 126) || ($ascii === 61))) {
+ $char_len = 3;
+ $char = '=' . Horde_String::upper(sprintf('%02s', dechex($ascii)));
+ } else {
+ $char_len = 1;
+ }
+
+ /* Lines must be $wrap characters or less. */
+ $curr_length += $char_len;
+ if ($curr_length > $wrap) {
+ $output .= '=' . $eol;
+ $curr_length = $char_len;
+ }
+ $output .= $char;
+ }
+
+ return $output;
+ }
+
+ /**
+ * Decodes a MIME encoded (RFC 2047) string.
+ *
+ * @param string $string The MIME encoded text.
+ *
+ * @return string The decoded text.
+ */
+ static public function decode($string)
+ {
+ /* Take out any spaces between multiple encoded words. */
+ $string = preg_replace('|\?=\s+=\?|', '?==?', $string);
+
+ $out = '';
+ $old_pos = 0;
+
+ while (($pos = strpos($string, '=?', $old_pos)) !== false) {
+ /* Save any preceding text. */
+ $out .= substr($string, $old_pos, $pos - $old_pos);
+
+ /* Search for first delimiting question mark (charset). */
+ if (($d1 = strpos($string, '?', $pos + 2)) === false) {
+ break;
+ }
+
+ $orig_charset = substr($string, $pos + 2, $d1 - $pos - 2);
+ if (self::$decodeWindows1252 &&
+ (Horde_String::lower($orig_charset) == 'iso-8859-1')) {
+ $orig_charset = 'windows-1252';
+ }
+
+ /* Search for second delimiting question mark (encoding). */
+ if (($d2 = strpos($string, '?', $d1 + 1)) === false) {
+ break;
+ }
+
+ $encoding = substr($string, $d1 + 1, $d2 - $d1 - 1);
+
+ /* Search for end of encoded data. */
+ if (($end = strpos($string, '?=', $d2 + 1)) === false) {
+ break;
+ }
+
+ $encoded_text = substr($string, $d2 + 1, $end - $d2 - 1);
+
+ switch ($encoding) {
+ case 'Q':
+ case 'q':
+ $out .= Horde_String::convertCharset(
+ preg_replace_callback(
+ '/=([0-9a-f]{2})/i',
+ function($ord) {
+ return chr(hexdec($ord[1]));
+ },
+ str_replace('_', ' ', $encoded_text)),
+ $orig_charset,
+ 'UTF-8'
+ );
+ break;
+
+ case 'B':
+ case 'b':
+ $out .= Horde_String::convertCharset(
+ base64_decode($encoded_text),
+ $orig_charset,
+ 'UTF-8'
+ );
+ break;
+
+ default:
+ // Ignore unknown encoding.
+ break;
+ }
+
+ $old_pos = $end + 2;
+ }
+
+ return $out . substr($string, $old_pos);
+ }
+
+ /**
+ * Encodes a MIME parameter string pursuant to RFC 2183 & 2231
+ * (Content-Type and Content-Disposition headers).
+ *
+ * @param string $name The parameter name.
+ * @param string $val The parameter value (UTF-8).
+ * @param array $opts Additional options:
+ * - charset: (string) The charset to encode to.
+ * DEFAULT: UTF-8
+ * - lang: (string) The language to use when encoding.
+ * DEFAULT: None specified
+ *
+ * @return array The encoded parameter string (US-ASCII).
+ */
+ static public function encodeParam($name, $val, array $opts = array())
+ {
+ $curr = 0;
+ $encode = $wrap = false;
+ $output = array();
+
+ $charset = isset($opts['charset'])
+ ? $opts['charset']
+ : 'UTF-8';
+
+ // 2 = '=', ';'
+ $pre_len = strlen($name) + 2;
+
+ /* Several possibilities:
+ * - String is ASCII. Output as ASCII (duh).
+ * - Language information has been provided. We MUST encode output
+ * to include this information.
+ * - String is non-ASCII, but can losslessly translate to ASCII.
+ * Output as ASCII (most efficient).
+ * - String is in non-ASCII, but doesn't losslessly translate to
+ * ASCII. MUST encode output (duh). */
+ if (empty($opts['lang']) && !self::is8bit($val, 'UTF-8')) {
+ $string = $val;
+ } else {
+ $cval = Horde_String::convertCharset($val, 'UTF-8', $charset);
+ $string = Horde_String::lower($charset) . '\'' . (empty($opts['lang']) ? '' : Horde_String::lower($opts['lang'])) . '\'' . rawurlencode($cval);
+ $encode = true;
+ /* Account for trailing '*'. */
+ ++$pre_len;
+ }
+
+ if (($pre_len + strlen($string)) > 75) {
+ /* Account for continuation '*'. */
+ ++$pre_len;
+ $wrap = true;
+
+ while ($string) {
+ $chunk = 75 - $pre_len - strlen($curr);
+ $pos = min($chunk, strlen($string) - 1);
+
+ /* Don't split in the middle of an encoded char. */
+ if (($chunk == $pos) && ($pos > 2)) {
+ for ($i = 0; $i <= 2; ++$i) {
+ if ($string[$pos - $i] == '%') {
+ $pos -= $i + 1;
+ break;
+ }
+ }
+ }
+
+ $lines[] = substr($string, 0, $pos + 1);
+ $string = substr($string, $pos + 1);
+ ++$curr;
+ }
+ } else {
+ $lines = array($string);
+ }
+
+ foreach ($lines as $i => $line) {
+ $output[$name . (($wrap) ? ('*' . $i) : '') . (($encode) ? '*' : '')] = $line;
+ }
+
+ if (self::$brokenRFC2231 && !isset($output[$name])) {
+ $output = array_merge(array(
+ $name => self::encode($val, $charset)
+ ), $output);
+ }
+
+ /* Escape certain characters in params (See RFC 2045 [Appendix A]).
+ * Must be quoted-string if one of these exists.
+ * Forbidden: SPACE, CTLs, ()<>@,;:\"/[]?= */
+ foreach ($output as $k => $v) {
+ if (preg_match(self::MIME_PARAM_QUOTED, $v)) {
+ $output[$k] = '"' . addcslashes($v, '\\"') . '"';
+ }
+ }
+
+ return $output;
+ }
+
+ /**
+ * Decodes a MIME parameter string pursuant to RFC 2183 & 2231
+ * (Content-Type and Content-Disposition headers).
+ *
+ * @param string $type Either 'Content-Type' or 'Content-Disposition'
+ * (case-insensitive).
+ * @param mixed $data The text of the header or an array of param name
+ * => param values.
+ *
+ * @return array An array with the following entries (all strings in
+ * UTF-8):
+ * - params: (array) The header's parameter values.
+ * - val: (string) The header's "base" value.
+ */
+ static public function decodeParam($type, $data)
+ {
+ $convert = array();
+ $ret = array('params' => array(), 'val' => '');
+ $splitRegex = '/([^;\'"]*[\'"]([^\'"]*([^\'"]*)*)[\'"][^;\'"]*|([^;]+))(;|$)/';
+ $type = Horde_String::lower($type);
+
+ if (is_array($data)) {
+ // Use dummy base values
+ $ret['val'] = ($type == 'content-type')
+ ? 'text/plain'
+ : 'attachment';
+ $params = $data;
+ } else {
+ /* This code was adapted from PEAR's Mail_mimeDecode::. */
+ if (($pos = strpos($data, ';')) === false) {
+ $ret['val'] = trim($data);
+ return $ret;
+ }
+
+ $ret['val'] = trim(substr($data, 0, $pos));
+ $data = trim(substr($data, ++$pos));
+ $params = $tmp = array();
+
+ if (strlen($data) > 0) {
+ /* This splits on a semi-colon, if there's no preceeding
+ * backslash. */
+ preg_match_all($splitRegex, $data, $matches);
+
+ for ($i = 0, $cnt = count($matches[0]); $i < $cnt; ++$i) {
+ $param = $matches[0][$i];
+ while (substr($param, -2) == '\;') {
+ $param .= $matches[0][++$i];
+ }
+ $tmp[] = $param;
+ }
+
+ for ($i = 0, $cnt = count($tmp); $i < $cnt; ++$i) {
+ $pos = strpos($tmp[$i], '=');
+ $p_name = trim(substr($tmp[$i], 0, $pos), "'\";\t\\ ");
+ $p_val = trim(str_replace('\;', ';', substr($tmp[$i], $pos + 1)), "'\";\t\\ ");
+ if (strlen($p_val) && ($p_val[0] == '"')) {
+ $p_val = substr($p_val, 1, -1);
+ }
+
+ $params[$p_name] = $p_val;
+ }
+ }
+ /* End of code adapted from PEAR's Mail_mimeDecode::. */
+ }
+
+ /* Sort the params list. Prevents us from having to manually keep
+ * track of continuation values below. */
+ uksort($params, 'strnatcasecmp');
+
+ foreach ($params as $name => $val) {
+ /* Asterisk at end indicates encoded value. */
+ if (substr($name, -1) == '*') {
+ $name = substr($name, 0, -1);
+ $encode = true;
+ } else {
+ $encode = false;
+ }
+
+ /* This asterisk indicates continuation parameter. */
+ if (($pos = strrpos($name, '*')) !== false) {
+ $name = substr($name, 0, $pos);
+ }
+
+ if (!isset($ret['params'][$name]) ||
+ ($encode && !isset($convert[$name]))) {
+ $ret['params'][$name] = '';
+ }
+
+ $ret['params'][$name] .= $val;
+
+ if ($encode) {
+ $convert[$name] = true;
+ }
+ }
+
+ foreach (array_keys($convert) as $name) {
+ $val = $ret['params'][$name];
+ $quote = strpos($val, "'");
+ $orig_charset = substr($val, 0, $quote);
+ if (self::$decodeWindows1252 &&
+ (Horde_String::lower($orig_charset) == 'iso-8859-1')) {
+ $orig_charset = 'windows-1252';
+ }
+ /* Ignore language. */
+ $quote = strpos($val, "'", $quote + 1);
+ substr($val, $quote + 1);
+ $ret['params'][$name] = Horde_String::convertCharset(urldecode(substr($val, $quote + 1)), $orig_charset, 'UTF-8');
+ }
+
+ /* MIME parameters are supposed to be encoded via RFC 2231, but many
+ * mailers do RFC 2045 encoding instead. However, if we see at least
+ * one RFC 2231 encoding, then assume the sending mailer knew what
+ * it was doing. */
+ if (empty($convert)) {
+ foreach (array_diff(array_keys($ret['params']), array_keys($convert)) as $name) {
+ $ret['params'][$name] = self::decode($ret['params'][$name]);
+ }
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Generates a Message-ID string conforming to RFC 2822 [3.6.4] and the
+ * standards outlined in 'draft-ietf-usefor-message-id-01.txt'.
+ *
+ * @param string A message ID string.
+ */
+ static public function generateMessageId()
+ {
+ return '<' . strval(new Horde_Support_Guid(array('prefix' => 'Horde'))) . '>';
+ }
+
+ /**
+ * Performs MIME ID "arithmetic" on a given ID.
+ *
+ * @param string $id The MIME ID string.
+ * @param string $action One of the following:
+ * - down: ID of child. Note: down will first traverse to "$id.0" if
+ * given an ID *NOT* of the form "$id.0". If given an ID of the
+ * form "$id.0", down will traverse to "$id.1". This behavior
+ * can be avoided if 'norfc822' option is set.
+ * - next: ID of next sibling.
+ * - prev: ID of previous sibling.
+ * - up: ID of parent. Note: up will first traverse to "$id.0" if
+ * given an ID *NOT* of the form "$id.0". If given an ID of the
+ * form "$id.0", down will traverse to "$id". This behavior can be
+ * avoided if 'norfc822' option is set.
+ * @param array $options Additional options:
+ * - count: (integer) How many levels to traverse.
+ * DEFAULT: 1
+ * - norfc822: (boolean) Don't traverse rfc822 sub-levels
+ * DEFAULT: false
+ *
+ * @return mixed The resulting ID string, or null if that ID can not
+ * exist.
+ */
+ static public function mimeIdArithmetic($id, $action, $options = array())
+ {
+ $pos = strrpos($id, '.');
+ $end = ($pos === false) ? $id : substr($id, $pos + 1);
+
+ switch ($action) {
+ case 'down':
+ if ($end == '0') {
+ $id = ($pos === false) ? 1 : substr_replace($id, '1', $pos + 1);
+ } else {
+ $id .= empty($options['norfc822']) ? '.0' : '.1';
+ }
+ break;
+
+ case 'next':
+ ++$end;
+ $id = ($pos === false) ? $end : substr_replace($id, $end, $pos + 1);
+ break;
+
+ case 'prev':
+ if (($end == '0') ||
+ (empty($options['norfc822']) && ($end == '1'))) {
+ $id = null;
+ } elseif ($pos === false) {
+ $id = --$end;
+ } else {
+ $id = substr_replace($id, --$end, $pos + 1);
+ }
+ break;
+
+ case 'up':
+ if ($pos === false) {
+ $id = ($end == '0') ? null : '0';
+ } elseif (!empty($options['norfc822']) || ($end == '0')) {
+ $id = substr($id, 0, $pos);
+ } else {
+ $id = substr_replace($id, '0', $pos + 1);
+ }
+ break;
+ }
+
+ return (!is_null($id) && !empty($options['count']) && --$options['count'])
+ ? self::mimeIdArithmetic($id, $action, $options)
+ : $id;
+ }
+
+ /**
+ * Determines if a given MIME ID lives underneath a base ID.
+ *
+ * @param string $base The base MIME ID.
+ * @param string $id The MIME ID to query.
+ *
+ * @return boolean Whether $id lives underneath $base.
+ */
+ static public function isChild($base, $id)
+ {
+ $base = (substr($base, -2) == '.0')
+ ? substr($base, 0, -1)
+ : rtrim($base, '.') . '.';
+
+ return ((($base == 0) && ($id != 0)) ||
+ (strpos(strval($id), strval($base)) === 0));
+ }
+
+ /**
+ * Scans $input for uuencoded data and converts it to unencoded data.
+ *
+ * @param string $input The input data
+ *
+ * @return array A list of arrays, with each array corresponding to
+ * a file in the input and containing the following keys:
+ * - data: (string) Unencoded data.
+ * - name: (string) Filename.
+ * - perms: (string) Octal permissions.
+ */
+ static public function uudecode($input)
+ {
+ $data = array();
+
+ /* Find all uuencoded sections. */
+ if (preg_match_all("/begin ([0-7]{3}) (.+)\r?\n(.+)\r?\nend/Us", $input, $matches, PREG_SET_ORDER)) {
+ reset($matches);
+ while (list(,$v) = each($matches)) {
+ $data[] = array(
+ 'data' => self::_uudecode($v[3]),
+ 'name' => $v[2],
+ 'perm' => $v[1]
+ );
+ }
+ }
+
+ return $data;
+ }
+
+ /**
+ * PHP 5's built-in convert_uudecode() is broken. Need this wrapper.
+ *
+ * @param string $input UUencoded input.
+ *
+ * @return string Decoded string.
+ */
+ static protected function _uudecode($input)
+ {
+ $decoded = '';
+
+ foreach (explode("\n", $input) as $line) {
+ $c = count($bytes = unpack('c*', substr(trim($line,"\r\n\t"), 1)));
+
+ while ($c % 4) {
+ $bytes[++$c] = 0;
+ }
+
+ foreach (array_chunk($bytes, 4) as $b) {
+ $b0 = ($b[0] == 0x60) ? 0 : $b[0] - 0x20;
+ $b1 = ($b[1] == 0x60) ? 0 : $b[1] - 0x20;
+ $b2 = ($b[2] == 0x60) ? 0 : $b[2] - 0x20;
+ $b3 = ($b[3] == 0x60) ? 0 : $b[3] - 0x20;
+
+ $b0 <<= 2;
+ $b0 |= ($b1 >> 4) & 0x03;
+ $b1 <<= 4;
+ $b1 |= ($b2 >> 2) & 0x0F;
+ $b2 <<= 6;
+ $b2 |= $b3 & 0x3F;
+
+ $decoded .= pack('c*', $b0, $b1, $b2);
+ }
+ }
+
+ return rtrim($decoded, "\0");
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeStreamFilterEolphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Stream-Filter-Eol.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Stream-Filter-Eol.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Stream-Filter-Eol.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,84 @@
</span><ins>+<?php
+/**
+ * Copyright 2009-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @copyright 2009-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Stream_Filter
+ */
+
+/**
+ * Stream filter class to convert EOL characters.
+ *
+ * Usage:
+ * stream_filter_register('horde_eol', 'Horde_Stream_Filter_Eol');
+ * stream_filter_[app|pre]pend($stream, 'horde_eol',
+ * [ STREAM_FILTER_[READ|WRITE|ALL] ],
+ * [ $params ]);
+ *
+ * $params is an array that can contain the following:
+ * - eol: (string) The EOL string to use.
+ * DEFAULT: <CR><LF> ("\r\n")
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2009-2013 Horde LLC
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Stream_Filter
+ */
+class Horde_Stream_Filter_Eol extends php_user_filter
+{
+ /**
+ * Search array.
+ *
+ * @param mixed
+ */
+ protected $_search;
+
+ /**
+ * Replacement data
+ *
+ * @param mixed
+ */
+ protected $_replace;
+
+ /**
+ * @see stream_filter_register()
+ */
+ public function onCreate()
+ {
+ $eol = isset($this->params['eol']) ? $this->params['eol'] : "\r\n";
+
+ if (!strlen($eol)) {
+ $this->_search = array("\r", "\n");
+ $this->_replace = '';
+ } elseif (in_array($eol, array("\r", "\n"))) {
+ $this->_search = array("\r\n", ($eol == "\r") ? "\n" : "\r");
+ $this->_replace = $eol;
+ } else {
+ $this->_search = array("\r\n", "\r", "\n");
+ $this->_replace = array("\n", "\n", $eol);
+ }
+
+ return true;
+ }
+
+ /**
+ * @see stream_filter_register()
+ */
+ public function filter($in, $out, &$consumed, $closing)
+ {
+ while ($bucket = stream_bucket_make_writeable($in)) {
+ $bucket->data = str_replace($this->_search, $this->_replace, $bucket->data);
+ $consumed += $bucket->datalen;
+ stream_bucket_append($out, $bucket);
+ }
+
+ return PSFS_PASS_ON;
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeStringphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/String.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/String.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/String.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,797 @@
</span><ins>+<?php
+/**
+ * Provides static methods for charset and locale safe string manipulation.
+ *
+ * Copyright 2003-2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @author Jan Schneider <jan@horde.org>
+ * @category Horde
+ * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
+ * @package Util
+ */
+class Horde_String
+{
+ /**
+ * lower() cache.
+ *
+ * @var array
+ */
+ static protected $_lowers = array();
+
+ /**
+ * upper() cache.
+ *
+ * @var array
+ */
+ static protected $_uppers = array();
+
+ /**
+ * Converts a string from one charset to another.
+ *
+ * Uses the iconv or the mbstring extensions.
+ * The original string is returned if conversion failed or none
+ * of the extensions were available.
+ *
+ * @param mixed $input The data to be converted. If $input is an an
+ * array, the array's values get converted
+ * recursively.
+ * @param string $from The string's current charset.
+ * @param string $to The charset to convert the string to.
+ * @param boolean $force Force conversion?
+ *
+ * @return mixed The converted input data.
+ */
+ static public function convertCharset($input, $from, $to, $force = false)
+ {
+ /* Don't bother converting numbers. */
+ if (is_numeric($input)) {
+ return $input;
+ }
+
+ /* If the from and to character sets are identical, return now. */
+ if (!$force && $from == $to) {
+ return $input;
+ }
+ $from = self::lower($from);
+ $to = self::lower($to);
+ if (!$force && $from == $to) {
+ return $input;
+ }
+
+ if (is_array($input)) {
+ $tmp = array();
+ reset($input);
+ while (list($key, $val) = each($input)) {
+ $tmp[self::_convertCharset($key, $from, $to)] = self::convertCharset($val, $from, $to, $force);
+ }
+ return $tmp;
+ }
+
+ if (is_object($input)) {
+ // PEAR_Error/Exception objects are almost guaranteed to contain
+ // recursion, which will cause a segfault in PHP. We should never
+ // reach this line, but add a check.
+ if (($input instanceof Exception) ||
+ ($input instanceof PEAR_Error)) {
+ return '';
+ }
+
+ $input = clone $input;
+ $vars = get_object_vars($input);
+ while (list($key, $val) = each($vars)) {
+ $input->$key = self::convertCharset($val, $from, $to, $force);
+ }
+ return $input;
+ }
+
+ if (!is_string($input)) {
+ return $input;
+ }
+
+ return self::_convertCharset($input, $from, $to);
+ }
+
+ /**
+ * Internal function used to do charset conversion.
+ *
+ * @param string $input See self::convertCharset().
+ * @param string $from See self::convertCharset().
+ * @param string $to See self::convertCharset().
+ *
+ * @return string The converted string.
+ */
+ static protected function _convertCharset($input, $from, $to)
+ {
+ /* Use utf8_[en|de]code() if possible and if the string isn't too
+ * large (less than 16 MB = 16 * 1024 * 1024 = 16777216 bytes) - these
+ * functions use more memory. */
+ if (Horde_Util::extensionExists('xml') &&
+ ((strlen($input) < 16777216) ||
+ !Horde_Util::extensionExists('iconv') ||
+ !Horde_Util::extensionExists('mbstring'))) {
+ if (($to == 'utf-8') &&
+ in_array($from, array('iso-8859-1', 'us-ascii', 'utf-8'))) {
+ return utf8_encode($input);
+ }
+
+ if (($from == 'utf-8') &&
+ in_array($to, array('iso-8859-1', 'us-ascii', 'utf-8'))) {
+ return utf8_decode($input);
+ }
+ }
+
+ /* Try UTF7-IMAP conversions. */
+ if (($from == 'utf7-imap') || ($to == 'utf7-imap')) {
+ try {
+ if ($from == 'utf7-imap') {
+ return self::convertCharset(Horde_Imap_Client_Utf7imap::Utf7ImapToUtf8($input), 'UTF-8', $to);
+ } else {
+ if ($from == 'utf-8') {
+ $conv = $input;
+ } else {
+ $conv = self::convertCharset($input, $from, 'UTF-8');
+ }
+ return Horde_Imap_Client_Utf7imap::Utf8ToUtf7Imap($conv);
+ }
+ } catch (Horde_Imap_Client_Exception $e) {
+ return $input;
+ }
+ }
+
+ /* Try iconv with transliteration. */
+ if (Horde_Util::extensionExists('iconv')) {
+ unset($php_errormsg);
+ ini_set('track_errors', 1);
+ $out = @iconv($from, $to . '//TRANSLIT', $input);
+ $errmsg = isset($php_errormsg);
+ ini_restore('track_errors');
+ if (!$errmsg && $out !== false) {
+ return $out;
+ }
+ }
+
+ /* Try mbstring. */
+ if (Horde_Util::extensionExists('mbstring')) {
+ $out = @mb_convert_encoding($input, $to, self::_mbstringCharset($from));
+ if (!empty($out)) {
+ return $out;
+ }
+ }
+
+ return $input;
+ }
+
+ /**
+ * Makes a string lowercase.
+ *
+ * @param string $string The string to be converted.
+ * @param boolean $locale If true the string will be converted based on
+ * a given charset, locale independent else.
+ * @param string $charset If $locale is true, the charset to use when
+ * converting.
+ *
+ * @return string The string with lowercase characters.
+ */
+ static public function lower($string, $locale = false, $charset = null)
+ {
+ if ($locale) {
+ if (Horde_Util::extensionExists('mbstring')) {
+ if (is_null($charset)) {
+ throw new InvalidArgumentException('$charset argument must not be null');
+ }
+ $ret = @mb_strtolower($string, self::_mbstringCharset($charset));
+ if (!empty($ret)) {
+ return $ret;
+ }
+ }
+ return strtolower($string);
+ }
+
+ if (!isset(self::$_lowers[$string])) {
+ $language = setlocale(LC_CTYPE, 0);
+ setlocale(LC_CTYPE, 'C');
+ self::$_lowers[$string] = strtolower($string);
+ setlocale(LC_CTYPE, $language);
+ }
+
+ return self::$_lowers[$string];
+ }
+
+ /**
+ * Makes a string uppercase.
+ *
+ * @param string $string The string to be converted.
+ * @param boolean $locale If true the string will be converted based on a
+ * given charset, locale independent else.
+ * @param string $charset If $locale is true, the charset to use when
+ * converting. If not provided the current charset.
+ *
+ * @return string The string with uppercase characters.
+ */
+ static public function upper($string, $locale = false, $charset = null)
+ {
+ if ($locale) {
+ if (Horde_Util::extensionExists('mbstring')) {
+ if (is_null($charset)) {
+ throw new InvalidArgumentException('$charset argument must not be null');
+ }
+ $ret = @mb_strtoupper($string, self::_mbstringCharset($charset));
+ if (!empty($ret)) {
+ return $ret;
+ }
+ }
+ return strtoupper($string);
+ }
+
+ if (!isset(self::$_uppers[$string])) {
+ $language = setlocale(LC_CTYPE, 0);
+ setlocale(LC_CTYPE, 'C');
+ self::$_uppers[$string] = strtoupper($string);
+ setlocale(LC_CTYPE, $language);
+ }
+
+ return self::$_uppers[$string];
+ }
+
+ /**
+ * Returns a string with the first letter capitalized if it is
+ * alphabetic.
+ *
+ * @param string $string The string to be capitalized.
+ * @param boolean $locale If true the string will be converted based on a
+ * given charset, locale independent else.
+ * @param string $charset The charset to use, defaults to current charset.
+ *
+ * @return string The capitalized string.
+ */
+ static public function ucfirst($string, $locale = false, $charset = null)
+ {
+ if ($locale) {
+ if (is_null($charset)) {
+ throw new InvalidArgumentException('$charset argument must not be null');
+ }
+ $first = self::substr($string, 0, 1, $charset);
+ if (self::isAlpha($first, $charset)) {
+ $string = self::upper($first, true, $charset) . self::substr($string, 1, null, $charset);
+ }
+ } else {
+ $string = self::upper(substr($string, 0, 1), false) . substr($string, 1);
+ }
+
+ return $string;
+ }
+
+ /**
+ * Returns a string with the first letter of each word capitalized if it is
+ * alphabetic.
+ *
+ * Sentences are splitted into words at whitestrings.
+ *
+ * @param string $string The string to be capitalized.
+ * @param boolean $locale If true the string will be converted based on a
+ * given charset, locale independent else.
+ * @param string $charset The charset to use, defaults to current charset.
+ *
+ * @return string The capitalized string.
+ */
+ static public function ucwords($string, $locale = false, $charset = null)
+ {
+ $words = preg_split('/(\s+)/', $string, -1, PREG_SPLIT_DELIM_CAPTURE);
+ for ($i = 0, $c = count($words); $i < $c; $i += 2) {
+ $words[$i] = self::ucfirst($words[$i], $locale, $charset);
+ }
+ return implode('', $words);
+ }
+
+ /**
+ * Returns part of a string.
+ *
+ * @param string $string The string to be converted.
+ * @param integer $start The part's start position, zero based.
+ * @param integer $length The part's length.
+ * @param string $charset The charset to use when calculating the part's
+ * position and length, defaults to current
+ * charset.
+ *
+ * @return string The string's part.
+ */
+ static public function substr($string, $start, $length = null,
+ $charset = 'UTF-8')
+ {
+ if (is_null($length)) {
+ $length = self::length($string, $charset) - $start;
+ }
+
+ if ($length == 0) {
+ return '';
+ }
+
+ /* Try mbstring. */
+ if (Horde_Util::extensionExists('mbstring')) {
+ $ret = @mb_substr($string, $start, $length, self::_mbstringCharset($charset));
+
+ /* mb_substr() returns empty string on failure. */
+ if (strlen($ret)) {
+ return $ret;
+ }
+ }
+
+ /* Try iconv. */
+ if (Horde_Util::extensionExists('iconv')) {
+ $ret = @iconv_substr($string, $start, $length, $charset);
+
+ /* iconv_substr() returns false on failure. */
+ if ($ret !== false) {
+ return $ret;
+ }
+ }
+
+ return substr($string, $start, $length);
+ }
+
+ /**
+ * Returns the character (not byte) length of a string.
+ *
+ * @param string $string The string to return the length of.
+ * @param string $charset The charset to use when calculating the string's
+ * length.
+ *
+ * @return integer The string's length.
+ */
+ static public function length($string, $charset = 'UTF-8')
+ {
+ $charset = self::lower($charset);
+
+ if ($charset == 'utf-8' || $charset == 'utf8') {
+ return strlen(utf8_decode($string));
+ }
+
+ if (Horde_Util::extensionExists('mbstring')) {
+ $ret = @mb_strlen($string, self::_mbstringCharset($charset));
+ if (!empty($ret)) {
+ return $ret;
+ }
+ }
+
+ return strlen($string);
+ }
+
+ /**
+ * Returns the numeric position of the first occurrence of $needle
+ * in the $haystack string.
+ *
+ * @param string $haystack The string to search through.
+ * @param string $needle The string to search for.
+ * @param integer $offset Allows to specify which character in haystack
+ * to start searching.
+ * @param string $charset The charset to use when searching for the
+ * $needle string.
+ *
+ * @return integer The position of first occurrence.
+ */
+ static public function pos($haystack, $needle, $offset = 0,
+ $charset = 'UTF-8')
+ {
+ if (Horde_Util::extensionExists('mbstring')) {
+ $track_errors = ini_set('track_errors', 1);
+ $ret = @mb_strpos($haystack, $needle, $offset, self::_mbstringCharset($charset));
+ ini_set('track_errors', $track_errors);
+ if (!isset($php_errormsg)) {
+ return $ret;
+ }
+ }
+
+ return strpos($haystack, $needle, $offset);
+ }
+
+ /**
+ * Returns the numeric position of the last occurrence of $needle
+ * in the $haystack string.
+ *
+ * @param string $haystack The string to search through.
+ * @param string $needle The string to search for.
+ * @param integer $offset Allows to specify which character in haystack
+ * to start searching.
+ * @param string $charset The charset to use when searching for the
+ * $needle string.
+ *
+ * @return integer The position of first occurrence.
+ */
+ static public function rpos($haystack, $needle, $offset = 0,
+ $charset = 'UTF-8')
+ {
+ if (Horde_Util::extensionExists('mbstring')) {
+ $track_errors = ini_set('track_errors', 1);
+ $ret = @mb_strrpos($haystack, $needle, $offset, self::_mbstringCharset($charset));
+ ini_set('track_errors', $track_errors);
+ if (!isset($php_errormsg)) {
+ return $ret;
+ }
+ }
+
+ return strrpos($haystack, $needle, $offset);
+ }
+
+ /**
+ * Returns a string padded to a certain length with another string.
+ * This method behaves exactly like str_pad() but is multibyte safe.
+ *
+ * @param string $input The string to be padded.
+ * @param integer $length The length of the resulting string.
+ * @param string $pad The string to pad the input string with. Must
+ * be in the same charset like the input string.
+ * @param const $type The padding type. One of STR_PAD_LEFT,
+ * STR_PAD_RIGHT, or STR_PAD_BOTH.
+ * @param string $charset The charset of the input and the padding
+ * strings.
+ *
+ * @return string The padded string.
+ */
+ static public function pad($input, $length, $pad = ' ',
+ $type = STR_PAD_RIGHT, $charset = 'UTF-8')
+ {
+ $mb_length = self::length($input, $charset);
+ $sb_length = strlen($input);
+ $pad_length = self::length($pad, $charset);
+
+ /* Return if we already have the length. */
+ if ($mb_length >= $length) {
+ return $input;
+ }
+
+ /* Shortcut for single byte strings. */
+ if ($mb_length == $sb_length && $pad_length == strlen($pad)) {
+ return str_pad($input, $length, $pad, $type);
+ }
+
+ switch ($type) {
+ case STR_PAD_LEFT:
+ $left = $length - $mb_length;
+ $output = self::substr(str_repeat($pad, ceil($left / $pad_length)), 0, $left, $charset) . $input;
+ break;
+
+ case STR_PAD_BOTH:
+ $left = floor(($length - $mb_length) / 2);
+ $right = ceil(($length - $mb_length) / 2);
+ $output = self::substr(str_repeat($pad, ceil($left / $pad_length)), 0, $left, $charset) .
+ $input .
+ self::substr(str_repeat($pad, ceil($right / $pad_length)), 0, $right, $charset);
+ break;
+
+ case STR_PAD_RIGHT:
+ $right = $length - $mb_length;
+ $output = $input . self::substr(str_repeat($pad, ceil($right / $pad_length)), 0, $right, $charset);
+ break;
+ }
+
+ return $output;
+ }
+
+ /**
+ * Wraps the text of a message.
+ *
+ * @param string $string String containing the text to wrap.
+ * @param integer $width Wrap the string at this number of
+ * characters.
+ * @param string $break Character(s) to use when breaking lines.
+ * @param boolean $cut Whether to cut inside words if a line
+ * can't be wrapped.
+ * @param boolean $line_folding Whether to apply line folding rules per
+ * RFC 822 or similar. The correct break
+ * characters including leading whitespace
+ * have to be specified too.
+ *
+ * @return string String containing the wrapped text.
+ */
+ static public function wordwrap($string, $width = 75, $break = "\n",
+ $cut = false, $line_folding = false)
+ {
+ $wrapped = '';
+
+ while (self::length($string, 'UTF-8') > $width) {
+ $line = self::substr($string, 0, $width, 'UTF-8');
+ $string = self::substr($string, self::length($line, 'UTF-8'), null, 'UTF-8');
+
+ // Make sure we didn't cut a word, unless we want hard breaks
+ // anyway.
+ if (!$cut && preg_match('/^(.+?)((\s|\r?\n).*)/us', $string, $match)) {
+ $line .= $match[1];
+ $string = $match[2];
+ }
+
+ // Wrap at existing line breaks.
+ if (preg_match('/^(.*?)(\r?\n)(.*)$/su', $line, $match)) {
+ $wrapped .= $match[1] . $match[2];
+ $string = $match[3] . $string;
+ continue;
+ }
+
+ // Wrap at the last colon or semicolon followed by a whitespace if
+ // doing line folding.
+ if ($line_folding &&
+ preg_match('/^(.*?)(;|:)(\s+.*)$/u', $line, $match)) {
+ $wrapped .= $match[1] . $match[2] . $break;
+ $string = $match[3] . $string;
+ continue;
+ }
+
+ // Wrap at the last whitespace of $line.
+ $sub = $line_folding
+ ? '(.+[^\s])'
+ : '(.*)';
+
+ if (preg_match('/^' . $sub . '(\s+)(.*)$/u', $line, $match)) {
+ $wrapped .= $match[1] . $break;
+ $string = ($line_folding ? $match[2] : '') . $match[3] . $string;
+ continue;
+ }
+
+ // Hard wrap if necessary.
+ if ($cut) {
+ $wrapped .= $line . $break;
+ continue;
+ }
+
+ $wrapped .= $line;
+ }
+
+ return $wrapped . $string;
+ }
+
+ /**
+ * Wraps the text of a message.
+ *
+ * @param string $text String containing the text to wrap.
+ * @param integer $length Wrap $text at this number of characters.
+ * @param string $break_char Character(s) to use when breaking lines.
+ * @param boolean $quote Ignore lines that are wrapped with the '>'
+ * character (RFC 2646)? If true, we don't
+ * remove any padding whitespace at the end of
+ * the string.
+ *
+ * @return string String containing the wrapped text.
+ */
+ static public function wrap($text, $length = 80, $break_char = "\n",
+ $quote = false)
+ {
+ $paragraphs = array();
+
+ foreach (preg_split('/\r?\n/', $text) as $input) {
+ if ($quote && (strpos($input, '>') === 0)) {
+ $line = $input;
+ } else {
+ /* We need to handle the Usenet-style signature line
+ * separately; since the space after the two dashes is
+ * REQUIRED, we don't want to trim the line. */
+ if ($input != '-- ') {
+ $input = rtrim($input);
+ }
+ $line = self::wordwrap($input, $length, $break_char);
+ }
+
+ $paragraphs[] = $line;
+ }
+
+ return implode($break_char, $paragraphs);
+ }
+
+ /**
+ * Return a truncated string, suitable for notifications.
+ *
+ * @param string $text The original string.
+ * @param integer $length The maximum length.
+ *
+ * @return string The truncated string, if longer than $length.
+ */
+ static public function truncate($text, $length = 100)
+ {
+ return (self::length($text) > $length)
+ ? rtrim(self::substr($text, 0, $length - 3)) . '...'
+ : $text;
+ }
+
+ /**
+ * Return an abbreviated string, with characters in the middle of the
+ * excessively long string replaced by '...'.
+ *
+ * @param string $text The original string.
+ * @param integer $length The length at which to abbreviate.
+ *
+ * @return string The abbreviated string, if longer than $length.
+ */
+ static public function abbreviate($text, $length = 20)
+ {
+ return (self::length($text) > $length)
+ ? rtrim(self::substr($text, 0, round(($length - 3) / 2))) . '...' . ltrim(self::substr($text, (($length - 3) / 2) * -1))
+ : $text;
+ }
+
+ /**
+ * Returns the common leading part of two strings.
+ *
+ * @param string $str1 A string.
+ * @param string $str2 Another string.
+ *
+ * @return string The start of $str1 and $str2 that is identical in both.
+ */
+ static public function common($str1, $str2)
+ {
+ for ($result = '', $i = 0;
+ isset($str1[$i]) && isset($str2[$i]) && $str1[$i] == $str2[$i];
+ $i++) {
+ $result .= $str1[$i];
+ }
+ return $result;
+ }
+
+ /**
+ * Returns true if the every character in the parameter is an alphabetic
+ * character.
+ *
+ * @param string $string The string to test.
+ * @param string $charset The charset to use when testing the string.
+ *
+ * @return boolean True if the parameter was alphabetic only.
+ */
+ static public function isAlpha($string, $charset)
+ {
+ if (!Horde_Util::extensionExists('mbstring')) {
+ return ctype_alpha($string);
+ }
+
+ $charset = self::_mbstringCharset($charset);
+ $old_charset = mb_regex_encoding();
+
+ if ($charset != $old_charset) {
+ @mb_regex_encoding($charset);
+ }
+ $alpha = !@mb_ereg_match('[^[:alpha:]]', $string);
+ if ($charset != $old_charset) {
+ @mb_regex_encoding($old_charset);
+ }
+
+ return $alpha;
+ }
+
+ /**
+ * Returns true if ever character in the parameter is a lowercase letter in
+ * the current locale.
+ *
+ * @param string $string The string to test.
+ * @param string $charset The charset to use when testing the string.
+ *
+ * @return boolean True if the parameter was lowercase.
+ */
+ static public function isLower($string, $charset)
+ {
+ return ((self::lower($string, true, $charset) === $string) &&
+ self::isAlpha($string, $charset));
+ }
+
+ /**
+ * Returns true if every character in the parameter is an uppercase letter
+ * in the current locale.
+ *
+ * @param string $string The string to test.
+ * @param string $charset The charset to use when testing the string.
+ *
+ * @return boolean True if the parameter was uppercase.
+ */
+ static public function isUpper($string, $charset)
+ {
+ return ((self::upper($string, true, $charset) === $string) &&
+ self::isAlpha($string, $charset));
+ }
+
+ /**
+ * Performs a multibyte safe regex match search on the text provided.
+ *
+ * @param string $text The text to search.
+ * @param array $regex The regular expressions to use, without perl
+ * regex delimiters (e.g. '/' or '|').
+ * @param string $charset The character set of the text.
+ *
+ * @return array The matches array from the first regex that matches.
+ */
+ static public function regexMatch($text, $regex, $charset = null)
+ {
+ if (!empty($charset)) {
+ $regex = self::convertCharset($regex, $charset, 'utf-8');
+ $text = self::convertCharset($text, $charset, 'utf-8');
+ }
+
+ $matches = array();
+ foreach ($regex as $val) {
+ if (preg_match('/' . $val . '/u', $text, $matches)) {
+ break;
+ }
+ }
+
+ if (!empty($charset)) {
+ $matches = self::convertCharset($matches, 'utf-8', $charset);
+ }
+
+ return $matches;
+ }
+
+ /**
+ * Check to see if a string is valid UTF-8.
+ *
+ * @param string $text The text to check.
+ *
+ * @return boolean True if valid UTF-8.
+ */
+ static public function validUtf8($text)
+ {
+ $text = strval($text);
+
+ for ($i = 0, $len = strlen($text); $i < $len; ++$i) {
+ $c = ord($text[$i]);
+
+ if ($c > 128) {
+ if ($c > 247) {
+ // STD 63 (RFC 3629) eliminates 5 & 6-byte characters.
+ return false;
+ } elseif ($c > 239) {
+ $j = 3;
+ } elseif ($c > 223) {
+ $j = 2;
+ } elseif ($c > 191) {
+ $j = 1;
+ } else {
+ return false;
+ }
+
+ if (($i + $j) > $len) {
+ return false;
+ }
+
+ do {
+ $c = ord($text[++$i]);
+ if (($c < 128) || ($c > 191)) {
+ return false;
+ }
+ } while (--$j);
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Workaround charsets that don't work with mbstring functions.
+ *
+ * @param string $charset The original charset.
+ *
+ * @return string The charset to use with mbstring functions.
+ */
+ static protected function _mbstringCharset($charset)
+ {
+ /* mbstring functions do not handle the 'ks_c_5601-1987' &
+ * 'ks_c_5601-1989' charsets. However, these charsets are used, for
+ * example, by various versions of Outlook to send Korean characters.
+ * Use UHC (CP949) encoding instead. See, e.g.,
+ * http://lists.w3.org/Archives/Public/ietf-charsets/2001AprJun/0030.html */
+ return in_array(self::lower($charset), array('ks_c_5601-1987', 'ks_c_5601-1989'))
+ ? 'UHC'
+ : $charset;
+ }
+
+ /**
+ * Strip UTF-8 byte order mark (BOM) from string data.
+ *
+ * @param string $str Input string (UTF-8).
+ *
+ * @return string Stripped string (UTF-8).
+ */
+ static public function trimUtf8Bom($str)
+ {
+ return (substr($str, 0, 3) == pack('CCC', 239, 187, 191))
+ ? substr($str, 3)
+ : $str;
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeStubphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Stub.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Stub.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Stub.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,94 @@
</span><ins>+<?php
+/**
+ * Copyright 2008-2013 Horde LLC (http://www.horde.org/)
+ *
+ * @category Horde
+ * @copyright 2008-2013 Horde LLC
+ * @license http://www.horde.org/licenses/bsd BSD
+ * @package Support
+ */
+
+/**
+ * Class that can substitute for any object and safely do nothing.
+ *
+ * @category Horde
+ * @copyright 2008-2013 Horde LLC
+ * @license http://www.horde.org/licenses/bsd BSD
+ * @package Support
+ */
+class Horde_Support_Stub
+{
+ /**
+ * Cooerce to an empty string.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return '';
+ }
+
+ /**
+ * Ignore setting the requested property.
+ *
+ * @param string $key The property.
+ * @param mixed $val The property's value.
+ */
+ public function __set($key, $val)
+ {
+ }
+
+ /**
+ * Return null for any requested property.
+ *
+ * @param string $key The requested object property.
+ *
+ * @return null Null.
+ */
+ public function __get($key)
+ {
+ return null;
+ }
+
+ /**
+ * Property existence.
+ *
+ * @param string $key The requested object property.
+ *
+ * @return boolean False.
+ */
+ public function __isset($key)
+ {
+ return false;
+ }
+
+ /**
+ * Ignore unsetting a property.
+ *
+ * @param string $key The requested object property.
+ */
+ public function __unset($key)
+ {
+ }
+
+ /**
+ * Gracefully accept any method call and do nothing.
+ *
+ * @param string $method The method that was called.
+ * @param array $args The method's arguments.
+ */
+ public function __call($method, $args)
+ {
+ }
+
+ /**
+ * Gracefully accept any static method call and do nothing.
+ *
+ * @param string $method The method that was called.
+ * @param array $args The method's arguments.
+ */
+ public static function __callStatic($method, $args)
+ {
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeSupportCaseInsensitiveArrayphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Support-CaseInsensitiveArray.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Support-CaseInsensitiveArray.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Support-CaseInsensitiveArray.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,81 @@
</span><ins>+<?php
+/**
+ * Copyright 2013 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (BSD). If you
+ * did not receive this file, see http://www.horde.org/licenses/bsd.
+ *
+ * @category Horde
+ * @copyright 2013 Horde LLC
+ * @license http://www.horde.org/licenses/bsd BSD
+ * @package Support
+ */
+
+/**
+ * An array implemented as an object that contains case-insensitive keys.
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @copyright 2013 Horde LLC
+ * @license http://www.horde.org/licenses/bsd BSD
+ * @package Support
+ */
+class Horde_Support_CaseInsensitiveArray extends ArrayIterator
+{
+ /**
+ */
+ public function offsetGet($offset)
+ {
+ return (is_null($offset = $this->_getRealOffset($offset)))
+ ? null
+ : parent::offsetGet($offset);
+ }
+
+ /**
+ */
+ public function offsetSet($offset, $value)
+ {
+ if (is_null($roffset = $this->_getRealOffset($offset))) {
+ parent::offsetSet($offset, $value);
+ } else {
+ parent::offsetSet($roffset, $value);
+ }
+ }
+
+ /**
+ */
+ public function offsetExists($offset)
+ {
+ return (is_null($offset = $this->_getRealOffset($offset)))
+ ? false
+ : parent::offsetExists($offset);
+ }
+
+ /**
+ */
+ public function offsetUnset($offset)
+ {
+ if (!is_null($offset = $this->_getRealOffset($offset))) {
+ parent::offsetUnset($offset);
+ }
+ }
+
+ /**
+ * Determines the actual array offset given the input offset.
+ *
+ * @param string $offset Input offset.
+ *
+ * @return string Real offset or null.
+ */
+ protected function _getRealOffset($offset)
+ {
+ foreach (array_keys($this->getArrayCopy()) as $key) {
+ if (strcasecmp($key, $offset) === 0) {
+ return $key;
+ }
+ }
+
+ return null;
+ }
+
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeHordeSupportRandomidphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/Horde/Support-Randomid.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/Horde/Support-Randomid.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/Horde/Support-Randomid.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,74 @@
</span><ins>+<?php
+/**
+ * Class for generating a 23-character random ID string. This string uses all
+ * characters in the class [-_0-9a-zA-Z].
+ *
+ * <code>
+ * $id = (string)new Horde_Support_Randomid();
+ * </code>
+ *
+ * Copyright 2010-2013 Horde LLC (http://www.horde.org/)
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @license http://www.horde.org/licenses/bsd BSD
+ * @package Support
+ */
+class Horde_Support_Randomid
+{
+ /**
+ * Generated ID.
+ *
+ * @var string
+ */
+ private $_id;
+
+ /**
+ * New random ID.
+ */
+ public function __construct()
+ {
+ $this->_id = $this->generate();
+ }
+
+ /**
+ * Generate a random ID.
+ */
+ public function generate()
+ {
+ $r = mt_rand();
+
+ $elts = array(
+ $r,
+ uniqid(),
+ getmypid()
+ );
+ if (function_exists('zend_thread_id')) {
+ $elts[] = zend_thread_id();
+ }
+ if (function_exists('sys_getloadavg') &&
+ $loadavg = sys_getloadavg()) {
+ $elts = array_merge($elts, $loadavg);
+ }
+
+ shuffle($elts);
+
+ /* Base64 can have /, +, and = characters. Restrict to URL-safe
+ * characters. */
+ return substr(str_replace(
+ array('/', '+', '='),
+ array('-', '_', ''),
+ base64_encode(pack('H*', hash('md5', implode('', $elts))))
+ ) . $r, 0, 23);
+ }
+
+ /**
+ * Cooerce to string.
+ *
+ * @return string The random ID.
+ */
+ public function __toString()
+ {
+ return $this->_id;
+ }
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludeclasshordeimapclienttranslationphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/class-horde-imap-client-translation.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/class-horde-imap-client-translation.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/class-horde-imap-client-translation.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,37 @@
</span><ins>+<?php
+/**
+ * Bogus translation wrapper class for Horde_Imap_Client.
+ *
+ * Uses WP __() function instead.
+ *
+ */
+class Horde_Imap_Client_Translation
+{
+ /**
+ * Returns the translation of a message.
+ *
+ * @var string $message The string to translate.
+ *
+ * @return string The string translation, or the original string if no
+ * translation exists.
+ */
+ static public function t($message)
+ {
+ return __($message);
+ }
+
+ /**
+ * Returns the plural translation of a message.
+ *
+ * @param string $singular The singular version to translate.
+ * @param string $plural The plural version to translate.
+ * @param integer $number The number that determines singular vs. plural.
+ *
+ * @return string The string translation, or the original string if no
+ * translation exists.
+ */
+ static public function ngettext($singular, $plural, $number)
+ {
+ return _n( $singular, $plural, $number );
+ }
+}
</ins></span></pre></div>
<a id="2013codebykatpostbyemailtrunkincludehordewrapperphp"></a>
<div class="addfile"><h4>Added: 2013/codebykat/post-by-email/trunk/include/horde-wrapper.php (0 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/include/horde-wrapper.php (rev 0)
+++ 2013/codebykat/post-by-email/trunk/include/horde-wrapper.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -0,0 +1,60 @@
</span><ins>+<?php
+
+/* Wrapper for Horde IMAP Client */
+
+require_once('Horde/Exception.php');
+require_once('Horde/Exception-Wrapped.php');
+
+require_once('Horde/Mail-Rfc822.php');
+require_once('Horde/Mail-Rfc822-Object.php');
+require_once('Horde/Mail-Rfc822-Address.php');
+require_once('Horde/Mail-Rfc822-List.php');
+
+require_once('Horde/Support-CaseInsensitiveArray.php');
+require_once('Horde/Support-Randomid.php');
+
+require_once('Horde/Stream-Filter-Eol.php');
+
+require_once('Horde/Mime.php');
+require_once('Horde/Mime-Headers.php');
+require_once('Horde/Mime-Part.php');
+
+require_once('Horde/Horde-Util.php');
+
+require_once('Horde/Stub.php');
+
+require_once('Horde/String.php');
+
+require_once('Horde-Translation-Wrapper.php');
+
+require_once('Horde/Client.php');
+
+require_once('Horde/Client/Base.php');
+require_once('Horde/Client/Base/Mailbox.php');
+require_once('Horde/Client/Base/Connection.php');
+
+require_once('Horde/Client/Data/Fetch.php');
+require_once('Horde/Client/Data/Fetch/Pop3.php');
+
+require_once('Horde/Client/Data/Format.php');
+require_once('Horde/Client/Data/Format/Atom.php');
+require_once('Horde/Client/Data/Format/List.php');
+
+require_once('Horde/Client/Fetch/Query.php');
+require_once('Horde/Client/Fetch/Results.php');
+
+require_once('Horde/Client/Ids.php');
+require_once('Horde/Client/Ids/Map.php');
+require_once('Horde/Client/Ids/Pop3.php');
+
+require_once('Horde/Client/Search/Query.php');
+
+require_once('Horde/Client/Socket/Pop3.php');
+require_once('Horde/Client/Socket/Connection.php');
+require_once('Horde/Client/Socket/Connection/Pop3.php');
+
+require_once('Horde/Client/Mailbox.php');
+
+require_once('Horde/Client/Exception.php');
+
+?>
</ins><span class="cx">\ No newline at end of file
</span></span></pre></div>
<a id="2013codebykatpostbyemailtrunkpostbyemailphp"></a>
<div class="modfile"><h4>Modified: 2013/codebykat/post-by-email/trunk/post-by-email.php (2184 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/post-by-email.php 2013-08-01 16:15:19 UTC (rev 2184)
+++ 2013/codebykat/post-by-email/trunk/post-by-email.php 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -12,7 +12,7 @@
</span><span class="cx"> * Plugin Name: Post By Email
</span><span class="cx"> * Plugin URI: http://codebykat.wordpress.com
</span><span class="cx"> * Description: Gets email messages from the user's mailbox to add as WordPress posts.
</span><del>- * Version: 0.9.0
</del><ins>+ * Version: 0.9.5
</ins><span class="cx"> * Author: Kat Hagan
</span><span class="cx"> * Author URI: http://profiles.wordpress.org/codebykat
</span><span class="cx"> * Text Domain: post-by-email-locale
</span></span></pre></div>
<a id="2013codebykatpostbyemailtrunkreadmetxt"></a>
<div class="modfile"><h4>Modified: 2013/codebykat/post-by-email/trunk/readme.txt (2184 => 2185)</h4>
<pre class="diff"><span>
<span class="info">--- 2013/codebykat/post-by-email/trunk/readme.txt 2013-08-01 16:15:19 UTC (rev 2184)
+++ 2013/codebykat/post-by-email/trunk/readme.txt 2013-08-01 23:53:09 UTC (rev 2185)
</span><span class="lines">@@ -2,7 +2,7 @@
</span><span class="cx"> Contributors: codebykat
</span><span class="cx"> Tags: post-by-email, email
</span><span class="cx"> Requires at least: 3.6
</span><del>-Tested up to: 3.6
</del><ins>+Tested up to: 3.7
</ins><span class="cx"> License: GPLv2 or later
</span><span class="cx"> License URI: http://www.gnu.org/licenses/gpl-2.0.html
</span><span class="cx">
</span><span class="lines">@@ -20,5 +20,9 @@
</span><span class="cx">
</span><span class="cx"> == Changelog ==
</span><span class="cx">
</span><ins>+= 0.9.5 =
+* Using Horde IMAP library instead of old SquirrelMail class (still assumes POP3 server). This fixes a
+ bug where post content was blank, and also lays some groundwork for later SSL/IMAP support.
+
</ins><span class="cx"> = 0.9 =
</span><span class="cx"> * Initial version (straight port from core)
</span><span class="cx">\ No newline at end of file
</span></span></pre>
</div>
</div>
</body>
</html>