Page MenuHomePhorge

No OneTemporary

diff --git a/CHANGELOG b/CHANGELOG
index 5cba239ea..7542fde2a 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,1618 +1,1619 @@
CHANGELOG Roundcube Webmail
===========================
+- Fix IMAP connection issue with default_socket_timeout < 0 and imap_timeout < 0 (#1489090)
- Fix various PHP code bugs found using static analysis (#1489086)
- Fix backslash character handling on vCard import (#1489085)
- Fix csv import from Thunderbird with French localization (#1489059)
- Fix messages list focus issue in Opera and Webkit (#1489058)
- Make PHP code eval() free, use create_function()
- Add option to display email address together with a name in mail preview (#1488732)
- Fix Reply-To header handling in Reply-All action (#1489037)
- Fix so Sender: address is added to Cc: field on reply to all (#1489011)
- Fix so addressbook_search_mode works also for group search (#1489079)
- Fix removal of a contact from a group in LDAP addressbook (#1489081)
- Support CSV import from Atmail (#1489045)
- Inlcude SQL query in the log on SQL error (#1489064)
- Fix handling untagged responses in IMAP FETCH - "could not load message" error (#1489074)
- Fix very small window size in Chrome (#1488931)
- Fix list page reset when viewing a message in Larry skin (#1489076)
- Fix min_refresh_interval handling on preferences save (#1489073)
- Fix PDF support detection for Firefox PDF.js (#1488972)
- Add db_prefix configuration option in place of db_table_*/db_sequence_* options
- Make possible to use db_prefix for schema initialization in Installer (#1489067)
- Fix updatedb.sh script so it recognizes also table prefix for external DDL files
- Fix possible collision in generated thumbnail cache key (#1489069)
- Fix exit code on bootsrap errors in CLI mode (#1489044)
- Fix error handling in CLI mode, use STDERR and non-empty exit code (#1489043)
- Fix error when using check_referer=true
- Fix incorrect handling of some specific links (#1489060)
- Fix incorrect handling of leading spaces in text wrapping
- Fix unintentional messages list jumps on click in Internet Explorer (#1489056)
- Fix list of required configuration options (#1489055)
- Fix DB error when creating a new contact and a group is selected (#1489051)
- Fix handling of deprecated boolean value of reply_mode option (#1489052)
- Fix parsing invalid date string (#1489035)
- Add "with attachment" option to messages list filter (#1485382)
- Call resize handler in intervals to prevent lags and double onresize calls in Chrome (#1489005)
- Add rel="noreferrer" for links in displayed messages (#1484686)
- Add ability to toggle between HTML and text while viewing a message (#1486939)
- Remove "HTML message" from attachments list while viewing a message in text mode (#1486939)
- Support IMAP MOVE extension [RFC 6851]
- Add attachment menu with Open and Download options (#1488975)
- Display user-friendly message on IMAP "over quota" errors (#1484164)
- Extended archive plugin with user-configurable options to store messages into subfolders
- Fix export of selected contacts from search result (#1488905)
- Feature to export only selected contacts from addressbook (by Phil Weir)
RELEASE 0.9.0
-------------
- Fix display of HTML entities in protected folder name (#1489042)
- Set minimal permissions to temp files (#1488996)
- Improve content check for embedded images without filename (#1489029)
- Fix handling of invalid characters in message headers and output (#1489032)
- Fix selecting collapsed rows on select-all (#1489036)
- Avoid race-conditions with concurrent attachment uploads (#1488422)
- Fix possible header duplicates when using additional headers (#1489033)
- Fix session issues with use_https=true (#1488986)
- Fix blockquote width in sent mail (#1489031)
- Fix keyboard events on list widgets in Internet Explorer (#1489025)
RELEASE 0.9-rc2
---------------
- Fix security issue in save-pref command
- Remove sig_above configuration option, use reply_mode only (#1489001)
- Refresh current folder in opener window after draft save or message sent (#1488997)
- Fix saving draft just after entering compose window (#1489012)
- Fix javascript error in IE9 when loading form with placeholders into an iframe (#1489008)
- Fix handling of some conditional comment tags in HTML message (#1489004)
- Fix so forward as attachment works if additional attachment is added by message_compose hook (#1489000)
- Better handling of session errors in ajax requests (#1488960)
- Fix HTML part detection for some specific message structures (#1488992)
- Don't show fake address - phishing prevention (#1488981)
- Fix forward as attachment bug with editormode != 1 (#1488991)
- Fix LIMIT/OFFSET queries handling on MS SQL Server (#1488984)
- Fix so task name can really contain all from a-z0-9_- characters (#1488941)
- Fix javascript errors when working in a page opened with taget="_blank"
- Mention SQLite database format change in UPGRADING file (#1488983)
- Increase maxlength to 254 chars for email input fields in addressbook (#1488987)
- Fix thumbnail size when GD extension is used for image resize (#1488985)
- Display notice that message is encrypted also for application/pkcs7-mime messages (#1488526)
RELEASE 0.9-rc
--------------
- Fix plain text spellchecker incorrect highlighting in non-ASCII text (#1488973)
- Add workaround for invalid message charset detection by IMAP servers (#1488968)
- Fix NUL characters in content-type of ms-tnef attachment (#1488964)
- Fix regression in handling LDAP contact identifiers (#1488959)
- Updated translations from Transifex
- Fix buggy error template in a frame (#1488938)
- Add addressbook widget on compose page in classic skin
- Add search box to compose address book widget (#1488381)
- Fix login in case when default_host is an array with one element (#1488928)
- Use LDAP fallback hosts on connect + bind instead of ldap_connect() only.
- Add config option for LDAP bind timeout (sets LDAP_OPT_NETWORK_TIMEOUT option)
- Submit Addressbook advanced search form with Enter key (#1488568)
- Also block remote images in HTML part view (#1488827)
- Improved database schema upgrade procedure, added updatedb.sh script
- Force autocommit mode in mysql database driver (#1488902)
RELEASE 0.9-beta
----------------
- Fix searching by date in address book (#1488888)
- Improve charset detection by prioritizing charset according to user language (#1485669)
- Fix handling of escaped separator in vCard file (#1488896)
- Add option to use envelope From address for MDN responses (#1488880)
- Add possibility to search in message body only (#1488770)
- Support "multipart/relative" as an alias for "multipart/related" type (#1488886)
- Display PGP/MIME signature attachments as "Digital Signature" (#1488570)
- Workaround UW-IMAP bug where hierarchy separator is added to the shared folder name (#1488879)
- Fix version comparisons with -stable suffix (#1488876)
- Add unsupported alternative parts to attachments list (#1488870)
- Add Compose button on message view page (#1488747)
- Display 'Sender' header in message preview
- Plugin API: Added message_before_send hook
- Fix contact copy/add-to-group operations on search result (#1488862)
- Use matching identity in MDN response (#1488864)
- Fix handling of signatures on draft edit (#1488798)
- Fix so compacting of non-empty folder is possible also when messages list is empty (#1488858)
- Allow forwarding of multiple emails (#1486854)
- Fix big memory consumption of DB layer (#1488856)
- Fix broken message/part bodies when FETCH response contains more untagged lines (#1488836)
- Fix empty email on identities list after identity update (#1488834)
- Add new identities_level: (4) one identity with possibility to edit only signature
- Use Delivered-To and Envelope-To headers for identity selection (#1488840, #1488553)
- Fix XSS vulnerability using Flash files (#1488828)
- Always save drafts with format=flowed in order to keep original line wraps (#1488799)
- Select default_addressbook on the list in Address Book (#1488280)
- Fix so mobile phone has TYPE=CELL in exported vCard (#1488812)
- Support contacts import from CSV file (#1486399)
- Improved keep-alive action. Now the interval is based on session_lifetime (#1488507)
- Added cross-task 'refresh' request for system state updates (#1488507)
- Renamed config options: keep_alive to refresh_interval, min_keep_alive to min_refresh_interval
- Fix handling of text/enriched content on message reply/forward/edit
- Option to display attached images as thumbnails below message body
- Upgraded to jQuery 1.8.3 and jQuery UI 1.9.1
- Add config option to automatically generate LDAP attributes for new entries
- Add user settings to open message view and compose form in new windows (#1485486)
- Better client-side timezone detection using the jsTimezoneDetect library (#1488725)
- Add option to disable saving sent mail in Sent folder - no_save_sent_messages (#1488686)
- Fix handling dont_override with message_sort_col and message_sort_order settings (#1488760)
- Fix handling of URLs with asterisk characters (#1488759)
- Remove automatic to-lowercase conversion of usernames (#1488715)
- Plugin API: Add 'email_list' argument for identities data in user_create hook
- Integrated zipdownload plugin to download all attachments (#1445509)
- Fix HTML special characters handling in message list/header display (#1488523)
- List related text/html part as attachment in plain text mode (#1488677)
- Use IMAP BINARY (RFC3516) extension to fetch message/part bodies
- Fix folder creation under public namespace root (#1488665)
- Fix so "Edit as new" on draft creates a new message (#1488687)
- Fix invalid error message on deleting mail from read only folder (#1488694)
- Replace data URIs of images (pasted in HTML editor) with inline attachments (#1488502)
- Remove (too big) min-width on mail screen
- Added template object 'frame'
- Add option to enable HTML editor on forwarding (#1488517)
- Add option to not include original message on reply, rename option top_posting to reply_mode (#1485149)
- Added session_path config option and unified cookies settings in javascript
- Added "Undeleted" option to messages list filter
- Rewritten test scripts for PHPUnit
- Add new DB abstraction layer based on PHP PDO, supporting SQLite3 (#1488332)
- Removed PEAR::MDB2 package
- Removed users.alias column, added option ('user_aliases')
to use email address from identities as username (#1488581)
- Removed redundant cache.cache_id column (#1488528)
- Fix order of attachments in sent mail (#1488423)
- Fix Shift + delete button does not permanently delete messages (#1488243)
- Add Content-Length for attachments where possible (#1485478)
- Fix attachment sizes in message print page and attachment preview page (#1488515)
- Add mail attachments using drag & drop on HTML5 enabled browsers
- Add workaround for invalid BODYSTRUCTURE response - parse message with Mail_mimeDecode package (#1485585)
- Display Tiff as Jpeg in browsers without Tiff support (#1488452)
- Don't display Pdf/Tiff/Flash attachments inline without browser support (#1488452, #1487929)
- Add is_escaped attribute for html_select and html_textarea (#1488485)
- Fix issue where draft auto-save wasn't executed after some inactivity time
- Add vCard import from multiple files at once (#1488015)
- Roundcube Framework:
Add possibility to replace IMAP driver with custom class
Add IMAP auto-connection feature, improving performance with caching enabled
Replace imap_init hook with storage_init (with additional 'driver' argument)
Improved performance by caching IMAP server's capabilities in session
Unified global functions naming (rcube_ prefix)
Better classes separation
Framework files moved to lib/Roundcube
RELEASE 0.8.5
-------------
- Fix #countcontrols issue in IE<=8 when text is very long (#1488890)
- Fix unwanted horizontal scrollbar in message preview header (#1488866)
- Add workaround for IE<=8 bug where Content-Disposition:inline was ignored (#1488844)
- Fix XSS vulnerability in vbscript: and data:text links handling (#1488850)
- Fix absolute positioning in HTML messages (#1488819)
- Fix cache (in)validation after setting \Deleted flag
- Fix keybord events on messages list in opera browser (#1488823)
- Fix selection of collapsed thread rows (#1488772)
- Fix wrapping of quoted text with format=flowed (#1488177)
RELEASE 0.8.4
-------------
- Fix regression where unintentional page reload was done after request abort (#1488802)
- Fix XSS vulnerability in handling of text/enriched messages (#1488806)
- Fix handling of 'media' attribute on linked css (#1488789)
- Fix excessive LFs at the end of composed message with top_posting=true (#1488797)
- Fix bug where leading blanks were stripped from quoted lines (#1488795)
RELEASE 0.8.3
-------------
- Fix AREA links handling (#1488792)
- Fix possible HTTP DoS on error in keep-alive requests (#1488782)
- Fix compatybility with MDB2 2.5.0b4 (#1488779)
- Fix a bug where saving a message in INBOX wasn't possible
- Fix HTML part detection in messages with attachments (#1488769)
- Fix bug where wrong words were highlighted on spell-before-send check
- Fix scrolling quirk in email preview frame using Opera 12 (#1488763)
- Fix displaying of multipart/alternative messages with empty parts (#1488750)
- Fix threaded list sorting on PHP < 5.2.9 (#1488748)
- Fix Warning: htmlspecialchars(): charset `RCMAIL_CHARSET' not supported warning in Installer (#1488744)
RELEASE 0.8.2
-------------
- Fix XSS vulnerability from HTTP User-Agent header (#1488737)
- Force fonts in compose fields to be all the same (#1488690)
- Fix handling vCard entries with TEL;TYPE=CELL (#1488728)
- Fix error where session wasn't updated after folder rename/delete (#1488692)
- Fix PLAIN authentication for some IMAP servers (#1488674)
- Fix encoding vCard file when contains PHOTO;ENCODING=b (#1488683)
- Fix focus issue in IE when selecting message row (#1488620)
- Add full headers view in message preview window (#1488538)
- Fix message display page issues - unified with message preview (#1488590, #1488642)
- Fix displaying all headers when they contain malformed characters (#1488666)
- Fix decoding of HTML messages with UTF-16 charset specified (#1488654)
- Fix quota capability detection so it can be overwritten by a plugin (#1488655)
- Fix identity selection on reply (#1488101)
- Fix Larry's messages list filter in IE (#1488632)
- Fix more IE issues by disabling Compat. mode with X-UA-Compatible meta tag (#1488626)
- Fix setting locales under Solaris - use additional .UTF-8 suffix (#1488628)
- Fix email address validation for addresses with IP address in domain part
- Fix Larry skin issues in IE7 compat. mode (#1488618)
- Fix so subscribed non-existing/non-accessible shared folder can be unsubscribed
RELEASE 0.8.1
-------------
- Fix bug where domain name was converted to lower-case even with login_lc=false (#1488593)
- Fix lower-casing email address on replies (#1488598)
- Fix line separator in exported messages (#1488603)
- Fix XSS issue where plain signatures wasn't secured in HTML mode (#1488613)
- Fix XSS issue where href="javascript:" wasn't secured (#1488613)
- Fix impossible to create message with empty plain text part (#1488610)
- Fix stripped apostrophes when replying in plain text to HTML message (#1488606)
- Fix inactive Save search option after advanced search (#1488607)
- Fix Remove from group option is active for contact search result (#1488608)
- Disable autocapitalization in login form on iPad/iPhone (#1488609)
- Fix focus on the list when list row is clicked (#1488600)
- Added separate From and To columns apart from smart From/To column (#1486891)
- Fix fallback to Larry skin when configured skin isn't available (#1488591)
- Fix (workaround) delete operations with some versions of memcache (#1488592)
- Fix (disable) request validation for spell and spell_html actions
RELEASE 0.8.0
-------------
- Don't show product version on login screen (can be enabled by config)
- Renamed old default skin to 'classic'. Larry is the new default skin.
- Support connections to memcached socket file (#1488577)
- Enable TinyMCE inlinepopups plugin
- Update to TinyMCE 3.5.6
- Correctly escape localized labels in javascript variable (#1488567)
- Update Net_SMTP/Auth_SASL packages to fix Digest-MD5/Cram-MD5 authentication (#1488571)
- Don't add attachments content into reply/forward/draft message body (#1488557)
- Fix 'no connection' errors on page unloads (#1488547)
- Plugin API: Add 'unauthenticated' hook (#1488138)
- Show explicit error message when provided hostname is invalid (#1488550)
- Fix wrong compose screen elements focus in IE9 (#1488541)
- Fix fatal error when date.timezone isn't set (#1488546)
- Update to TinyMCE 3.5.4.1
- Better icons with distinct shapes for priority columns (#1488377)
- Show dedicated icon for multipart/report messages (#1488524)
- Properly hide text of icon links/buttons (#1488534)
- Fix handling of unitless CSS size values in HTML message (#1488535)
- Fix removing contact photo using LDAP addressbook (#1488420)
- Fix storing X-ANNIVERSARY date in vCard format (#1488527)
- Update to Mail_Mime-1.8.5 (#1488521)
- Fix XSS vulnerability in message subject handling using Larry skin (#1488519)
- Fix handling of links with various URI schemes e.g. "skype:" (#1488106)
- Fix handling of links inside PRE elements on html to text conversion
- Fix indexing of links on html to text conversion
- Decode header value in rcube_mime::get() by default (#1488511)
- Fix errors with enabled PHP magic_quotes_sybase option (#1488506)
- Fix SQL query for contacts listing on MS SQL Server (#1488505)
- Fix window.resize handler on IE8 and Opera (#1488453)
- Don't let error message popups cover the login form (#1488500)
- Update to TinyMCE 3.5.2
- Don't show errors when moving contacts into groups they are already in (#1488493)
- Make folders with unread messages in subfolders bold again (#1486793)
- Abbreviate long attachment file names with ellipsis (#1488499)
- Fix html2text conversion of strong|b|a|th|h tags when used in upper case
- Add listcontrols template container in Larry skin (#1488498)
- Fix host autoselection when default_host is an array (#1488495)
- Move messages forwarding mode setting into Preferences
- Fix HTML entities handling in HTML editor (#1488483)
- Fix listing shared folders on Courier IMAP (#1488466)
RELEASE 0.8-rc
--------------
- Added new translations in Belarusian, Interlingua and Malayalam
- Flipped compose options arrow (#1488474)
- Fix handling of large uuencode attachments (#1488473)
- Fix handling of "usemap" attribute (#1488472)
- Fix handling of some HTML tags e.g. IMG (#1488471)
- Use similar language as a fallback for plugin localization (#1488401)
- Fix issue where signature wasn't re-added on draft compose (#1488322)
- Update to TinyMCE 3.5 (#1488459)
- Fixed multi-threaded autocompletion when number of threads > number of sources
- Allow to configure the number of values allowed for each LDAP attribute
- Support for serialized LDAP address values (usually delimited with a $)
- Less restrictive session auth checks, repeat keep-alive requests on failure (#1488449)
- Fix redirect to mail/compose on re-login (#1488226)
- Add IE8 hack for messages list issue (#1487821)
- Fix handling errors on draft auto-save
- Fix importing vCard photo with ENCODING param specified (#1488432)
- Support mutliple name/email pairs for Bcc and Reply-To identity settings (#1488445)
- Set flexible width to login form fields (#1488418)
- Fix re-draw bug on list columns change in IE8 (#1487822)
- Allow mass-removal of addresses from a group (#1487748)
- Fix removing all contacts on import to LDAP addressbook
- Fix so "Back" from compose/show doesn't reset search request (#1488238)
- Add option to delete messages instead of moving to Trash when in Junk folder (#1486686)
- Fix invisible cursor when replying to a html message (#1487073)
- Reset IP stored in session when destroying session data (#1488056)
- Fix bug where memory_limit = -1 wasn't handled properly
- Support LDAP RFC2256's country object class read/write (#1488123)
- Upgraded to jQuery 1.7.2
- Image resize with GD extension (#1488383)
- Fix lack of warning when switching task in compose window (#1488399)
- Fix bug where it wasn't possible to enter ( or & characters in autocomplete fields
- Request all needed fields from address book backends (#1488394)
- Unified (single) spellchecker button
- Scroll long lists on drag&drop (#1485946)
- Copy all skins in installto script (#1488376)
RELEASE 0.8-beta
----------------
- Upgraded to jQuery 1.7.1 (#1488337) and jQuery UI 1.8.18
- Add Russian to the spellchecker languages list (#1488135)
- Remember custom skin selection after logout (#1488355)
- Make sure About tab is always the last tab (#1488257)
- Fix issue with folder creation under INBOX. namespace (#1488349)
- Added mailto: protocol handler registration link in User Preferences (#1486580)
- Handle identity details box with an iframe (#1487020)
- Fix issue where some text from original message was missing on reply (#1488340)
- Fix autoselect_host() for login (#1488297)
- Changed license to GNU GPLv3+ with exceptions for skins & plugins
- Added address book widget on compose screen
- Use proper timezones from PHP's internal timezonedb (#1485592)
- Add separate pagesize setting for mail messages and contacts (#1488269)
- Deprecate $DB, $USER, $IMAP global variables, Use $RCMAIL instead
- Add option to set default font for HTML message (#1484137)
- Fix issues with big memory allocation of IMAP results
- Prevent from memory_limit exceeding when trying to parse big messages bodies (#1487424)
- Add possibility to add SASL mechanisms for SMTP in smtp_connect hook (#1487937)
- Mark (with different color) folders with recent messages (#1486234)
- Added About tab in Settings
- TinyMCE updated to 3.4.6
RELEASE 0.7.2
-------------
- Fix encoding of attachment with comma in name (#1488389)
- Fix handling of % character in IMAP protocol (#1488382)
- Fix duplicate names handling in addressbook searches (#1488375)
- Fix displaying of HTML messages from Disqus (#1488372)
- Disable E_STRICT warnings on PHP 5.4
- Prevent from folder selection on virtual folder collapsing (#1488346)
- Fix automatic unsubscribe of non-existent folders
- Fix double-quotes handling in recipient names
- User configurable setting how to display contact names in list
- Make contacts list sorting configurable for the admin/user
- Fix parse errors in DDL files for MS SQL Server
- Revert SORT=DISPLAY support, removed by mistake (#1488327)
- Add lost translation label in de_DE (#1488315)
- Fix drafts update issues when edited from preview pane (#1488314)
- Fix wrong variable name in rcube_ldap.php (#1488302)
- Make mime type detection based on filename extension to be case-insensitive
- Fix failure on MySQL database upgrade from 0.7 - text column can't have default value (#1488300)
RELEASE 0.7.1
-------------
- Fix bug in handling of base href and inline content (#1488290)
- Fix SQL Error when saving a contact with many email addresses (#1488286)
- Fix strict email address searching if contact has more than one address
- Remove duplicated 'organization' label (#1488287)
- Fix so editor selector is hidden when 'htmleditor' is listed in 'dont_override'
- Fix wrong (long) label usage (#1488283)
- Fix handling of INBOX's subfolders in special folders config (#1488279)
- Add ifModule statement for setting Options -Indexes in .htaccess file (#1488274)
- Fix crashes with eAccelerator (#1488256)
- Fix searching on IMAP servers without CHARSET specifier support (#1488271)
- Fix expanding folders during drag&drop (#1488260)
- Fix wrong postgres sequence name in upgrade from 0.6
- Fix broken CREATE INDEX queries in SQLite DDL files (#1488255)
RELEASE 0.7
-----------
- Make Roundcube render the Email Standards Project Acid Test correctly
- Replace prompt() with jQuery UI dialog (#1485135)
- Fix navigation in messages search results
- Improved handling of some malformed values encoded with quoted-printable (#1488232)
- Add possibility to do LDAP bind before searching for bind DN
- Fix handling of empty <U> tags in HTML messages (#1488225)
- Add content filter for embedded attachments to protect from XSS on IE (#1487895)
- Use strpos() instead of strstr() when possible (#1488211)
- Fix handling HTML entities when converting HTML to text (#1488212)
- Fix fit_string_to_size() renders browser and ui unresponsive (#1488207)
- Fix handling of invalid characters in request (#1488124)
- Fix merging some configuration options in update.sh script (#1485864)
- Fix so TEXT key will remove all HEADER keys in IMAP SEARCH (#1488208)
- Fix handling contact photo url with https:// prefix (#1488202)
- Fix possible infinite redirect on attachment preview (#1488199)
- Improved clickjacking protection for browsers which don't support X-Frame-Options headers
- Fixed bug where similar folder names were highlighted wrong (#1487860)
- Fixed bug in handling link with '!' character in it (#1488195)
- Fixed bug where session ID's length was limited to 40 characters (#1488196)
- TinyMCE security issue: removed moxieplayer (embedding flv and mp4 is not supported anymore)
RELEASE 0.7-beta
----------------
- Fix handling of HTML form elements in messages (#1485137)
- Fix regression in setting recipient to self when replying to a Sent message (#1487074)
- Fix listing of folders in hidden namespaces (#1486796)
- Don't consider \Noselect flag when building folders tree (#1488004)
- Fix sorting autocomplete results (#1488084)
- Add option to set session name (#1486433)
- Add option to skip alternative email addresses in autocompletion
- Fix inconsistent behaviour of Compose button in Drafts folder, add Edit button for drafts
- Fix problem with parsing HTML message body with non-unicode characters (#1487813)
- Add option to define matching method for addressbook search (#1486564, #1487907)
- Make email recipients separator configurable
- Fix so folders with \Noinferiors attribute aren't listed in parent selector
- Fix handling of curly brackets in URLs (#1488168)
- Fix handling of dates (birthday/anniversary) in contact data (#1488147)
- Fix error on opening searched LDAP contact (#1488144)
- Fix redundant line break in flowed format (#1488146)
- Fix IDN address validation issue (#1488137)
- Fix JS error when dst_active checkbox doesn't exist (#1488133)
- Autocomplete LDAP records when adding contacts from mail (#1488073)
- Plugin API: added 'ready' hook (#1488063)
- Ignore DSN request when it isn't supported by SMTP server (#1487800)
- Make sure LDAP name fields aren't arrays (#1488108)
- Fixed imap test to non-default port when using ssl (#1488118)
- Force all files to be overwritten when updating (#1488117)
- Fix issue where it wasn't possible to change list view mode in folder manager for INBOX (#1488107)
- Fix namespace handling in special folders settings (#1488112)
- Disable time limit for CLI scripts (#1488109)
- Fix misleading display when chaning editor type (#1488104)
- Add loading indicator on contact delete
- Fix bug where after delete message rows can be added to the list of another folder (#1487752)
- Add notice on autocompletion that not all records were displayed
- Add option 'searchonly' for LDAP address books
- Add Priority filter to the messages list
- Cache synchronization using QRESYNC/CONDSTORE
- Trigger 'new_messages' hook for all checked folders (#1488083)
- Make date/time format user configurable; drop 'date_today' config option
- Fix setting title for truncated subject in IE (#1487128)
- Fix displaying multipart/alternative messages with only one part (#1487938)
- Rewritten messages caching:
Indexes are stored in a separate table, so there's no need to store all messages in a folder
Added threads data caching
Flags are stored separately, so flag change doesn't cause DELETE+INSERT, just UPDATE
- Improved FETCH response handling
- Improvements in response tokenization method
- Use 'From' and 'To' labels instead of 'Sender' and 'Recipient'
- Fix username case-insensitivity issue in MySQL (#1488021)
- Addressbook Saved Searches
- Added spellchecker exceptions dictionary (shared or per-user)
- Added possibility to ignore words containing caps, numbers, symbols (spellcheck_ignore_* options)
- Added 'priority' column on messages list (#1486782)
- Localize forwarded message header (#1488058)
RELEASE 0.6
-----------
- Fix bug where the last identity is used on reply (#1488101)
- Fix locked folder rename option on servers supporting RFC2086 only (#1488089)
- Fix session race conditions when composing new messages
- Fix encoding of LDAP contacts identifiers (#1488079)
- jQuery 1.6.4
- Fix handling of binary attachments encoded with quoted-printable (#1488065)
- Fix text-overflow:ellipsis issues on messages list in FF7 and Webkit (#1488061)
- Fix handling of links with IP address
- Fix compacting folder resets message list filter (#1488076)
RELEASE 0.6-rc
----------------
- Send X-Frame-Options headers to protect from clickjacking (#1487037)
- Fallback to mail_domain in LDAP variable replacements; added 'host' to 'user_create' hook arguments (#1488024)
- Fixed wrong vCard type parameter mobile (#1488067)
- Fixed vCard WORKFAX issue (#1488046)
- Add vCard's Profile URL support (#1488062)
- jQuery 1.6.3
- Fix imap_cache setting to values other than 'db' (#1488060)
- Fix handling of attachments inside message/rfc822 parts (#1488026)
- Make list of mimetypes that open in preview window configurable (#1487625)
- Added plugin hook 'message_part_get' for attachment downloads
- Added unique connection identifier to IMAP debug messages
- Fix image type check for contact photo uploads
RELEASE 0.6-beta
----------------
- Fixed selecting identity on reply/forward (#1487981)
- Add option to hide selected LDAP addressbook on the list
- Add client-side checking of uploaded files size
- Add newlines between organization, department, jobtitle (#1488028)
- Recalculate date when replying to a message and localize the cite header (#1487675)
- Fix handling of email addresses with quoted local part (#1487939)
- Fix EOL character in vCard exports (#1487873)
- Added optional "multithreading" autocomplete feature
- Plugin API: Added 'config_get' hook
- Fixed new_user_identity plugin to work with updated rcube_ldap class (#1487994)
- Plugin API: added folder_delete and folder_rename hooks
- Added possibility to undo last contact delete operation
- Fix sorting of contact groups after group create (#1487747)
- Add optional textual upload progress indicator (#1486039)
- Fix parsing URLs containing commas (#1487970)
- Added vertical splitter for books/groups list in addressbook (#1487923)
- Improved namespace roots handling in folder manager
- Added searching in all addressbook sources
- Added addressbook source selection in contacts import
- Implement LDAPv3 Virtual List View (VLV) for paged results listing
- Use 'address_template' config option when adding a new address block (#1487944)
- Added addressbook advanced search
- Add popup with basic fields selection for addressbook search
- Case-insensitive matching in autocompletion (#1487933)
- Added option to force spellchecking before sending a message (#1485458)
- Fix handling of "<" character in contact data, search fields and folder names (#1487864)
- Fix saving "<" character in identity name and organization fields (#1487864)
- Added option to specify to which address book add new contacts
- Added plugin hook for keep-alive requests
- Store user preferences in session when write-master is not available and session is stored in memcache, write them later
- Improve performence of folder manager operations
- Fix default_port option handling in Installer when config.inc.php file exists (#1487925)
- Removed option focus_on_new_message, added newmail_notifier plugin
- Added general rcube_cache class with Memcache and APC support
- Improved caching performance by skipping writes of unchanged data
- Option enable_caching replaced by imap_cache and messages_cache options
- Fix WORKFAX saving in address book (#1487910)
- Add forward-as-attachment feature
- jQuery-1.6.2 (#1487913, #1487144)
- Improve display name composition when saving contacts (#1487143)
- Fix problems with subfolders of INBOX folder on some IMAP servers (#1487725)
- Fix handling of folders that doesn't belong to any namespace (#1487637)
- Enable multiselection for attachments uploading in capable browsers (#1485969)
- Add possibility to change HTML editor configuration by skin
- Fix a bug where selecting too many contacts would produce too large URI request (#1487892)
- Improve performance by including files with absolute path (#1487849)
- Move folder name truncation to client/skin (#1485412)
- Added plugin hook for request token creation
- Replace LDAP vars in group queries (#1487837)
- Fix vcard folding with uncode characters (#1487868)
- Keep all submitted data if contact form validation fails (#1487865)
- Handle uncode strings in rcube_addressbook::normalize_string() (#1487866)
- Fix handling of debug_level=4 in ajax requests (#1487831)
- Enable TinyMCE's contextmenu (#1487014)
- Allow multiple concurrent compose sessions
- New config option for custom logo
- Allow skins to define/override texts with <roundcube:label />
- Add simple ACL rights/namespace handling in folder manager
- Force IE to send referers (#1487806)
- Better display of vcard import results (#1485457)
- Improved vcard import
- Interactive update script with improved DB schema check
- Fix problem with contactgroupmembers table creation on MySQL 4.x, add index on contact_id column
- Add LDAP SASL bind and proxy authentication (#1486692)
- Replying to a sent message puts the old recipient as the new recipient (#1487074)
- Fulltext search over (almost) all data for contacts
- Extend address book with rich contact information
RELEASE 0.5.4
-------------
- Fix XSS vulnerability in UI messages (#1488030)
RELEASE 0.5.3
-------------
- Fix identities "reply-to" and "bcc" fields have a bogus value when left empty (#1487943)
- Fix issue which cases IMAP disconnection when encrypt() method was used (#1487900)
- Fix some CSS issues in Settings for Internet Explorer
- Fixed handling of folder with name "0" in folder selector
- Fix bug where messages were deleted instead moved to trash folder after Shift key was used (#1487902)
- Fix relative URLs handling according to a <base> in HTML (#1487889)
- Fix handling of top-level domains with more than 5 chars or unicode chars (#1487883)
- Fix usage of non-standard HTTP error codes (#1487797)
- Fix PHP warning on mistaken in_array() usage (#1487901)
RELEASE 0.5.2
-------------
- TinyMCE 3.4.2 now compatible with IE9
- PEAR::Net_SMTP 1.5.2, fixed timeout issue (#1487843)
- Fix bug where template name without plugin prefix was used in render_page hook
- Support 'abort' and 'result' response in 'preferences_save' hook, add error handling
- Fix bug where some content would cause hang on html2text conversion (#1487863)
- Improve space-stuffing handling in format=flowed messages (#1487861)
- Fix bug where some dates would produce SQL error in MySQL (#1487856)
- Added workaround for some IMAP server with broken STATUS response (#1487859)
- Fix bug where default_charset was not used for text messages (#1487836)
- Stateless request tokens. No keep-alive necessary on login page (#1487829)
- Force names of unique constraints in PostgreSQL DDL
- Add code for prevention from IMAP connection hangs when server closes socket unexpectedly
- Remove redundant DELETE query (for old session deletion) on login
- Get around unreliable rand() and mt_rand() in session ID generation (#1486281)
- Fix some emails are not shown using Cyrus IMAP (#1487820)
- Fix handling of mime-encoded words with non-integral number of octets in a word (#1487801)
- Fix parsing links with non-printable characters inside (#1487805)
- Fixed de_CH Localization bugs (#1487773)
- Add variable for 'Today' label in date_today option (#1486120)
- Fix dont_override setting does not override existing user preferences (#1487664)
- Use only one from IMAP authentication methods to prevent login delays (1487784)
- Support strftime format in date_today option
- Fix SQL query in rcube_user::query() so it uses index on MySQL again
- Removed redundant </form> tags from contact add/edit pages
- Fix CSS error in contact details screen on IE7 (#1487775)
RELEASE 0.5.1
-------------
- Fix handling of attachments with invalid content type (#1487767)
- Add workaround for DBMail's bug http://www.dbmail.org/mantis/view.php?id=881 (#1487766)
- Use IMAP's ID extension (RFC2971) to print more info into debug log
- Security: add optional referer check to prevent CSRF in GET requests
- Fix email_dns_check setting not used for identities/contacts (#1487740)
- Fix ICANN example addresses doesn't validate (#1487742)
- Security: protect login form submission from CSRF
- Security: prevent from relaying malicious requests through modcss.inc
- Fix handling of non-image attachments in multipart/related messages (#1487750)
- Fix IDNA support when IDN/INTL modules are in use (#1487742)
- Fix handling of invalid HTML comments in messages (#1487759)
- Fix parsing FETCH response for very long headers (#1487753)
- Fix add/remove columns in message list when message_sort_order isn't set (#1487751)
- Check mime headers before attempt to parse them (#1487745)
- Quote header values in show_additional_headers plugin (#1487744)
- Fix settings UI on IE 6 (#1487724)
- Remove double borders in folder listing (#1487713)
- Separate full message headers UI element from headers table (#1487715)
- Add part MIME ID to message_part_* hooks (#1487718)
- Improve parsing of MS Outlook vCards (#1487716)
- Updated PEAR::Net_Socket to 1.0.10
- Updated PEAR::Net_IDNA2 to 0.1.1
- Fix handling of comments inside an email address spec. (#1487673)
- Show full mail subject as title when hovering a cut subject link (#1487128)
- Fix randomly disappearing folders list in IE (#1487704)
- Fix list column add/removal in IE (#1487703)
- Fix login redirect issues (#1487686)
- Require PHP 5.2.1 or greater
- Fix %h/%z variables in username_domain option (#1487701)
- Workaround for setting charset in case of malformed bodystructure response (#1487700)
- Fix impossible to subscribe to protected folders (#1487656)
- Fix setting timezone in Preferences (#1487705)
RELEASE 0.5
-----------
- Fix double-login/session issue (#1487104)
- Wrap HTML parts with <html><body> and add Doctype declaration (#1487098)
- Make rcube_autoload silently skip unknown classes (#1487109)
- Fix charset detection in vcards with encoded values (#1485542)
- Better CSS cursors for splitters (#1486874)
- Show the same message only once (#1487641)
- Fix namespaces handling (#1487649)
- Add handling of multifolder METADATA/ANNOTATION responses
- Fix handling of INBOX when personal namespace prefix is non-empty (#1487657)
- Fix handling square brackets in links (#1487672)
- Add description of 'use_https' option in main.inc.php.dist file
RELEASE 0.5-RC
--------------
- Plugin API: Add 'pass' argument in 'authenticate' hook (#1487134)
- Fix attachments of type message/rfc822 are not listed on attachments list
- Add 'login_lc' config option for case-insensitive authentication (#1487113)
- Fix window is blur'ed in IE when selecting a message (#1487316)
- Fix cursor position on compose form in Webkit browsers (#1486674)
- Fix setting charset of attachment filenames (#1487122)
- Allow setting autocomplete attribute for all inputs separately (#1487313)
- New Folder Manager UI
- Fix invalid Request when creating a folder (#1487443)
- Add folder size and quota indicator in folder manager (#1485780)
- Add possibility to move a subfolder into root folder (#1486791)
- Fix copying all messages in a folder copies only messages from current page
- Improve performance of moving or copying of all messages in a folder
- Fix plaintext versions of HTML messages don't contain placeholders for emotions (#1485206)
- Improve performance of folder rename and delete actions
- Better support for READ-ONLY and NOPERM responses handling (#1487083)
- Add confirmation message on purge/expunge command response
- Fix handling of untagged responses for AUTHENTICATE command (#1487450)
- Add username and IP address to log message on unsuccessful login (#1487626)
- Improved Mail-Followup-To and Mail-Reply-To headers handling
- Fix charset conversion for text attachments without charset specification (#1487634)
RELEASE 0.5-BETA
----------------
- Make session data storage more robust against garbage session data (#1487136)
- Config option for autocomplete on login screen
- Allow plugin templates to include local files (#1487133)
- List groups in address detail view and allow to subscribe/unsubscribe from there (#1486753)
- Messages caching: performance improvements, fixed syncing, fixes related with #1486748
- Add link to identities in compose window (#1486729)
- Add Internationalized Domain Name (IDNA) support (#1483894)
- Add option to automatically send read notifications for known senders (#1485883)
- Add option to "Return receipt" will be always checked (#1486352)
- Fix HTML to plain text conversion doesn't handle citation blocks (#1486921)
- Use custom sorting when SORT is disabled by IMAP admin (#1486959)
- Allow setting some washtml options from plugin (#1486578)
- Add option do bind for an individual LDAP address book (#1486997)
- Change reply prefix to display email address only if sender name doesn't exist (#1486550)
- Plugin API: improved 'abort' flag handling, added 'result' item in some hooks (#1486914)
- Fix mailto optional params in plain text messages aren't handled (#1487026)
- Add Reply-to-List feature (#1484252)
- Add Mail-Followup-To/Mail-Reply-To support (#1485547)
- Fix confirmation message isn't displayed after sending mail on Chrome (#1486177)
- Fix keyboard doesn't work with autocomplete list with Chrome (#1487029)
- Improve tabs to fixed width and add tabs in identities info (#1486974)
- Add unique index on users.username+users.mail_host
- Make htmleditor option more consistent and add option to use HTML on reply to HTML message (#1485840)
- Use empty envelope sender address for message disposition notifications (RFC 2298.3)
- Support SMTP Delivery Status Notifications - RFC 3461 (#1486142)
- Use css sprite image for messages list
- Add (different) attachment icon for messages of type multipart/report (#1486165)
- Prevent from inserting empty link when composing HTML message (#1486944)
- Add caching support in id2uid and uid2id functions (#1487019)
- Add SASL proxy authentication for SMTP (#1486693)
- Improve displaying of UI messages (#1486977)
- Fix double e-mail filed in identity form (#1487054)
- Display IMAP errors for LIST/THREAD/SEARCH commands (#1486905)
- Add LITERAL+ (IMAP4 non-synchronizing literals) support (RFC 2088)
- Add separate column for message status icon (#1486665)
- Add ACL extension support into IMAP classes (RFC 4314)
- Add ANNOTATEMORE extension support into IMAP classes (draft-daboo-imap-annotatemore)
- Add METADATA extension support into IMAP classes (RFC 5464)
- Fix decoding of e-mail address strings in message headers (#1487068)
- Fix handling of attachments when Content-Disposition is not inline nor attachment (#1487051)
- Improve performance of unseen messages counting (#1487058)
- Improve performance of messages counting using ESEARCH extension (RFC4731)
- Add LIST-STATUS support in rcube_imap_generic class (RFC 5819)
- Add SASL-IR support in IMAP (RFC 4959)
- Add LOGINDISABLED support (RFC 2595)
- Add support for AUTH=PLAIN in IMAP authentication
- Re-implemented SMTP proxy authentication support
- Add support for IMAP proxy authentication (#1486690)
- Add support for AUTH=DIGEST-MD5 in IMAP (RFC 2831)
- Fix parent folder with unread subfolder not bold when message is open (#1487078)
- Add basic IMAP LIST's \Noselect option support
- Add support for selection options from LIST-EXTENDED extension (RFC 5258)
- Don't list subscribed but non-existent folders (#1486225)
- Fix handling of URLs with tilde (~) or semicolon (;) character (#1487087, #1487088)
- Plugin API: added 'contact_form' hook
- Add SORT=DISPLAY support (RFC 5957)
- Plugin API: add possibility to disable plugin in AJAX mode, 'noajax' property
- Plugin API: add possibility to disable plugin in framed mode, 'noframe' property
- Improve performance of setting IMAP flags using .SILENT suffix
- Improve performance of message cache status checking with skip_disabled=true
- Support contact's email addresses up to 255 characters long (#1487095)
- Add option to place replies in the folder of the message being replied to (#1485945)
- Add missing confirmation/error messages on contact/group/message actions (#1486845)
- Add 'loading' message on message move/copy/delete/mark actions
- Improve responsiveness of messages displaying (#1486986)
- Add option for minimum length of autocomplete's string (#1486428)
- Fix operations on messages in unsubscribed folder (#1487107)
- Add support for shared folders (#1403507)
- Fix handling of folders with name "0" (#1487119)
- Fix handling of folders with "<>" characters in name
- jQuery 1.4.4
- Fix handling of HTML entity strings in plain text messages
- Fix focused elements aren't unfocused when clicking on the list (#1487123)
- Fix error in MSSQL DDL scripts (#1487112)
- Lock submit button in onsubmit event on login page (#1487036)
- Don't set attachment's charset in Content-type header (#1487122)
- Fix handling of message bodies (quoted-printable encoded) with NULL characters (#1486189)
- Add workaround for MSOE's multipart/related messages with non-related attachments
RELEASE 0.4.2
-------------
- Fix handling of backslash as IMAP delimiter
- Fix charset replacement in HTML message bodies (#1487021)
- Fix: contact group input is empty when using rename action more than once on the same group record
- Fix "Server Error! (Not Found)" when using utils/save-pref action (#1487023)
- Fix handling of Thunderbird's vCards (#1487024)
RELEASE 0.4.1
-------------
- Fix space-stuffing in format=flowed messages (#1487018)
- Fix msgexport.sh now using the new imap wrapper
- Avoid displaying password on shell (#1486947)
- Only lower-case user name if first login attempt failed (#1486393)
- Make alias setting in squirrelmail_usercopy plugin configurable (patch by pommi, #1487007)
- Prevent from saving a non-existing skin path in user prefs (#1486936)
- Improve handling of single-part messages with bogus BODYSTRUCTURE (#1486898)
- Fix path to SQL files when using pgsql/mysqli/sqlsrv drivers (#1486902)
- Fix upgrade script for SQLite (#1486903)
- Fixes in SQL init script + added update script for MSSQL database
- Remove redundant date in syslog messages (#1486945)
- Fix contacts list page controls when a group is selected (#1486946)
- Fix SMTP test in Installer (#1486952)
- Fix "Select all" causes message to be opened in folder with exactly one message (#1486913)
- Fix Tab key doesn't work in HTML editor in Google Chrome (#1486925)
- Fix TinyMCE uses zh_CN when zh_TW locale is set (#1486929)
- Fix TinyMCE buttons are hidden in Opera (#1486922)
- Fix JS error on IE when trying to send HTML message with enabled spellchecker (#1486940)
- Display inline images with known extensions and non-image content-type (#1486934)
- Fix "Threaded" checkbox after subfolder creation (#1486928)
- Fix timezone string in sent mail (#1486961)
- Show disabled checkboxes for protected folders instead of dots (#1485498)
- Added fieldsets in Identity form, added 'identity_form' hook
- Re-added 'Close' button in upload form (#1486930, #1486823)
- Fix handling of charsets with LATIN-* label
- Fix messages background image handling in some cases (#1486990)
- Fix format=flowed handling (#1486989)
- Fix when IMAP connection fails in 'get' action session shouldn't be destroyed (#1486995)
- Fix list_cols is not updated after column dragging (#1486999)
- Support %z variable in host configuration options (#1487003)
RELEASE 0.4
-----------
- Fix disapearing upload form disapears when user selects a file on Safari (#1486823)
- Don't replace error messages with loading info (#1486300)
- Fix JS errors on compose mode switch (#1486870)
- Fix message structure parsing when it lacks optional fields (#1486881)
- Include all recipients in sendmail log
- Support HTTP_X_FORWARDED_PROTO header for HTTPS detecting (#1486866)
- Fix default IMAP port configuration (#1486864)
- Create Sent folder when starting to compose a new message (#1486802)
- Fix handling of messages with Content-Type: application/* and no filename (#1484050)
- Improved compose screen: resizable body and attachments list, vertical splitter, options menu
- Fix RC forgets search results (#1483883)
- TinyMCE 3.3.7
- Improve parsing of styled empty tags in HTML messages (#1486812)
- Add %dc variable support in base_dn/bind_dn config (#1486779)
- Add button to hide/unhide the preview pane (#1484215)
- Fix no-cache headers on https to prevent content caching by proxies (#1486798)
- Fix attachment filenames broken with TNEF decoder using long filenames (#1486795)
- Use user's timezone in Date header, not server's timezone (#1486119)
- Add option to set separate footer for HTML messages (#1486660)
- Add real SMTP error description to displayed error messages (#1485927)
- Fix some IMAP errors handling when opening the message (#1485443)
- Fix related parts aren't displayed when got mimetype other than image/* (#1486432)
- Multiple identity and database support for squirrelmail_usercopy plugin (#1486517)
- Support dynamic hostname (%d/%n) variables in configuration options (#1485438)
- Add 'messages_list' hook (#1486266)
- Add request* event triggers in http_post/http_request (#1486054)
- Fix use RFC-compliant line-delimiter when saving messages on IMAP (#1486712)
- Add 'imap_timeout' option (#1486760)
- Fix forwarding of messages with winmail attachments
- Fix handling of uuencoded attachments in message body (#1485839)
- Added list_mailboxes hook in rcube_imap::list_unsubscribed() (#1486668)
- Fix wrong message on file upload error (#1486725)
- Add support for data URI scheme [RFC2397] (#1486740)
- Added 'actionbefore', 'actionafter', 'responsebefore', 'responseafter' events
- Fix double-addition of e-mail domain to content ID in HTML images
- Read and send messages with format=flowed (#1484370), fixes word wrapping issues (#1486543)
- Fix duplicated attachments when forwarding a message (#1486487)
- Fix message/rfc822 attachments containing only attachments are not parsed properly (#1486743)
- Fix %00 character in winmail.dat attachments names (#1486738)
- Fix handling errors of folder deletion (#1486705)
- Parse untagged CAPABILITY response for LOGIN command (#1486742)
- Renamed all php-cli scripts to use .sh extension
- Some files from /bin + spellchecking actions moved to the new 'utils' task
- Added thread tree icons
- Extend contact groups support (#1486682)
- Fix check-recent action issues and performance (#1486526)
- Fix messages order after checking for recent (#1484664)
- Fix autocomplete shows entries without email (#1486452)
- Fix listupdate event doesn't trigger on search response (#1486708)
- Fix select_all_mode value after selecting a message (#1486720)
- Set focus to editor on reply in HTML mode (#1486632)
- Fix composing in HTML jumps cursor to body instead of recipients (#1486674)
- Allow columns order change per user - drag&drop (#1485795)
- Add References header in read receipt (#1486681)
- Fix database constraint violation when opening a message (#1486696)
- Add 'loading' message while login is in progress (#1486667)
- Fix quota_zero_as_unlimited (#1486662)
- Fix folder subscription checking (#1486684)
- Fix INBOX appears (sometimes) twice in mailbox list (#1486672)
- Fix listing of attachments of some types e.g. "x-epoc/x-sisx-app" (#1486653)
- Fix DB Schema checking when some db_table_* options are not set (#1486654)
RELEASE 0.4-beta
----------------
- Add sizelimit and timelimit variables in LDAP config (#1486544)
- Hide IMAP host dropdown when single host is defined (#1486326)
- Add images pre-loading on login page (#1451160)
- Add HTTP_X_REAL_IP and HTTP_X_FORWARDED_FOR to successful logins log (#1486441)
- Fix setting spellcheck languages with extended codes (#1486605)
- Fix messages list scrolling in FF3.6 (#1486472)
- Fix quicksearch input focus (#1486637)
- Always set changed date when flagging a DB record as deleted + provide a cleanup script
- Fix address book/group selection (#1486619)
- Assign newly created contacts to the active group (#1486626)
- Added option not to mark messages as read when viewed in preview pane (#1485012)
- Allow plugins modify the Sent folder when composing (#1486548)
- Added optional (max_recipients) support to restrict total number of recipients per message (#1484542)
- Re-organize editor buttons, add blockquote and search buttons
- Make possible to write inside or after a quoted html message (#1485476)
- Fix bugs on unexpected IMAP connection close (#1486190, #1486270)
- Iloha's imap.inc rewritten into rcube_imap_generic class
- Added contact groups in address book (not finished yet)
- Added PageUp/PageDown/Home/End keys support on lists (#1486430)
- Added possibility to select all messages in a folder (#1484756)
- Added 'imap_force_caps' option for after-login CAPABILITY checking (#1485750)
- Password: Support dovecotpw encryption
- TinyMCE 3.3.1
- Implemented messages copying using drag&drop + SHIFT (#1484086)
- Improved performance of folders operations (#1486525)
- Fix blocked.gif attachment is not attached to the message (#1486516)
- Managesieve: import from Horde-INGO
- Managesieve: support for more than one match (#1486078)
- Managesieve: support for selectively disabling rules within a single sieve script (#1485882)
- Threaded message listing now available
- Added sorting by ARRIVAL and CC
- Message list columns configurable by the user
- Removed 'index_sort' option, now we're using empty 'message_sort_col' for this
- virtuser_query: support other identity data (#1486148)
- Options virtuser_* replaced with virtuser_* plugins
- Plugin API: Implemented 'email2user' and 'user2email' hooks
- Fix forwarding message omits CC header (#1486305)
- Add 'default_charset' option to user preferences (#1485451)
- Add 'delete_always' option to user preferences
- Support/Require tls:// prefix in 'smtp_server' option for TLS connections
- Fix inconsistent behaviour of 'delete_always' option (#1486299)
- Fix deleting all messages from last list page (#1486293)
- Flag original messages when sending a draft (#1486203)
- Changed signature separator when top-posting (#1486330)
- Let the admin define defaults for search modifiers (#1485897)
- Fix long e-mail addresses validation (#1486453)
- Remember search modifiers in user prefs (#1486146)
- Added force_7bit option to force MIME encoding of plain/text messages (#1486510)
- Use case sensitive check when checking for default folders (#1486346)
- Fix checking for new mail: now checks unseen count of inbox (#1485794)
- Improve performance by avoiding unnecessary updates to the session table (#1486325)
- Fix invalid <font> tags which cause HTML message rendering problems (#1486521)
- Fix CVE-2010-0464: Disable DNS prefetching (#1486449)
- Fix Received headers to behave better with SpamAssassin (#1486513)
- Password: Make passwords encoding consistent with core, add 'password_charset' global option (#1486473)
- Fix adding contacts SQL error on mysql (#1486459)
- Squirrelmail_usercopy: support reply-to field (#1486506)
- Fix IE spellcheck suggestion popup issue (#1486471)
- Fix email address auto-completion shows regexp pattern (#1486258)
- Fix merging of configuration parameters: user prefs always survive (#1486368)
- Fix quota indicator value after folder purge/expunge (#1486488)
- Fix external mailto links support for use as protocol handler (#1486037)
- Fix attachment excessive memory use, support messages of any size (#1484660)
- Fix setting task name according to auth state
- Password: fix vpopmaild driver (#1486478)
- Add workaround for MySQL bug [http://bugs.mysql.com/bug.php?id=46293] (#1486474)
- Fix quoted text wrapping when replying to an HTML email in plain text (#1484141)
- Fix handling of extended mailto links (with params) (#1486354)
- Fix sorting by date of messages without date header on servers without SORT (#1486286)
- Fix inconsistency when not using default table names (#1486467)
- Fix folder rename/delete buttons do not appear on creation of first folder (#1486468)
- Fix character set conversion fails on systems where iconv doesn't accept //IGNORE (#1486375)
- Log in performance: Create default folders on first login only
- Import contacts into the selected address book (by Phil Weir)
- Add support for MDB2's 'sqlsrv' driver (#1486395)
- Use jQuery-1.4
- Removed problematic browser-caching of messages
- Fix incompatybility with suhosin.executor.disable_emodifier (#1486321)
- Use PLAIN auth when CRAM fails and imap_auth_type='check' (#1486371)
- Fix removal of <title> tag from HTML messages (#1486432)
- Fix 'force_https' to specified port when URL contains a port number (#1486411)
- Fix to-text converting of HTML entities inside b/strong/th/hX tags (#1486422)
- Bug in spellchecker suggestions when server charset != UTF8 (#1486406)
- Managesieve: Fix requires generation for multiple actions (#1486397)
- Fix LDAP problem with special characters in RDN (#1486320)
- Improved handling of message parts of type message/rfc822
- Plugin API: added 'quota' hook
- Fix parsing conditional comments in HTML messages (#1486350)
- Use built-in json_encode() for proper JSON format in AJAX replies
- Allow setting only selected params in 'message_compose' hook (#1486312)
- Plugin API: added 'message_compose_body' hook (#1486285)
- Fix counters of all folders are checked in 'getunread' action with check_all_folders disabled (#1486128)
- Fix displaying alternative parts in messages of type message/rfc822 (#1486246)
- Fix possible messages exposure when using Roundcube behind a proxy (#1486281)
- Fix unicode para and line separators in javascript response (#1486310)
- Additional_message_headers: allow unsetting headers, support plugin's config file (#1486268)
- Fix displaying of hidden directories in skins list (#1486301)
- Fix open_basedir restriction error when reading skins list (#1486304)
- Fix pasting from Office apps into html editor (#1486271)
- Fix empty <a> tags parsing (#1486272)
- Don't cut off attachment names when using non-RFC2231 encoding (#1485515)
- Allow inserting signatures above replied message body (#1484272)
- Managesieve 2.0: multi-script support
- Fix imap_auth_type regression (#1486263)
RELEASE 0.3.1
------------------
- Specify toolbar container in compose template (#1486247)
- Fix $_SERVER['HTTPS'] check for SSL forcing on IIS (#1486243)
- Avoid unnecessary page loads for selected tab (#1486032)
- Fix quota indicator issues by content generation on client-size (#1486197, #1486220)
- Don't display disabled sections in Settings (#1486099)
- Added server-side e-mail address validation with 'email_dns_check' option (#1485857)
- Fix login page loading into an iframe when session expires (#1485952)
- Allow setting port number in 'force_https' option (#1486091)
- Option 'force_https' replaced by 'force_https' plugin
- Fix IE issue with non-UTF-8 characters in AJAX response (#1486159)
- Partially fixed "empty body" issue by showing raw body of malformed message (#1486166)
- Fix importing/sending to email address with whitespace (#1486214)
- Added XIMSS (CommuniGate) driver for Password plugin
- Fix newly attached files are not saved in drafts w/o editing any text (#1486202)
- Added attachment upload indicator with parallel upload (#1486058)
- Use default_charset for bodies of messages without charset definition (#1486187)
- Password: added cPanel driver
- Fix return to first page from e-mail screen (#1486105)
- Fix handling HTML comments in HTML messages (#1486189)
- Fix folder/messagelist controls alignment - icons used (#1486072)
- Fix LDAP addressbook shows 'Contact not found' error sometimes (#1486178)
- Fix cache status checking + improve cache operations performance (#1486104)
- Prevent from setting INBOX as any of special folders (#1486114)
- Fix regular expression for e-mail address (#1486152)
- Fix Received header format
- Implemented sorting by message index - added 'index_sort' option (#1485936)
- Fix dl() use in installer (#1486150)
- Added 'ldap_debug' option
- Fix "Empty startup greeting" bug (#1486085)
- Fix setting user name in 'new_user_identity' plugin (#1486137)
- Fix incorrect count of new messages in folder list when using multiple IMAP clients (#1485995)
- Fix all folders checking for new messages with disabled caching (#1486128)
- Support skins in 'archive' and 'markasjunk' plugins
- Added 'html_editor' hook (#1486068)
- Fix DB constraint violation when populating messages cache (#1486052)
- Password: added password strength options (#1486062)
- Fix LDAP partial result warning (#1485536)
- Fix delete in message view deletes permanently with flag_for_deletion=true (#1486101)
- Use faster/secure mt_rand() (#1486094)
- Fix roundcube hangs on empty inbox with bincimapd (#1486093)
- Fix wrong headers for IE on servers without $_SERVER['HTTPS'] (#1485926)
- Force IE style headers for attachments in non-HTTPS session, 'use_https' option (#1485655)
- Check 'post_max_size' for upload max filesize (#1486089)
- Password Plugin: Fix %d inserts username instead of domain (#1486088)
- Fix rcube_mdb2::affected_rows() (#1486082)
RELEASE 0.3-stable
------------------
- Fix gn and givenName should be synonymous in LDAP addressbook (#1485892)
- Add mail_domain to LDAP email entries without @ sign (#1485201)
- Fix saving empty values in LDAP contact data (#1485781)
- Fix LDAP contact update when RDN field is changed (#1485788)
- Fix LDAP attributes case senitivity problems (#1485830)
- Fix LDAP addressbook browsing when only one directory is used (#1486022)
- Fix endless loop on error response for APPEND command (#1486060)
- Don't require date.timezone setting in installer (#1485989)
- Fix date sorting problem with Courier IMAP server (#1486065)
- Unselect pressed buttons on mouse up (#1485987)
- Don't set php_value error_log in .htaccess but mention in INSTALL (#1485924)
- Fix too small status/flag/attachment columns in Safari 4 (#1486063)
- Fix selection disabling while dragging splitter in webkit browsers (#1486056)
- Added 'new_messages' plugin hook (#1486005)
- Added 'logout_after' plugin hook (#1486042)
- Added 'message_compose' hook
- Added 'imap_connect' hook (#1485956)
- Fix vcard_attachments plugin (#1486035)
- Updated PEAR::Auth_SASL to 1.0.3 version
- Use sequence names only with PostgreSQL (#1486018)
- Re-designed User Preferences interface
- Fix MS SQL DDL (#1486020)
- Fix rcube_mdb2.php: call to setCharset not implemented in mssql driver (#1486019)
- Added 'display_next' option
- Fix rcube_mdb2::unixtimestamp for MS SQL (#1486015)
- Fix HTML washing to respect character encoding
- Fix endless loop in iil_C_Login() with Courier IMAP (#1486010)
- Fix #messagemenu display on IE (#1486006)
- Speedup UI by using sprites for (toolbar) buttons
- Fix charset names with X- prefix handling
- Fix displaying of HTML messages with unknown/malformed tags (#1486003)
RELEASE 0.3-RC1
---------------
- Fix import of vCard entries with params (#1485453)
- Fix HTML messages output with empty block elements (#1485974)
- Use request tokens to protect POST requests from CSRF
- Added hook when killing a session
- Added hook to write_log function (#1485971)
- Performance improvements by use UID commands (#1485690)
- Fix HTML editor tabIndex setting (#1485972)
- Added 'imap_debug' and 'smtp_debug' options
- Support strftime's format modifiers in date_* options (#1484806)
- Support %h variable in 'smtp_server' option (#1485766)
- Show SMTP errors in browser (#1485927)
- Allow WBR tag in HTML message (#1485960)
- Use spl_autoload_register() instead of __autoload (#1485947)
- Add hook for identities listing (#1485958)
- Trigger hook 'smtp_connect' when opening an SMTP connection (#1485954)
- Added config option to enforce HTTPS connections
- Fix non-unicode characters caching in unicode database (#1484608)
- Performance improvements of messages caching
- Fix empty Date header issue (#1485923)
- Open collapsed folders during drag & drop (#1485914)
- Fixed link text replacements (#1485789)
- Also trigger 'insertrow' events on page load (#1485826)
- No link on subject in IE browsers (#1484913)
- Fixed filename encoding according to RFC2231 (#1485875)
- Added message Edit feature (#1483891, #1484440)
- Fix message Etag generation for counter issues (#1485623)
- Fix messages searching on MailEnable IMAP (#1485762)
- Fixed many 'skip_deleted' issues (#1485634)
- Fixed messages list sorting on servers without SORT capability
- Colorized signatures in plain text messages
- Reviewed/fixed skip_deleted/read_when_deleted/flag_for_deletion options handling in UI
- Fix displaying of big maximum upload filesize (#1485889)
- Added possibility to invert messages selection
- After move/delete from 'show' action display next message instead of messages list (#1485887)
- Fixed problem with double quote at the end of folder name (#1485884)
- Speedup UI by using CSS sprites and etags/expires/deflate in Apache config (#1484858,#1485800)
- Support UID EXPUNGE: remove only moved/deleted messages
- Add drag cancelling with ESC key (#1484344)
- Support initial identity name from virtuser_query (#1484003)
- Added message menu, removed Print and Source buttons
- Added possibility to save message as .eml file (#1485861)
- Added 1 minute interval in autosave options (#1485854)
- Support UTF-7 encoding in messages (#1485832)
- Better support for malformed character names (#1485758)
RELEASE 0.3-BETA
----------------
- Plugin API + jQuery engine
- Added possibility to encrypt received header, option 'http_received_header_encrypt',
added some more logic in encrypt/decrypt functions for security
- Fix Answered/Forwarded flag setting for messages in subfolders
- Fix autocomplete problem with capital letters (#1485792)
- Support UUencode content encoding (#1485839)
- Minimize chance of race condition in session handling (#1485659, #1484678)
- Fix session handling on non-session SQL query error (#1485734)
- Fix html editor mode setting when reopening draft message (#1485834)
- Added quick search box menu (#1484304)
- Fix wrong column sort order icons (#1485823)
- Updated TinyMCE to 3.2.3 version
- Fix attachment names encoding when charset isn't specified in attachment part (#1484969)
- Fix message normal priority problem (#1485820)
- Fix autocomplete spinning wheel does not disappear (#1485804)
- Added log_date_format option (#1485709)
- Fix text wrapping in HTML editor after switching from plain text to HTML (#1485521)
- Fix auto-complete function hangs with plus sign (#1485815)
- Fix AJAX requests errors handler (#1485000)
- Speed up message list displaying on IE
- Fix read/write database recognition (#1485811)
RELEASE 0.2.2
-------------
- Fix quicksearchbox look in Chrome and Konqueror (#1484841)
- Fix UTF-8 byte-order mark removing (#1485514)
- Fix folders subscribtions on Konqueror (#1484841)
- Fix debug console on Konqueror and Safari
- Fix messagelist focus issue when modifying status of selected messages (#1485807)
- Support STARTTLS in IMAP connection (#1485284)
- Fix DEL key problem in search boxes (#1485528)
- Support several e-mail addresses per user from virtuser_file (#1485678)
- Fix drag&drop with scrolling on IE (#1485786)
- Fix adding signature separator in html mode (#1485350)
- Fix opening attachment marks message as read (#1485803)
- Fix 'temp_dir' does not support relative path under Windows (#1484529)
- Fix "Initialize Database" button missing from installer (#1485802)
- Fix compose window doesn't fit 1024x768 window (#1485396)
- Fix service not available error when pressing back from compose dialog (#1485552)
- Fix using mail() on Windows (#1485779)
- Fix word wrapping in message-part's <PRE>s for printing (#1485787)
- Fix incorrect word wrapping in outgoing plaintext multibyte messages (#1485714)
- Fix double footer in HTML message with embedded images
- Fix TNEF implementation bug (#1485773)
- Fix incorrect row id parsing for LDAP contacts list (#1485784)
- Fix 'mode' parameter in sqlite DSN (#1485772)
RELEASE 0.2.1
------------------
- Use US-ASCII as failover when Unicode searching fails (#1485762)
- Fix errors handling in IMAP command continuations (#1485762)
- Fix FETCH result parsing for servers returning flags at the end of result (#1485763)
- Fix datetime columns defaults in mysql's DDL (#1485641)
- Fix attaching more than nine inline images (#1485759)
- Support 'UNICODE-1-1-UTF-7' alias for UTF-7 encoding (#1485758)
- Fix mime-type detection using a hard-coded map (#1485311)
- Don't return empty string if charset conversion failed (#1485757)
- Disable concurrent autocomplete query results display (#1485743)
- Fix new lines stripped from message footer (#1485751)
- Fix IE problem with mouse click autocomplete (#1485739)
- Fix html body washing on reply/forward + fix attachments handling (#1485676)
- Fix multiple recipients input parsing (#1485733)
- Fix replying to message with html attachment (#1485676)
- Use default_charset for messages without specified charset (#1485661, #1484961)
- Support non-standard "GMT-XXXX" literal in date header (#1485729)
- Added TNEF support to decode MS Outlook attachments (winmail.dat)
- Fix "value continuation" MIME headers by adding required semicolon (#1485727)
- Fix pressing select all/unread multiple times (#1485723)
- Fix selecting all unread does not honor new messages (#1485724)
- Fix some base64 encoded attachments handling (#1485725)
- Support NGINX as IMAP backend: better BAD response handling (#1485720)
- Performance fix: don't fetch attachment parts headers twice to parse filename
- Fix checking for recent messages on various IMAP servers (#1485702)
- Performance fix: Don't fetch quota and recent messages in "message view" mode
- Fix displaying of alternative-inside-alternative messages (#1485713)
- Fix MDNSent flag checking, use arbitrary keywords (asterisk) flag (#1485706)
- Fix creation of folders with '&' sign in name
- Fix parsing of email addresses without angle brackets (#1485693)
- Save spellcheck corrections when switching from plain to html editor (and spellchecking is on)
- Fix large search results on server without SORT capability (#1485668)
- Get rid of preg_replace() with eval modifier and create_function usage (#1485686)
- Bring back <base> and <link> tags in HTML messages
- Fix XSS vulnerability through background attributes as reported by Julien Cayssol
- Fix problems with backslash as IMAP hierarchy delimiter (#1484467)
- Secure vcard export by getting rid of preg's 'e' modifier use (#1485689)
- Fix authentication when submitting form with existing session (#1485679)
- Allow absolute URLs to images in HTML messages/sigs (#1485666)
- Fix message body which contains both inline attachments and emotions
- Fix SQL query execution errors handling in rcube_mdb2 class (#1485509)
- Fix address names with '@' sign handling (#1485654)
- Improve messages display performance
- Fix messages searching with 'to:' modifier
RELEASE 0.2-STABLE
------------------
- Fix mark popup in IE 7 (#1485369)
- Fix line-break issue when copy & paste in Firefox (#1485425)
- Fix autocomplete "unknown server error" (#1485637)
- Fix STARTTLS before AUTH in SMTP connection (#1484883)
- Support multiple quota values in QUOTAROOT resonse (#1485626)
- Only abbreviate file name for IE < 7 browsers (#1485063)
- Performance: allow setting imap rootdir and delimiter before connect (#1485172)
- Fix sorting of folders with more than 2 levels (#1485569)
- Fix search results page jumps in LDAP addressbook (#1485253)
- Fix empty line before the signature in IE (#1485351)
- Fix horizontal scrollbar in preview pane on IE (#1484633)
- Add Robots meta tag in login page and installer (#1484846)
- Added 'show_images' option, removed 'addrbook_show_images' (#1485597)
- Option to check for new mails in all folders (#1484374)
- Don't set client busy when checking for new messages (#1485276)
- Allow UTF-8 folder names in config (#1485579)
- Add junk_mbox option configuration in installer (#1485579)
- Do serverside addressbook queries for autocompletion (#1485531)
- Allow setting attachment col position in 'list_cols' option
- Allow override 'list_cols' via skin (#1485577)
- Fix 'cache' table cleanup on session destroy (#1485516)
- Increase speed of session destroy and garbage clean up
- Fix session timeout when DB server got clock skew (#1485490)
- Fix handling of some malformed messages (#1484438)
- Speed up raw message body handling
- Better HTML entities conversion in html2text (#1485519)
- Fix big memory consumption and speed up searching on servers without SORT capability
- Fix setting locale to tr_TR, ku and az_AZ (#1485470)
- Use SORT for searching on servers with SORT capability
- Added message status filter
- Fix empty file sending (#1485389)
- Improved searching with many criterias (calling one SEARCH command)
- Fix HTML editor initialization on IE (#1485304)
- Add warning when switching editor mode from html to plain (#1485488)
- Make identities list scrollable (#1485538)
- Fix problem with numeric folder names (#1485527)
- Added BYE response simple support to prevent from endless loops in imap.inc (#1483956)
- Fix unread message unintentionally marked as read if read_when_deleted=true (#1485409)
- Remove port number from SERVER_NAME in smtp_helo_host (#1485518)
- Don't send disposition notification receipts for messages marked as 'read' (#1485523)
- Added 'keep_alive' and 'min_keep_alive' options (#1485360)
- Added option 'identities_level', removed 'multiple_identities'
- Allow deleting identities when multiple_identities=false (#1485435)
- Added option focus_on_new_message (#1485374)
- Fix html2text class autoloading on Windows (#1485505)
- Fix html signature formatting when identity save error occured (#1485426)
- Add feedback and set busy when moving folder (#1485497)
- Fix 'Empty' link visibility for some languages e.g. Slovak (#1485489)
- Fix messages count bar overlapping (#1485270)
- Fix adding signature in drafts compose mode (#1485484)
- Fix iil_C_Sort() to support very long and/or divided responses (#1485283)
- Fix matching case sensitivity when setting identity on reply (#1485480)
- Prefer default identity on reply
- Fix imap searching on ISMail server (#1485466)
- Add css class for flagged messages (#1485464)
- Write username instead of id in sendmail log (#1485477)
- Fix htmlspecialchars() use for PHP version < 5.2.3 (#1485475)
- Fix js keywords escaping in json_serialize() for IE/Opera (#1485472)
- Added bin/killcache.php script (#1485434)
- Add support for SJIS, GB2312, BIG5 in rc_detect_encoding()
- Fix vCard file encoding detection for non-UTF-8 strings (#1485410)
- Add 'skip_deleted' option in User Preferences (#1485445)
- Minimize "inline" javascript scripts use (#1485433)
- Fix css class setting for folders with names matching defined classes names (#1485355)
- Fix race conditions when changing mailbox
- Fix spellchecking when switching to html editor (#1485362)
- Fix compose window width/height (#1485396)
- Allow calling msgimport.sh/msgexport.sh from any directory (#1485431)
- Localized filesize units (#1485340)
- Better handling of "no identity" and "no email in identity" situations (#1485117)
- Added 'mime_param_folding' option with possibility to choose long/non-ascii attachment names encoding eg. to be readable in MS Outlook/OE (#1485320)
- Added "advanced options" feature in User Preferences
- Fix unread counter when displaying cached massage in preview panel (#1485290)
- Fix htmleditor spellchecking on MS Windows (#1485397)
- Fix problem with non-ascii attachment names in Mail_mime (#1485267, #1485096)
- Fix language autodetection (#1485401)
- Fix button label in folders management (#1485405)
- Fix collapsed folder not indicating unread msgs count of all subfolders (#1485403)
- Fix handling of apostrophes in filenames decoded according to rfc2231
RELEASE 0.2-BETA
----------------
- Made config files location configurable (#1485215)
- Reduced memory footprint when forwarding attachments (#1485345)
- Allow and use spellcheck attribute for input/textarea fields (#1485060)
- Added icons for forwarded/forwarded+replied messages (#1485257)
- Added Reply-To to forwarded emails (#1485315)
- Display progress message for folders create/delete/rename (#1485357)
- Smart Tags and NOBR tag support in html messages (#1485363, #1485327)
- Redesign of the identities settings (#1484042)
- Add config option to disable creation/deletion of identities (#1484498)
- Added 'sendmail_delay' option to restrict messages sending interval (#1484491)
- Added vertical splitter for folders list resizing
- Added possibility to view all headers in message view
- Fixed splitter drag/resize on Opera (#1485170)
- Fixed quota img height/width setting from template (#1484857)
- Refactor drag & drop functionality. Don't rely on browser events anymore (#1484453)
- Insert "virtual" folders in subscription list (#1484779)
- Added link to open message in new window
- Enable export of address book contacts as vCard
- Add feature to import contacts from vcard files (#1326103)
- Respect Content-Location headers in multipart/related messages according to RFC2110 (#1484946)
- Allowed max. attachment size now indicated in compose screen (#1485030)
- Also capture backspace key in list mode (#1484566)
- Allow application/pgp parts to be displayed (#1484753)
- Correctly handle options in mailto-links (#1485228)
- Immediately save sort_col/sort_order in user prefs (#1485265)
- Truncate very long (above 50 characters) attachment filenames when displaying
- Allow to auto-detect client language if none set (#1484434)
- Auto-detect the client timezone (user configurable)
- Add RFC2231 header value continuations support for attachment filenames + hack for servers that not support that feature
- Fix Reply-To header displaying (#1485314)
- Mark form buttons that provide the most obvious operation (mainaction)
- Added option 'quota_zero_as_unlimited' (#1484604)
- Added PRE handling in html2text class (#1484740)
- Added folder hierarchy collapsing
- Added options to use syslog instead of log file (#1484850)
- Added Logging & Debugging section in Installer
- Fix In-Reply-To and References headers when composing saved draft message (#1485288)
- Fix html message charset conversion for charsets with underline (#1485287)
- Fix buttons status after contacts deletion (#1485233)
- Fix escaping of To: and From: fields when building message body for reply or forward in the HTML editor (#1484904)
- Use current mailbox name in template (#1485256)
- Better fix for skipping untagged responses (#1485261)
- Added pspell support patch by Kris Steinhoff (#1483960)
- Enable spellchecker for HTML editor (#1485114)
- Respect spellcheck_uri in tinyMCE spellchecker (#1484196)
- Case insensitive contacts searching using PostgreSQL (#1485259)
- Make default imap folders configurable for each user (#1485075)
- Save outgoing mail to selectable folder (#1324581)
- Fix hiding of mark menu when clicking th button again (#1484944)
- Use long date format in print mode (#1485191)
- Updated TinyMCE to version 3.1.0.1
- Re-enable autocomplete attribute for login form (#1485211)
- Check PERMANENTFLAGS before saving $MDNSent flag (#1484963, #1485163)
- Added flag column on messages list (#1484623)
- Patched Mail/MimePart.php (http://pear.php.net/bugs/bug.php?id=14232)
- Allow trash/junk subfolders to be purged (#1485085)
- Store compose parameters in session and redirect to a unique URL
- Fixed CRAM-MD5 authentication (#1484819)
- Fixed forwarding messages with one HTML attachment (#1484442)
- Fixed encoding of message/rfc822 attachments and image/pjpeg handling (#1484914)
- Added option to select skin in user preferences
- Added option to configure displaying of attached images below the message body
- Added option to display images in messages from known senders (#1484601)
- User preferences grouped in more fieldsets
- Fix corrupted MIME headers of messages in Sent folder (#1485111)
- Fixed bug in MDB2 package: http://pear.php.net/bugs/bug.php?id=14124
- Use keypress instead of keydown to select list's row (#1484816)
- Don't call expunge and don't remove message row after message move if flag_for_deletion is set to true (#1485002)
RELEASE 0.2-ALPHA
-----------------
- Added option to disable autocompletion from selected LDAP address books (#1484922)
- TLS support in LDAP connections: 'use_tls' property (#1485104)
- Fixed removing messages from search set after deleting them (#1485106)
- imap.inc: Fixed iil_C_FetchStructureString() to handle many
literal strings in response (#1484969)
- Support for subfolders in default/protected folders (#1484665)
- Disallowed delimiter in folder name (#1484803)
- Support " and \ in folder names
- Escape \ in login (#1484614)
- Better HTML sanitization with the DOM-based washtml script (#1484701)
- Fixed sorting of folders with non-ascii characters
- Fixed Mysql DDL for default identities creation (#1485070)
- In Preferences added possibility to configure 'read_when_deleted',
'mdn_requests', 'flag_for_deletion' options
- Made IMAP auth type configurable (#1483825)
- Fixed empty values with FROM_UNIXTIME() in rcube_mdb2 (#1485055)
- Fixed attachment list on IE 6/7 (#1484807)
- Fixed JavaScript in compose.html that shows cc/bcc fields if populated
- Make password input fields of type password in installer (#1484886)
- Don't attempt to delete cache entries if enable_caching is FALSE (#1485051)
- Optimized messages sorting on servers without sort capability (#1485049)
- Corrected message headers decoding when charset isn't specified and improved
support for native languages (#1485050, #1485048)
- Expanded LDAP configuration options to support LDAP server writes.
- Installer: encode special characters in DB username/password (#1485042)
- Fixed management of folders with national characters in names (#1485036, #1485001)
- Fixed identities saving when using MDB2 pgsql driver (#1485032)
- Fixed BCC header reset (#1484997)
- Improved messages list performance - patch from Justin Heesemann
- Append skin_path to images location only when it starts with '/' sign (#1484859)
- Fix IMAP response in message body when message has no body (#1484964)
- Fixed non-RFC dates formatting (#1484901)
- Fixed typo in set_charset() (#1484991)
- Decode entities when inserting HTML signature to plain text message (#1484990)
- HTML editing is now working with PHP5 updates and TinyMCE v3.0.6
- Fixed signature loading on Windows (#1484545)
- Added language support to HTML editing (#1484862)
- Fixed remove signature when replying (#1333167)
- Fixed problem with line with a space at the end (#1484916)
- Fixed <!DOCTYPE> tag filtering (#1484391)
- Fixed <?xml> tag filtering (#1484403)
- Added sections (fieldset+label) in Settings interface
- Mark as read in one action with message preview (#1484972)
- Deleted redundant quota reads (#1484972)
- Added options for empty trash and expunge inbox on logout (#1483863)
- Removed lines wrapping when displaying message
- Fixed month localization
- Changed codebase to PHP5 with autoloader
RELEASE 0.1.1
-------------
- Clear selection when selecting single item (#1484942)
- Remove hard-coded image size in skin templates (#1484893)
- Database schema improvements (dropped unnecessary indexes)
- Fixed creating a new folder with a comma in its name (#1484681)
- Fixed sorting of messages when default mailbox is empty (#1484317)
- Improve message previewpane - less loading (#1484316)
- Fixed login form autoompletion (#1484839)
- Fixed virtuser_query option for mdb2 backend (#1484874)
- Fixed attachment resoting from Drafts when message body was empty (#1484506)
- Fixed usage of ob_gzhandler (#1484851)
- Fixed message part window in IE6 (#1484610)
- Fixed decoding of mime-encoded strings (#1484191)
- Fixed some iconv/mb_string problems (#1484598)
- Correctly quote mailbox name when using in URL (#1484313)
- Fixed "headers already sent" errors (#1484860)
RELEASE 0.1-STABLE
------------------
- Added interactive installer script
- Fix folder adding/renaming inspired by #1484800
- Localize folder name in page title (#1484785)
- Fix code using wrong variable name (#1484018)
- Allow to send mail with BCC recipients only
- condense TinyMCE toolbar down to one line, removing table buttons (#1484747)
- Add function to mark the selected messages as read/unread (#1457360)
- Also do charset decoding as suggested in RFC 2231 (fix #1484321)
- Show message count in folder list and hint when creating a subfolder
- Distinguish ssl and tls for imap connections (#1484667)
- Added some charset aliases to fix typical mis-labelling (#1484565)
- Remember decision to display images for a certain message during session (#1484754)
- Truncate attachment filenames to 55 characters due to an IE bug (#1484757)
- Make sending of read receipts configurable
- Respect config when localize folder names (#1484707)
- Also respect receipt and priority settings when re-opening a draft message
- Remember search results (closes #1483883), patch by the_glu
- Add Received header on outgoing mail
- Upgrade to TinyMCE 2.1.3
- Allow inserting image attachments into HTML messages while composing (#1484557)
- Implement Message-Disposition-Notification (Receipts)
- Fix overriding of session vars when register_globals is on (#1484670)
- Fix bug with case-sensitive folder names (#1484245)
- Don't create default folders by default
- Fixed some potential security risks (audited by Andris)
- Only show new messages if they match the current search (#1484176)
- Switch to/from when searcing in Sent folder (#1484555)
- Correctly read the References header (#1484646)
- Unset old cookie before sending a new value (#1484639)
- Correctly decode attachments when downloading them (#1484645 and #1484642)
- Suppress IE errors when clearing attachments form (#1484356)
- Log error when login fails due to auto_create_user turned off
- Filter linked/imported CSS files (closes #1484056)
- Improve message compose screen (closes #1484383)
- Select next row after removing one from list (#1484387)
RELEASE 0.1-RC2
---------------
- Enable drag-&-dropping of folders to a new parent and allow to create subfolders (#1457344)
- Suppress IE errors when clearing attachments form (#1484356)
- Set preferences field in user table to NULL (#1484386)
- Log error when login fails due to auto_create_user turned off
- Filter linked/imported CSS files (closes #1484056)
- Improve message compose screen (closes #1484383)
- Select next row after removing one from list (#1484387)
- Make smtp HELO/EHLO hostname configurable (#1484067)
- IPv6 Compatability (#1484322), Patch #1484373
- Unlock interface when message sending fails (#1484570)
- Eval PHP code in template includes (if configured)
- Show message when folder is empty. Mo more static text in table (#1484395)
- Only display unread count in page title when new messages arrived
- Fixed wrong delete button tooltip (#1483965)
- Fixed charset encoding bug (#1484429)
- Applied patch for LDAP version (#1484552)
- Improved XHTML validation
- Fix message list selection (#1484550)
- Better fix lowercased usernames (#1484473)
- Update pngbehavior Script as suggested in #1484490
- Fixed moving/deleting messages when more than 1 is selected
- Applied patch for LDAP contacts listing by Glen Ogilvie
- Applied patch for more address fields in LDAP contacts (#1484402)
- Add alternative for getallheaders() (fix #1484508)
- Identify mailboxes case-sensitive
- Sort mailbox list case-insensitive (closes #1484338)
- Fix display of multipart messages from Apple Mail (closes #1484027)
- Protect AJAX request from being fetched by a foreign site (XSS)
- Make autocomplete for loginform configurable by the skin template
- Fix compose function from address book (closes #1484426)
- Added //IGNORE to iconv call (patch #1484420, closes #1484023)
- Check if mbstring supports charset (#1484290 and #1484292)
- Prefer iconv over mbstring (as suggested in #1484292)
- Check filesize of template includes (#1484409)
- Fixed bug with buttons not dimming/enabling properly after switching folders
- Fixed compose window becoming unresponsive after saving a draft (#1484487)
- Re-enabled "Back" button in compose window now that bug #1484487 is fixed
- Fixed unresponsive interface issue when downloading attachments (#1484496)
- Lowered status message time from 5 to 3 seconds to improve responsiveness
- Raised .htaccess upload_max_filesize from 2M to 5M to differ from default php.ini
- Increased "mailboxcontrols" mail.css width from 160 to 170px to fix non-english languages (#1484499)
- Fix status message bug #1484464 with regard to #1484353
- Fix address adding bug reported by David Koblas
- Applied socket error patch by Thomas Mangin
- Pass-by-reference workarround for PHP5 in sendmail.inc
- Fixed buggy imap_root settings (closes #1484379)
- Prevent default events on subject links (#1484399)
- Use HTTP-POST requests for actions that change state
RELEASE 0.1-RC1
---------------
- Use global filters and bind username/ for Ldap searches (#1484159)
- Hide quota display if imap server does not support it
- Hide address groups if no LDAP servers configured
- Add link to message subjects (closes #1484257)
- Better SQL query for contact listing/search (closes #1484369)
- Fixed marking as read in preview pane (closes #1484364)
- CSS hack to display attachments correctly in IE6
- Wrap message body text (closes #1484148)
- LDAP access is back in address book (closes #1484087)
- Added search function for contacts
- New Template parsing and output encoding
- Fixed bugs #1484119 and #1483978
- Fixed message moving procedure (closes #1484308)
- Fixed display of multiple attachments (closes #1466563)
- Fixed check for new messages (closes #1484310)
- List attachments without filename
- New session authentication: Change sessid cookie when login, authentication with sessauth cookie is now configurable.
Should close bugs #1483951 and #1484299
- Correctly translate mailbox names (closes #1484276)
- Quote e-mail address links (closes #1484300)
- Updated PEAR::Mail_mime package
- Accept single quotes for HTML attributes when modifying message body (thanks Jason)
- Sanitize input for new users/identities (thanks Colin Alston)
- Don't download HTML message parts
- Convert HTML parts to plaintext if 'prefer_html' is off
- Correctly parse message/rfc822 parts (closes #1484045)
- Also use user_id for unique key in messages table (closes #1484074)
- Hide contacts drop down on blur (closes #1484203)
- Make entries in contacts drop down clickable
- Turn off browser autocompletion on login page
- Quote <? in text/html message parts
- Hide border around radio buttons
- Applied patch for attachment download by crichardson (closes #1484198)
- Fixed bug in Postgres DB handling (closes #1484068)
- Fixed bug of invalid calls to fetchRow() in rcube_db.inc (closes #1484280)
- Fixed array_merge bug (closes #1484281)
- Fixed flag for deletion in list view (closes #1484264)
- Finally support semicolons as recipient separator (closes ##1484251)
- Fixed message headers (subject) encoding
- check if safe mode is on or not (closes #1484269)
- Show "no subject" in message list if subject is missing (closes #1484243)
- Solved page caching of message preview (closes #1484153)
- Only use gzip compression if configured (closes #1484236)
- Fixed priority selector issue (#1484150)
- Fixed some CSS issues in default skin (closes #1484210 and #1484161)
- Prevent from double quoting of numeric HTML character references (closes #1484253)
- Fixed display of HTML message attachments (closes #1484178)
- Applied patch for preview caching (closes #1484186)
- Added error handling for attachment uploads
- Use multibyte safe string functions where necessary (closes #1483988)
- Applied security patch to validate the submitted host value (by Kees Cook)
- Applied security patch to validate input values when deleting contacts (by Kees Cook)
- Applied security patch that sanitizes emoticon paths when attaching them (by Kees Cook)
- Applied a patch to more aggressively sanitize a HTML message
- Visualize blocked images in HTML messages
- Fixed wrong message listing when showing search results (closes #1484131)
- Show remote images when opening HTML message part as attachment
- Improve memory usage when sending mail (closes #1484098)
- Mark messages as read once the preview is loaded (closes #1484132)
- Include smtp final response in log (closes #1484081)
- Corrected date string in sent message header (closes #1484125)
- Correclty choose "To" column in sent and draft mailboxes (closes #1483943)
- Changed srong tooltips for message browse buttons (closes #1483930)
- Fixed signature delimeter character to be standard (Bug #1484035)
- Fixed XSS vulnerability (Bug #1484109)
- Remove newlines from mail headers (Bug #1484031)
- Selection issues when moving/deleting (Bug #1484044)
- Applied patch of Clement Moulin for imap host auto-selection
- ISO-encode IMAP password for plaintext login (Bugs #1483977 & #1483886)
- Fixed folder name encoding in subscription list (Bug #1484113)
- Fixed JS errors in identity list (Bug #1484120)
- Translate foldernames in folder form (closes #1484113)
- Added first and last buttons to message list, address book
and message detail
- Pressing Shift-Del bypasses Trash folder
- Enable purge command for Junk folder
- Fetch all aliases if virtuser_query is used instead
- Re-enabled multi select of contacts (Bug #1484017)
- Enable contact editing right after creation (Bug #1459641)
- Correct UTF-7 to UTF-8 conversion if mbstring is not available
- Fixed IMAP fetch of message body (Bug #1484019)
- Fixed safe_mode problems (Bug #1418381)
- Fixed wrong header encoding (Bug #1483976)
- Made automatic draft saving configurable
- Fixed JS bug when renaming folders (Bug #1483989)
- Added quota display as image (by Brett Patterson)
- Corrected creation of a message-id
- New indentation for quoted message text
- Improved HTML validity
- Fixed URL character set (Ticket #1445501)
- Fixed saving of contact into MySQL from LDAP query results (Ticket #1483820)
- Fixed folder renaming: unsubscribe before rename (Bug #1483920)
- Finalized new message parsing (+ chaching)
- Fixed wrong usage of mbstring (Bug #1462439)
- Set default spelling language (Ticket #1483938)
- Added support for Nox Spell Server
- Re-built message parsing (Bug #1327068)
Now based on the message structure delivered by the IMAP server.
- Fixed some XSS and SQL injection issues
- Fixed charset problems with folder renaming
diff --git a/program/lib/Roundcube/rcube_imap_generic.php b/program/lib/Roundcube/rcube_imap_generic.php
index f7c1f86d7..6c1b85552 100644
--- a/program/lib/Roundcube/rcube_imap_generic.php
+++ b/program/lib/Roundcube/rcube_imap_generic.php
@@ -1,3790 +1,3790 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2005-2012, The Roundcube Dev Team |
| Copyright (C) 2011-2012, Kolab Systems AG |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Provide alternative IMAP library that doesn't rely on the standard |
| C-Client based version. This allows to function regardless |
| of whether or not the PHP build it's running on has IMAP |
| functionality built-in. |
| |
| Based on Iloha IMAP Library. See http://ilohamail.org/ for details |
+-----------------------------------------------------------------------+
| Author: Aleksander Machniak <alec@alec.pl> |
| Author: Ryo Chijiiwa <Ryo@IlohaMail.org> |
+-----------------------------------------------------------------------+
*/
/**
* PHP based wrapper class to connect to an IMAP server
*
* @package Framework
* @subpackage Storage
*/
class rcube_imap_generic
{
public $error;
public $errornum;
public $result;
public $resultcode;
public $selected;
public $data = array();
public $flags = array(
'SEEN' => '\\Seen',
'DELETED' => '\\Deleted',
'ANSWERED' => '\\Answered',
'DRAFT' => '\\Draft',
'FLAGGED' => '\\Flagged',
'FORWARDED' => '$Forwarded',
'MDNSENT' => '$MDNSent',
'*' => '\\*',
);
private $fp;
private $host;
private $logged = false;
private $capability = array();
private $capability_readed = false;
private $prefs;
private $cmd_tag;
private $cmd_num = 0;
private $resourceid;
private $_debug = false;
private $_debug_handler = false;
const ERROR_OK = 0;
const ERROR_NO = -1;
const ERROR_BAD = -2;
const ERROR_BYE = -3;
const ERROR_UNKNOWN = -4;
const ERROR_COMMAND = -5;
const ERROR_READONLY = -6;
const COMMAND_NORESPONSE = 1;
const COMMAND_CAPABILITY = 2;
const COMMAND_LASTLINE = 4;
/**
* Object constructor
*/
function __construct()
{
}
/**
* Send simple (one line) command to the connection stream
*
* @param string $string Command string
* @param bool $endln True if CRLF need to be added at the end of command
*
* @param int Number of bytes sent, False on error
*/
function putLine($string, $endln=true)
{
if (!$this->fp)
return false;
if ($this->_debug) {
$this->debug('C: '. rtrim($string));
}
$res = fwrite($this->fp, $string . ($endln ? "\r\n" : ''));
if ($res === false) {
@fclose($this->fp);
$this->fp = null;
}
return $res;
}
/**
* Send command to the connection stream with Command Continuation
* Requests (RFC3501 7.5) and LITERAL+ (RFC2088) support
*
* @param string $string Command string
* @param bool $endln True if CRLF need to be added at the end of command
*
* @return int|bool Number of bytes sent, False on error
*/
function putLineC($string, $endln=true)
{
if (!$this->fp) {
return false;
}
if ($endln) {
$string .= "\r\n";
}
$res = 0;
if ($parts = preg_split('/(\{[0-9]+\}\r\n)/m', $string, -1, PREG_SPLIT_DELIM_CAPTURE)) {
for ($i=0, $cnt=count($parts); $i<$cnt; $i++) {
if (preg_match('/^\{([0-9]+)\}\r\n$/', $parts[$i+1], $matches)) {
// LITERAL+ support
if ($this->prefs['literal+']) {
$parts[$i+1] = sprintf("{%d+}\r\n", $matches[1]);
}
$bytes = $this->putLine($parts[$i].$parts[$i+1], false);
if ($bytes === false)
return false;
$res += $bytes;
// don't wait if server supports LITERAL+ capability
if (!$this->prefs['literal+']) {
$line = $this->readLine(1000);
// handle error in command
if ($line[0] != '+')
return false;
}
$i++;
}
else {
$bytes = $this->putLine($parts[$i], false);
if ($bytes === false)
return false;
$res += $bytes;
}
}
}
return $res;
}
/**
* Reads line from the connection stream
*
* @param int $size Buffer size
*
* @return string Line of text response
*/
function readLine($size=1024)
{
$line = '';
if (!$size) {
$size = 1024;
}
do {
if ($this->eof()) {
return $line ? $line : NULL;
}
$buffer = fgets($this->fp, $size);
if ($buffer === false) {
$this->closeSocket();
break;
}
if ($this->_debug) {
$this->debug('S: '. rtrim($buffer));
}
$line .= $buffer;
} while (substr($buffer, -1) != "\n");
return $line;
}
/**
* Reads more data from the connection stream when provided
* data contain string literal
*
* @param string $line Response text
* @param bool $escape Enables escaping
*
* @return string Line of text response
*/
function multLine($line, $escape = false)
{
$line = rtrim($line);
if (preg_match('/\{([0-9]+)\}$/', $line, $m)) {
$out = '';
$str = substr($line, 0, -strlen($m[0]));
$bytes = $m[1];
while (strlen($out) < $bytes) {
$line = $this->readBytes($bytes);
if ($line === NULL)
break;
$out .= $line;
}
$line = $str . ($escape ? $this->escape($out) : $out);
}
return $line;
}
/**
* Reads specified number of bytes from the connection stream
*
* @param int $bytes Number of bytes to get
*
* @return string Response text
*/
function readBytes($bytes)
{
$data = '';
$len = 0;
while ($len < $bytes && !$this->eof())
{
$d = fread($this->fp, $bytes-$len);
if ($this->_debug) {
$this->debug('S: '. $d);
}
$data .= $d;
$data_len = strlen($data);
if ($len == $data_len) {
break; // nothing was read -> exit to avoid apache lockups
}
$len = $data_len;
}
return $data;
}
/**
* Reads complete response to the IMAP command
*
* @param array $untagged Will be filled with untagged response lines
*
* @return string Response text
*/
function readReply(&$untagged=null)
{
do {
$line = trim($this->readLine(1024));
// store untagged response lines
if ($line[0] == '*')
$untagged[] = $line;
} while ($line[0] == '*');
if ($untagged)
$untagged = join("\n", $untagged);
return $line;
}
/**
* Response parser.
*
* @param string $string Response text
* @param string $err_prefix Error message prefix
*
* @return int Response status
*/
function parseResult($string, $err_prefix='')
{
if (preg_match('/^[a-z0-9*]+ (OK|NO|BAD|BYE)(.*)$/i', trim($string), $matches)) {
$res = strtoupper($matches[1]);
$str = trim($matches[2]);
if ($res == 'OK') {
$this->errornum = self::ERROR_OK;
} else if ($res == 'NO') {
$this->errornum = self::ERROR_NO;
} else if ($res == 'BAD') {
$this->errornum = self::ERROR_BAD;
} else if ($res == 'BYE') {
$this->closeSocket();
$this->errornum = self::ERROR_BYE;
}
if ($str) {
$str = trim($str);
// get response string and code (RFC5530)
if (preg_match("/^\[([a-z-]+)\]/i", $str, $m)) {
$this->resultcode = strtoupper($m[1]);
$str = trim(substr($str, strlen($m[1]) + 2));
}
else {
$this->resultcode = null;
// parse response for [APPENDUID 1204196876 3456]
if (preg_match("/^\[APPENDUID [0-9]+ ([0-9]+)\]/i", $str, $m)) {
$this->data['APPENDUID'] = $m[1];
}
// parse response for [COPYUID 1204196876 3456:3457 123:124]
else if (preg_match("/^\[COPYUID [0-9]+ ([0-9,:]+) ([0-9,:]+)\]/i", $str, $m)) {
$this->data['COPYUID'] = array($m[1], $m[2]);
}
}
$this->result = $str;
if ($this->errornum != self::ERROR_OK) {
$this->error = $err_prefix ? $err_prefix.$str : $str;
}
}
return $this->errornum;
}
return self::ERROR_UNKNOWN;
}
/**
* Checks connection stream state.
*
* @return bool True if connection is closed
*/
private function eof()
{
if (!is_resource($this->fp)) {
return true;
}
// If a connection opened by fsockopen() wasn't closed
// by the server, feof() will hang.
$start = microtime(true);
if (feof($this->fp) ||
($this->prefs['timeout'] && (microtime(true) - $start > $this->prefs['timeout']))
) {
$this->closeSocket();
return true;
}
return false;
}
/**
* Closes connection stream.
*/
private function closeSocket()
{
@fclose($this->fp);
$this->fp = null;
}
/**
* Error code/message setter.
*/
function setError($code, $msg='')
{
$this->errornum = $code;
$this->error = $msg;
}
/**
* Checks response status.
* Checks if command response line starts with specified prefix (or * BYE/BAD)
*
* @param string $string Response text
* @param string $match Prefix to match with (case-sensitive)
* @param bool $error Enables BYE/BAD checking
* @param bool $nonempty Enables empty response checking
*
* @return bool True any check is true or connection is closed.
*/
function startsWith($string, $match, $error=false, $nonempty=false)
{
if (!$this->fp) {
return true;
}
if (strncmp($string, $match, strlen($match)) == 0) {
return true;
}
if ($error && preg_match('/^\* (BYE|BAD) /i', $string, $m)) {
if (strtoupper($m[1]) == 'BYE') {
$this->closeSocket();
}
return true;
}
if ($nonempty && !strlen($string)) {
return true;
}
return false;
}
private function hasCapability($name)
{
if (empty($this->capability) || $name == '') {
return false;
}
if (in_array($name, $this->capability)) {
return true;
}
else if (strpos($name, '=')) {
return false;
}
$result = array();
foreach ($this->capability as $cap) {
$entry = explode('=', $cap);
if ($entry[0] == $name) {
$result[] = $entry[1];
}
}
return !empty($result) ? $result : false;
}
/**
* Capabilities checker
*
* @param string $name Capability name
*
* @return mixed Capability values array for key=value pairs, true/false for others
*/
function getCapability($name)
{
$result = $this->hasCapability($name);
if (!empty($result)) {
return $result;
}
else if ($this->capability_readed) {
return false;
}
// get capabilities (only once) because initial
// optional CAPABILITY response may differ
$result = $this->execute('CAPABILITY');
if ($result[0] == self::ERROR_OK) {
$this->parseCapability($result[1]);
}
$this->capability_readed = true;
return $this->hasCapability($name);
}
function clearCapability()
{
$this->capability = array();
$this->capability_readed = false;
}
/**
* DIGEST-MD5/CRAM-MD5/PLAIN Authentication
*
* @param string $user
* @param string $pass
* @param string $type Authentication type (PLAIN/CRAM-MD5/DIGEST-MD5)
*
* @return resource Connection resourse on success, error code on error
*/
function authenticate($user, $pass, $type='PLAIN')
{
if ($type == 'CRAM-MD5' || $type == 'DIGEST-MD5') {
if ($type == 'DIGEST-MD5' && !class_exists('Auth_SASL')) {
$this->setError(self::ERROR_BYE,
"The Auth_SASL package is required for DIGEST-MD5 authentication");
return self::ERROR_BAD;
}
$this->putLine($this->nextTag() . " AUTHENTICATE $type");
$line = trim($this->readReply());
if ($line[0] == '+') {
$challenge = substr($line, 2);
}
else {
return $this->parseResult($line);
}
if ($type == 'CRAM-MD5') {
// RFC2195: CRAM-MD5
$ipad = '';
$opad = '';
// initialize ipad, opad
for ($i=0; $i<64; $i++) {
$ipad .= chr(0x36);
$opad .= chr(0x5C);
}
// pad $pass so it's 64 bytes
$padLen = 64 - strlen($pass);
for ($i=0; $i<$padLen; $i++) {
$pass .= chr(0);
}
// generate hash
$hash = md5($this->_xor($pass, $opad) . pack("H*",
md5($this->_xor($pass, $ipad) . base64_decode($challenge))));
$reply = base64_encode($user . ' ' . $hash);
// send result
$this->putLine($reply);
}
else {
// RFC2831: DIGEST-MD5
// proxy authorization
if (!empty($this->prefs['auth_cid'])) {
$authc = $this->prefs['auth_cid'];
$pass = $this->prefs['auth_pw'];
}
else {
$authc = $user;
$user = '';
}
$auth_sasl = Auth_SASL::factory('digestmd5');
$reply = base64_encode($auth_sasl->getResponse($authc, $pass,
base64_decode($challenge), $this->host, 'imap', $user));
// send result
$this->putLine($reply);
$line = trim($this->readReply());
if ($line[0] == '+') {
$challenge = substr($line, 2);
}
else {
return $this->parseResult($line);
}
// check response
$challenge = base64_decode($challenge);
if (strpos($challenge, 'rspauth=') === false) {
$this->setError(self::ERROR_BAD,
"Unexpected response from server to DIGEST-MD5 response");
return self::ERROR_BAD;
}
$this->putLine('');
}
$line = $this->readReply();
$result = $this->parseResult($line);
}
else { // PLAIN
// proxy authorization
if (!empty($this->prefs['auth_cid'])) {
$authc = $this->prefs['auth_cid'];
$pass = $this->prefs['auth_pw'];
}
else {
$authc = $user;
$user = '';
}
$reply = base64_encode($user . chr(0) . $authc . chr(0) . $pass);
// RFC 4959 (SASL-IR): save one round trip
if ($this->getCapability('SASL-IR')) {
list($result, $line) = $this->execute("AUTHENTICATE PLAIN", array($reply),
self::COMMAND_LASTLINE | self::COMMAND_CAPABILITY);
}
else {
$this->putLine($this->nextTag() . " AUTHENTICATE PLAIN");
$line = trim($this->readReply());
if ($line[0] != '+') {
return $this->parseResult($line);
}
// send result, get reply and process it
$this->putLine($reply);
$line = $this->readReply();
$result = $this->parseResult($line);
}
}
if ($result == self::ERROR_OK) {
// optional CAPABILITY response
if ($line && preg_match('/\[CAPABILITY ([^]]+)\]/i', $line, $matches)) {
$this->parseCapability($matches[1], true);
}
return $this->fp;
}
else {
$this->setError($result, "AUTHENTICATE $type: $line");
}
return $result;
}
/**
* LOGIN Authentication
*
* @param string $user
* @param string $pass
*
* @return resource Connection resourse on success, error code on error
*/
function login($user, $password)
{
list($code, $response) = $this->execute('LOGIN', array(
$this->escape($user), $this->escape($password)), self::COMMAND_CAPABILITY);
// re-set capabilities list if untagged CAPABILITY response provided
if (preg_match('/\* CAPABILITY (.+)/i', $response, $matches)) {
$this->parseCapability($matches[1], true);
}
if ($code == self::ERROR_OK) {
return $this->fp;
}
return $code;
}
/**
* Detects hierarchy delimiter
*
* @return string The delimiter
*/
function getHierarchyDelimiter()
{
if ($this->prefs['delimiter']) {
return $this->prefs['delimiter'];
}
// try (LIST "" ""), should return delimiter (RFC2060 Sec 6.3.8)
list($code, $response) = $this->execute('LIST',
array($this->escape(''), $this->escape('')));
if ($code == self::ERROR_OK) {
$args = $this->tokenizeResponse($response, 4);
$delimiter = $args[3];
if (strlen($delimiter) > 0) {
return ($this->prefs['delimiter'] = $delimiter);
}
}
return NULL;
}
/**
* NAMESPACE handler (RFC 2342)
*
* @return array Namespace data hash (personal, other, shared)
*/
function getNamespace()
{
if (array_key_exists('namespace', $this->prefs)) {
return $this->prefs['namespace'];
}
if (!$this->getCapability('NAMESPACE')) {
return self::ERROR_BAD;
}
list($code, $response) = $this->execute('NAMESPACE');
if ($code == self::ERROR_OK && preg_match('/^\* NAMESPACE /', $response)) {
$data = $this->tokenizeResponse(substr($response, 11));
}
if (!is_array($data)) {
return $code;
}
$this->prefs['namespace'] = array(
'personal' => $data[0],
'other' => $data[1],
'shared' => $data[2],
);
return $this->prefs['namespace'];
}
/**
* Connects to IMAP server and authenticates.
*
* @param string $host Server hostname or IP
* @param string $user User name
* @param string $password Password
* @param array $options Connection and class options
*
* @return bool True on success, False on failure
*/
function connect($host, $user, $password, $options=null)
{
// set options
if (is_array($options)) {
$this->prefs = $options;
}
// set auth method
if (!empty($this->prefs['auth_type'])) {
$auth_method = strtoupper($this->prefs['auth_type']);
} else {
$auth_method = 'CHECK';
}
$result = false;
// initialize connection
$this->error = '';
$this->errornum = self::ERROR_OK;
$this->selected = null;
$this->user = $user;
$this->host = $host;
$this->logged = false;
// check input
if (empty($host)) {
$this->setError(self::ERROR_BAD, "Empty host");
return false;
}
if (empty($user)) {
$this->setError(self::ERROR_NO, "Empty user");
return false;
}
if (empty($password)) {
$this->setError(self::ERROR_NO, "Empty password");
return false;
}
if (!$this->prefs['port']) {
$this->prefs['port'] = 143;
}
// check for SSL
if ($this->prefs['ssl_mode'] && $this->prefs['ssl_mode'] != 'tls') {
$host = $this->prefs['ssl_mode'] . '://' . $host;
}
if ($this->prefs['timeout'] <= 0) {
- $this->prefs['timeout'] = ini_get('default_socket_timeout');
+ $this->prefs['timeout'] = max(0, intval(ini_get('default_socket_timeout')));
}
// Connect
$this->fp = @fsockopen($host, $this->prefs['port'], $errno, $errstr, $this->prefs['timeout']);
if (!$this->fp) {
if (!$errstr) {
$errstr = "Unknown reason (fsockopen() function disabled?)";
}
$this->setError(self::ERROR_BAD, sprintf("Could not connect to %s:%d: %s", $host, $this->prefs['port'], $errstr));
return false;
}
if ($this->prefs['timeout'] > 0) {
stream_set_timeout($this->fp, $this->prefs['timeout']);
}
$line = trim(fgets($this->fp, 8192));
if ($this->_debug) {
// set connection identifier for debug output
preg_match('/#([0-9]+)/', (string)$this->fp, $m);
$this->resourceid = strtoupper(substr(md5($m[1].$this->user.microtime()), 0, 4));
if ($line)
$this->debug('S: '. $line);
}
// Connected to wrong port or connection error?
if (!preg_match('/^\* (OK|PREAUTH)/i', $line)) {
if ($line)
$error = sprintf("Wrong startup greeting (%s:%d): %s", $host, $this->prefs['port'], $line);
else
$error = sprintf("Empty startup greeting (%s:%d)", $host, $this->prefs['port']);
$this->setError(self::ERROR_BAD, $error);
$this->closeConnection();
return false;
}
// RFC3501 [7.1] optional CAPABILITY response
if (preg_match('/\[CAPABILITY ([^]]+)\]/i', $line, $matches)) {
$this->parseCapability($matches[1], true);
}
// TLS connection
if ($this->prefs['ssl_mode'] == 'tls' && $this->getCapability('STARTTLS')) {
if (version_compare(PHP_VERSION, '5.1.0', '>=')) {
$res = $this->execute('STARTTLS');
if ($res[0] != self::ERROR_OK) {
$this->closeConnection();
return false;
}
if (!stream_socket_enable_crypto($this->fp, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) {
$this->setError(self::ERROR_BAD, "Unable to negotiate TLS");
$this->closeConnection();
return false;
}
// Now we're secure, capabilities need to be reread
$this->clearCapability();
}
}
// Send ID info
if (!empty($this->prefs['ident']) && $this->getCapability('ID')) {
$this->id($this->prefs['ident']);
}
$auth_methods = array();
$result = null;
// check for supported auth methods
if ($auth_method == 'CHECK') {
if ($auth_caps = $this->getCapability('AUTH')) {
$auth_methods = $auth_caps;
}
// RFC 2595 (LOGINDISABLED) LOGIN disabled when connection is not secure
$login_disabled = $this->getCapability('LOGINDISABLED');
if (($key = array_search('LOGIN', $auth_methods)) !== false) {
if ($login_disabled) {
unset($auth_methods[$key]);
}
}
else if (!$login_disabled) {
$auth_methods[] = 'LOGIN';
}
// Use best (for security) supported authentication method
foreach (array('DIGEST-MD5', 'CRAM-MD5', 'CRAM_MD5', 'PLAIN', 'LOGIN') as $auth_method) {
if (in_array($auth_method, $auth_methods)) {
break;
}
}
}
else {
// Prevent from sending credentials in plain text when connection is not secure
if ($auth_method == 'LOGIN' && $this->getCapability('LOGINDISABLED')) {
$this->setError(self::ERROR_BAD, "Login disabled by IMAP server");
$this->closeConnection();
return false;
}
// replace AUTH with CRAM-MD5 for backward compat.
if ($auth_method == 'AUTH') {
$auth_method = 'CRAM-MD5';
}
}
// pre-login capabilities can be not complete
$this->capability_readed = false;
// Authenticate
switch ($auth_method) {
case 'CRAM_MD5':
$auth_method = 'CRAM-MD5';
case 'CRAM-MD5':
case 'DIGEST-MD5':
case 'PLAIN':
$result = $this->authenticate($user, $password, $auth_method);
break;
case 'LOGIN':
$result = $this->login($user, $password);
break;
default:
$this->setError(self::ERROR_BAD, "Configuration error. Unknown auth method: $auth_method");
}
// Connected and authenticated
if (is_resource($result)) {
if ($this->prefs['force_caps']) {
$this->clearCapability();
}
$this->logged = true;
return true;
}
$this->closeConnection();
return false;
}
/**
* Checks connection status
*
* @return bool True if connection is active and user is logged in, False otherwise.
*/
function connected()
{
return ($this->fp && $this->logged) ? true : false;
}
/**
* Closes connection with logout.
*/
function closeConnection()
{
if ($this->logged && $this->putLine($this->nextTag() . ' LOGOUT')) {
$this->readReply();
}
$this->closeSocket();
}
/**
* Executes SELECT command (if mailbox is already not in selected state)
*
* @param string $mailbox Mailbox name
* @param array $qresync_data QRESYNC data (RFC5162)
*
* @return boolean True on success, false on error
*/
function select($mailbox, $qresync_data = null)
{
if (!strlen($mailbox)) {
return false;
}
if ($this->selected === $mailbox) {
return true;
}
/*
Temporary commented out because Courier returns \Noselect for INBOX
Requires more investigation
if (is_array($this->data['LIST']) && is_array($opts = $this->data['LIST'][$mailbox])) {
if (in_array('\\Noselect', $opts)) {
return false;
}
}
*/
$params = array($this->escape($mailbox));
// QRESYNC data items
// 0. the last known UIDVALIDITY,
// 1. the last known modification sequence,
// 2. the optional set of known UIDs, and
// 3. an optional parenthesized list of known sequence ranges and their
// corresponding UIDs.
if (!empty($qresync_data)) {
if (!empty($qresync_data[2]))
$qresync_data[2] = self::compressMessageSet($qresync_data[2]);
$params[] = array('QRESYNC', $qresync_data);
}
list($code, $response) = $this->execute('SELECT', $params);
if ($code == self::ERROR_OK) {
$response = explode("\r\n", $response);
foreach ($response as $line) {
if (preg_match('/^\* ([0-9]+) (EXISTS|RECENT)$/i', $line, $m)) {
$this->data[strtoupper($m[2])] = (int) $m[1];
}
else if (preg_match('/^\* OK \[/i', $line, $match)) {
$line = substr($line, 6);
if (preg_match('/^(UIDNEXT|UIDVALIDITY|UNSEEN) ([0-9]+)/i', $line, $match)) {
$this->data[strtoupper($match[1])] = (int) $match[2];
}
else if (preg_match('/^(HIGHESTMODSEQ) ([0-9]+)/i', $line, $match)) {
$this->data[strtoupper($match[1])] = (string) $match[2];
}
else if (preg_match('/^(NOMODSEQ)/i', $line, $match)) {
$this->data[strtoupper($match[1])] = true;
}
else if (preg_match('/^PERMANENTFLAGS \(([^\)]+)\)/iU', $line, $match)) {
$this->data['PERMANENTFLAGS'] = explode(' ', $match[1]);
}
}
// QRESYNC FETCH response (RFC5162)
else if (preg_match('/^\* ([0-9+]) FETCH/i', $line, $match)) {
$line = substr($line, strlen($match[0]));
$fetch_data = $this->tokenizeResponse($line, 1);
$data = array('id' => $match[1]);
for ($i=0, $size=count($fetch_data); $i<$size; $i+=2) {
$data[strtolower($fetch_data[$i])] = $fetch_data[$i+1];
}
$this->data['QRESYNC'][$data['uid']] = $data;
}
// QRESYNC VANISHED response (RFC5162)
else if (preg_match('/^\* VANISHED [()EARLIER]*/i', $line, $match)) {
$line = substr($line, strlen($match[0]));
$v_data = $this->tokenizeResponse($line, 1);
$this->data['VANISHED'] = $v_data;
}
}
$this->data['READ-WRITE'] = $this->resultcode != 'READ-ONLY';
$this->selected = $mailbox;
return true;
}
return false;
}
/**
* Executes STATUS command
*
* @param string $mailbox Mailbox name
* @param array $items Additional requested item names. By default
* MESSAGES and UNSEEN are requested. Other defined
* in RFC3501: UIDNEXT, UIDVALIDITY, RECENT
*
* @return array Status item-value hash
* @since 0.5-beta
*/
function status($mailbox, $items=array())
{
if (!strlen($mailbox)) {
return false;
}
if (!in_array('MESSAGES', $items)) {
$items[] = 'MESSAGES';
}
if (!in_array('UNSEEN', $items)) {
$items[] = 'UNSEEN';
}
list($code, $response) = $this->execute('STATUS', array($this->escape($mailbox),
'(' . implode(' ', (array) $items) . ')'));
if ($code == self::ERROR_OK && preg_match('/\* STATUS /i', $response)) {
$result = array();
$response = substr($response, 9); // remove prefix "* STATUS "
list($mbox, $items) = $this->tokenizeResponse($response, 2);
// Fix for #1487859. Some buggy server returns not quoted
// folder name with spaces. Let's try to handle this situation
if (!is_array($items) && ($pos = strpos($response, '(')) !== false) {
$response = substr($response, $pos);
$items = $this->tokenizeResponse($response, 1);
if (!is_array($items)) {
return $result;
}
}
for ($i=0, $len=count($items); $i<$len; $i += 2) {
$result[$items[$i]] = $items[$i+1];
}
$this->data['STATUS:'.$mailbox] = $result;
return $result;
}
return false;
}
/**
* Executes EXPUNGE command
*
* @param string $mailbox Mailbox name
* @param string|array $messages Message UIDs to expunge
*
* @return boolean True on success, False on error
*/
function expunge($mailbox, $messages=NULL)
{
if (!$this->select($mailbox)) {
return false;
}
if (!$this->data['READ-WRITE']) {
$this->setError(self::ERROR_READONLY, "Mailbox is read-only");
return false;
}
// Clear internal status cache
unset($this->data['STATUS:'.$mailbox]);
if (!empty($messages) && $messages != '*' && $this->hasCapability('UIDPLUS')) {
$messages = self::compressMessageSet($messages);
$result = $this->execute('UID EXPUNGE', array($messages), self::COMMAND_NORESPONSE);
}
else {
$result = $this->execute('EXPUNGE', null, self::COMMAND_NORESPONSE);
}
if ($result == self::ERROR_OK) {
$this->selected = null; // state has changed, need to reselect
return true;
}
return false;
}
/**
* Executes CLOSE command
*
* @return boolean True on success, False on error
* @since 0.5
*/
function close()
{
$result = $this->execute('CLOSE', NULL, self::COMMAND_NORESPONSE);
if ($result == self::ERROR_OK) {
$this->selected = null;
return true;
}
return false;
}
/**
* Folder subscription (SUBSCRIBE)
*
* @param string $mailbox Mailbox name
*
* @return boolean True on success, False on error
*/
function subscribe($mailbox)
{
$result = $this->execute('SUBSCRIBE', array($this->escape($mailbox)),
self::COMMAND_NORESPONSE);
return ($result == self::ERROR_OK);
}
/**
* Folder unsubscription (UNSUBSCRIBE)
*
* @param string $mailbox Mailbox name
*
* @return boolean True on success, False on error
*/
function unsubscribe($mailbox)
{
$result = $this->execute('UNSUBSCRIBE', array($this->escape($mailbox)),
self::COMMAND_NORESPONSE);
return ($result == self::ERROR_OK);
}
/**
* Folder creation (CREATE)
*
* @param string $mailbox Mailbox name
*
* @return bool True on success, False on error
*/
function createFolder($mailbox)
{
$result = $this->execute('CREATE', array($this->escape($mailbox)),
self::COMMAND_NORESPONSE);
return ($result == self::ERROR_OK);
}
/**
* Folder renaming (RENAME)
*
* @param string $mailbox Mailbox name
*
* @return bool True on success, False on error
*/
function renameFolder($from, $to)
{
$result = $this->execute('RENAME', array($this->escape($from), $this->escape($to)),
self::COMMAND_NORESPONSE);
return ($result == self::ERROR_OK);
}
/**
* Executes DELETE command
*
* @param string $mailbox Mailbox name
*
* @return boolean True on success, False on error
*/
function deleteFolder($mailbox)
{
$result = $this->execute('DELETE', array($this->escape($mailbox)),
self::COMMAND_NORESPONSE);
return ($result == self::ERROR_OK);
}
/**
* Removes all messages in a folder
*
* @param string $mailbox Mailbox name
*
* @return boolean True on success, False on error
*/
function clearFolder($mailbox)
{
$num_in_trash = $this->countMessages($mailbox);
if ($num_in_trash > 0) {
$res = $this->flag($mailbox, '1:*', 'DELETED');
}
if ($res) {
if ($this->selected === $mailbox)
$res = $this->close();
else
$res = $this->expunge($mailbox);
}
return $res;
}
/**
* Returns list of mailboxes
*
* @param string $ref Reference name
* @param string $mailbox Mailbox name
* @param array $status_opts (see self::_listMailboxes)
* @param array $select_opts (see self::_listMailboxes)
*
* @return array List of mailboxes or hash of options if $status_opts argument
* is non-empty.
*/
function listMailboxes($ref, $mailbox, $status_opts=array(), $select_opts=array())
{
return $this->_listMailboxes($ref, $mailbox, false, $status_opts, $select_opts);
}
/**
* Returns list of subscribed mailboxes
*
* @param string $ref Reference name
* @param string $mailbox Mailbox name
* @param array $status_opts (see self::_listMailboxes)
*
* @return array List of mailboxes or hash of options if $status_opts argument
* is non-empty.
*/
function listSubscribed($ref, $mailbox, $status_opts=array())
{
return $this->_listMailboxes($ref, $mailbox, true, $status_opts, NULL);
}
/**
* IMAP LIST/LSUB command
*
* @param string $ref Reference name
* @param string $mailbox Mailbox name
* @param bool $subscribed Enables returning subscribed mailboxes only
* @param array $status_opts List of STATUS options (RFC5819: LIST-STATUS)
* Possible: MESSAGES, RECENT, UIDNEXT, UIDVALIDITY, UNSEEN
* @param array $select_opts List of selection options (RFC5258: LIST-EXTENDED)
* Possible: SUBSCRIBED, RECURSIVEMATCH, REMOTE
*
* @return array List of mailboxes or hash of options if $status_ops argument
* is non-empty.
*/
private function _listMailboxes($ref, $mailbox, $subscribed=false,
$status_opts=array(), $select_opts=array())
{
if (!strlen($mailbox)) {
$mailbox = '*';
}
$args = array();
if (!empty($select_opts) && $this->getCapability('LIST-EXTENDED')) {
$select_opts = (array) $select_opts;
$args[] = '(' . implode(' ', $select_opts) . ')';
}
$args[] = $this->escape($ref);
$args[] = $this->escape($mailbox);
if (!empty($status_opts) && $this->getCapability('LIST-STATUS')) {
$status_opts = (array) $status_opts;
$lstatus = true;
$args[] = 'RETURN (STATUS (' . implode(' ', $status_opts) . '))';
}
list($code, $response) = $this->execute($subscribed ? 'LSUB' : 'LIST', $args);
if ($code == self::ERROR_OK) {
$folders = array();
$last = 0;
$pos = 0;
$response .= "\r\n";
while ($pos = strpos($response, "\r\n", $pos+1)) {
// literal string, not real end-of-command-line
if ($response[$pos-1] == '}') {
continue;
}
$line = substr($response, $last, $pos - $last);
$last = $pos + 2;
if (!preg_match('/^\* (LIST|LSUB|STATUS) /i', $line, $m)) {
continue;
}
$cmd = strtoupper($m[1]);
$line = substr($line, strlen($m[0]));
// * LIST (<options>) <delimiter> <mailbox>
if ($cmd == 'LIST' || $cmd == 'LSUB') {
list($opts, $delim, $mailbox) = $this->tokenizeResponse($line, 3);
// Remove redundant separator at the end of folder name, UW-IMAP bug? (#1488879)
if ($delim) {
$mailbox = rtrim($mailbox, $delim);
}
// Add to result array
if (!$lstatus) {
$folders[] = $mailbox;
}
else {
$folders[$mailbox] = array();
}
// store LSUB options only if not empty, this way
// we can detect a situation when LIST doesn't return specified folder
if (!empty($opts) || $cmd == 'LIST') {
// Add to options array
if (empty($this->data['LIST'][$mailbox]))
$this->data['LIST'][$mailbox] = $opts;
else if (!empty($opts))
$this->data['LIST'][$mailbox] = array_unique(array_merge(
$this->data['LIST'][$mailbox], $opts));
}
}
// * STATUS <mailbox> (<result>)
else if ($cmd == 'STATUS') {
list($mailbox, $status) = $this->tokenizeResponse($line, 2);
for ($i=0, $len=count($status); $i<$len; $i += 2) {
list($name, $value) = $this->tokenizeResponse($status, 2);
$folders[$mailbox][$name] = $value;
}
}
}
return $folders;
}
return false;
}
/**
* Returns count of all messages in a folder
*
* @param string $mailbox Mailbox name
*
* @return int Number of messages, False on error
*/
function countMessages($mailbox, $refresh = false)
{
if ($refresh) {
$this->selected = null;
}
if ($this->selected === $mailbox) {
return $this->data['EXISTS'];
}
// Check internal cache
$cache = $this->data['STATUS:'.$mailbox];
if (!empty($cache) && isset($cache['MESSAGES'])) {
return (int) $cache['MESSAGES'];
}
// Try STATUS (should be faster than SELECT)
$counts = $this->status($mailbox);
if (is_array($counts)) {
return (int) $counts['MESSAGES'];
}
return false;
}
/**
* Returns count of messages with \Recent flag in a folder
*
* @param string $mailbox Mailbox name
*
* @return int Number of messages, False on error
*/
function countRecent($mailbox)
{
if (!strlen($mailbox)) {
$mailbox = 'INBOX';
}
$this->select($mailbox);
if ($this->selected === $mailbox) {
return $this->data['RECENT'];
}
return false;
}
/**
* Returns count of messages without \Seen flag in a specified folder
*
* @param string $mailbox Mailbox name
*
* @return int Number of messages, False on error
*/
function countUnseen($mailbox)
{
// Check internal cache
$cache = $this->data['STATUS:'.$mailbox];
if (!empty($cache) && isset($cache['UNSEEN'])) {
return (int) $cache['UNSEEN'];
}
// Try STATUS (should be faster than SELECT+SEARCH)
$counts = $this->status($mailbox);
if (is_array($counts)) {
return (int) $counts['UNSEEN'];
}
// Invoke SEARCH as a fallback
$index = $this->search($mailbox, 'ALL UNSEEN', false, array('COUNT'));
if (!$index->is_error()) {
return $index->count();
}
return false;
}
/**
* Executes ID command (RFC2971)
*
* @param array $items Client identification information key/value hash
*
* @return array Server identification information key/value hash
* @since 0.6
*/
function id($items=array())
{
if (is_array($items) && !empty($items)) {
foreach ($items as $key => $value) {
$args[] = $this->escape($key, true);
$args[] = $this->escape($value, true);
}
}
list($code, $response) = $this->execute('ID', array(
!empty($args) ? '(' . implode(' ', (array) $args) . ')' : $this->escape(null)
));
if ($code == self::ERROR_OK && preg_match('/\* ID /i', $response)) {
$response = substr($response, 5); // remove prefix "* ID "
$items = $this->tokenizeResponse($response, 1);
$result = null;
for ($i=0, $len=count($items); $i<$len; $i += 2) {
$result[$items[$i]] = $items[$i+1];
}
return $result;
}
return false;
}
/**
* Executes ENABLE command (RFC5161)
*
* @param mixed $extension Extension name to enable (or array of names)
*
* @return array|bool List of enabled extensions, False on error
* @since 0.6
*/
function enable($extension)
{
if (empty($extension)) {
return false;
}
if (!$this->hasCapability('ENABLE')) {
return false;
}
if (!is_array($extension)) {
$extension = array($extension);
}
if (!empty($this->extensions_enabled)) {
// check if all extensions are already enabled
$diff = array_diff($extension, $this->extensions_enabled);
if (empty($diff)) {
return $extension;
}
// Make sure the mailbox isn't selected, before enabling extension(s)
if ($this->selected !== null) {
$this->close();
}
}
list($code, $response) = $this->execute('ENABLE', $extension);
if ($code == self::ERROR_OK && preg_match('/\* ENABLED /i', $response)) {
$response = substr($response, 10); // remove prefix "* ENABLED "
$result = (array) $this->tokenizeResponse($response);
$this->extensions_enabled = array_unique(array_merge((array)$this->extensions_enabled, $result));
return $this->extensions_enabled;
}
return false;
}
/**
* Executes SORT command
*
* @param string $mailbox Mailbox name
* @param string $field Field to sort by (ARRIVAL, CC, DATE, FROM, SIZE, SUBJECT, TO)
* @param string $add Searching criteria
* @param bool $return_uid Enables UID SORT usage
* @param string $encoding Character set
*
* @return rcube_result_index Response data
*/
function sort($mailbox, $field, $add='', $return_uid=false, $encoding = 'US-ASCII')
{
$field = strtoupper($field);
if ($field == 'INTERNALDATE') {
$field = 'ARRIVAL';
}
$fields = array('ARRIVAL' => 1,'CC' => 1,'DATE' => 1,
'FROM' => 1, 'SIZE' => 1, 'SUBJECT' => 1, 'TO' => 1);
if (!$fields[$field]) {
return new rcube_result_index($mailbox);
}
if (!$this->select($mailbox)) {
return new rcube_result_index($mailbox);
}
// RFC 5957: SORT=DISPLAY
if (($field == 'FROM' || $field == 'TO') && $this->getCapability('SORT=DISPLAY')) {
$field = 'DISPLAY' . $field;
}
// message IDs
if (!empty($add))
$add = $this->compressMessageSet($add);
list($code, $response) = $this->execute($return_uid ? 'UID SORT' : 'SORT',
array("($field)", $encoding, 'ALL' . (!empty($add) ? ' '.$add : '')));
if ($code != self::ERROR_OK) {
$response = null;
}
return new rcube_result_index($mailbox, $response);
}
/**
* Executes THREAD command
*
* @param string $mailbox Mailbox name
* @param string $algorithm Threading algorithm (ORDEREDSUBJECT, REFERENCES, REFS)
* @param string $criteria Searching criteria
* @param bool $return_uid Enables UIDs in result instead of sequence numbers
* @param string $encoding Character set
*
* @return rcube_result_thread Thread data
*/
function thread($mailbox, $algorithm='REFERENCES', $criteria='', $return_uid=false, $encoding='US-ASCII')
{
$old_sel = $this->selected;
if (!$this->select($mailbox)) {
return new rcube_result_thread($mailbox);
}
// return empty result when folder is empty and we're just after SELECT
if ($old_sel != $mailbox && !$this->data['EXISTS']) {
return new rcube_result_thread($mailbox);
}
$encoding = $encoding ? trim($encoding) : 'US-ASCII';
$algorithm = $algorithm ? trim($algorithm) : 'REFERENCES';
$criteria = $criteria ? 'ALL '.trim($criteria) : 'ALL';
$data = '';
list($code, $response) = $this->execute($return_uid ? 'UID THREAD' : 'THREAD',
array($algorithm, $encoding, $criteria));
if ($code != self::ERROR_OK) {
$response = null;
}
return new rcube_result_thread($mailbox, $response);
}
/**
* Executes SEARCH command
*
* @param string $mailbox Mailbox name
* @param string $criteria Searching criteria
* @param bool $return_uid Enable UID in result instead of sequence ID
* @param array $items Return items (MIN, MAX, COUNT, ALL)
*
* @return rcube_result_index Result data
*/
function search($mailbox, $criteria, $return_uid=false, $items=array())
{
$old_sel = $this->selected;
if (!$this->select($mailbox)) {
return new rcube_result_index($mailbox);
}
// return empty result when folder is empty and we're just after SELECT
if ($old_sel != $mailbox && !$this->data['EXISTS']) {
return new rcube_result_index($mailbox, '* SEARCH');
}
// If ESEARCH is supported always use ALL
// but not when items are specified or using simple id2uid search
if (empty($items) && preg_match('/[^0-9]/', $criteria)) {
$items = array('ALL');
}
$esearch = empty($items) ? false : $this->getCapability('ESEARCH');
$criteria = trim($criteria);
$params = '';
// RFC4731: ESEARCH
if (!empty($items) && $esearch) {
$params .= 'RETURN (' . implode(' ', $items) . ')';
}
if (!empty($criteria)) {
$params .= ($params ? ' ' : '') . $criteria;
}
else {
$params .= 'ALL';
}
list($code, $response) = $this->execute($return_uid ? 'UID SEARCH' : 'SEARCH',
array($params));
if ($code != self::ERROR_OK) {
$response = null;
}
return new rcube_result_index($mailbox, $response);
}
/**
* Simulates SORT command by using FETCH and sorting.
*
* @param string $mailbox Mailbox name
* @param string|array $message_set Searching criteria (list of messages to return)
* @param string $index_field Field to sort by (ARRIVAL, CC, DATE, FROM, SIZE, SUBJECT, TO)
* @param bool $skip_deleted Makes that DELETED messages will be skipped
* @param bool $uidfetch Enables UID FETCH usage
* @param bool $return_uid Enables returning UIDs instead of IDs
*
* @return rcube_result_index Response data
*/
function index($mailbox, $message_set, $index_field='', $skip_deleted=true,
$uidfetch=false, $return_uid=false)
{
$msg_index = $this->fetchHeaderIndex($mailbox, $message_set,
$index_field, $skip_deleted, $uidfetch, $return_uid);
if (!empty($msg_index)) {
asort($msg_index); // ASC
$msg_index = array_keys($msg_index);
$msg_index = '* SEARCH ' . implode(' ', $msg_index);
}
else {
$msg_index = is_array($msg_index) ? '* SEARCH' : null;
}
return new rcube_result_index($mailbox, $msg_index);
}
function fetchHeaderIndex($mailbox, $message_set, $index_field='', $skip_deleted=true,
$uidfetch=false, $return_uid=false)
{
if (is_array($message_set)) {
if (!($message_set = $this->compressMessageSet($message_set)))
return false;
} else {
list($from_idx, $to_idx) = explode(':', $message_set);
if (empty($message_set) ||
(isset($to_idx) && $to_idx != '*' && (int)$from_idx > (int)$to_idx)) {
return false;
}
}
$index_field = empty($index_field) ? 'DATE' : strtoupper($index_field);
$fields_a['DATE'] = 1;
$fields_a['INTERNALDATE'] = 4;
$fields_a['ARRIVAL'] = 4;
$fields_a['FROM'] = 1;
$fields_a['REPLY-TO'] = 1;
$fields_a['SENDER'] = 1;
$fields_a['TO'] = 1;
$fields_a['CC'] = 1;
$fields_a['SUBJECT'] = 1;
$fields_a['UID'] = 2;
$fields_a['SIZE'] = 2;
$fields_a['SEEN'] = 3;
$fields_a['RECENT'] = 3;
$fields_a['DELETED'] = 3;
if (!($mode = $fields_a[$index_field])) {
return false;
}
/* Do "SELECT" command */
if (!$this->select($mailbox)) {
return false;
}
// build FETCH command string
$key = $this->nextTag();
$cmd = $uidfetch ? 'UID FETCH' : 'FETCH';
$fields = array();
if ($return_uid)
$fields[] = 'UID';
if ($skip_deleted)
$fields[] = 'FLAGS';
if ($mode == 1) {
if ($index_field == 'DATE')
$fields[] = 'INTERNALDATE';
$fields[] = "BODY.PEEK[HEADER.FIELDS ($index_field)]";
}
else if ($mode == 2) {
if ($index_field == 'SIZE')
$fields[] = 'RFC822.SIZE';
else if (!$return_uid || $index_field != 'UID')
$fields[] = $index_field;
}
else if ($mode == 3 && !$skip_deleted)
$fields[] = 'FLAGS';
else if ($mode == 4)
$fields[] = 'INTERNALDATE';
$request = "$key $cmd $message_set (" . implode(' ', $fields) . ")";
if (!$this->putLine($request)) {
$this->setError(self::ERROR_COMMAND, "Unable to send command: $request");
return false;
}
$result = array();
do {
$line = rtrim($this->readLine(200));
$line = $this->multLine($line);
if (preg_match('/^\* ([0-9]+) FETCH/', $line, $m)) {
$id = $m[1];
$flags = NULL;
if ($return_uid) {
if (preg_match('/UID ([0-9]+)/', $line, $matches))
$id = (int) $matches[1];
else
continue;
}
if ($skip_deleted && preg_match('/FLAGS \(([^)]+)\)/', $line, $matches)) {
$flags = explode(' ', strtoupper($matches[1]));
if (in_array('\\DELETED', $flags)) {
continue;
}
}
if ($mode == 1 && $index_field == 'DATE') {
if (preg_match('/BODY\[HEADER\.FIELDS \("*DATE"*\)\] (.*)/', $line, $matches)) {
$value = preg_replace(array('/^"*[a-z]+:/i'), '', $matches[1]);
$value = trim($value);
$result[$id] = $this->strToTime($value);
}
// non-existent/empty Date: header, use INTERNALDATE
if (empty($result[$id])) {
if (preg_match('/INTERNALDATE "([^"]+)"/', $line, $matches))
$result[$id] = $this->strToTime($matches[1]);
else
$result[$id] = 0;
}
} else if ($mode == 1) {
if (preg_match('/BODY\[HEADER\.FIELDS \("?(FROM|REPLY-TO|SENDER|TO|SUBJECT)"?\)\] (.*)/', $line, $matches)) {
$value = preg_replace(array('/^"*[a-z]+:/i', '/\s+$/sm'), array('', ''), $matches[2]);
$result[$id] = trim($value);
} else {
$result[$id] = '';
}
} else if ($mode == 2) {
if (preg_match('/(UID|RFC822\.SIZE) ([0-9]+)/', $line, $matches)) {
$result[$id] = trim($matches[2]);
} else {
$result[$id] = 0;
}
} else if ($mode == 3) {
if (!$flags && preg_match('/FLAGS \(([^)]+)\)/', $line, $matches)) {
$flags = explode(' ', $matches[1]);
}
$result[$id] = in_array('\\'.$index_field, $flags) ? 1 : 0;
} else if ($mode == 4) {
if (preg_match('/INTERNALDATE "([^"]+)"/', $line, $matches)) {
$result[$id] = $this->strToTime($matches[1]);
} else {
$result[$id] = 0;
}
}
}
} while (!$this->startsWith($line, $key, true, true));
return $result;
}
/**
* Returns message sequence identifier
*
* @param string $mailbox Mailbox name
* @param int $uid Message unique identifier (UID)
*
* @return int Message sequence identifier
*/
function UID2ID($mailbox, $uid)
{
if ($uid > 0) {
$index = $this->search($mailbox, "UID $uid");
if ($index->count() == 1) {
$arr = $index->get();
return (int) $arr[0];
}
}
return null;
}
/**
* Returns message unique identifier (UID)
*
* @param string $mailbox Mailbox name
* @param int $uid Message sequence identifier
*
* @return int Message unique identifier
*/
function ID2UID($mailbox, $id)
{
if (empty($id) || $id < 0) {
return null;
}
if (!$this->select($mailbox)) {
return null;
}
$index = $this->search($mailbox, $id, true);
if ($index->count() == 1) {
$arr = $index->get();
return (int) $arr[0];
}
return null;
}
/**
* Sets flag of the message(s)
*
* @param string $mailbox Mailbox name
* @param string|array $messages Message UID(s)
* @param string $flag Flag name
*
* @return bool True on success, False on failure
*/
function flag($mailbox, $messages, $flag) {
return $this->modFlag($mailbox, $messages, $flag, '+');
}
/**
* Unsets flag of the message(s)
*
* @param string $mailbox Mailbox name
* @param string|array $messages Message UID(s)
* @param string $flag Flag name
*
* @return bool True on success, False on failure
*/
function unflag($mailbox, $messages, $flag) {
return $this->modFlag($mailbox, $messages, $flag, '-');
}
/**
* Changes flag of the message(s)
*
* @param string $mailbox Mailbox name
* @param string|array $messages Message UID(s)
* @param string $flag Flag name
* @param string $mod Modifier [+|-]. Default: "+".
*
* @return bool True on success, False on failure
*/
private function modFlag($mailbox, $messages, $flag, $mod = '+')
{
if ($mod != '+' && $mod != '-') {
$mod = '+';
}
if (!$this->select($mailbox)) {
return false;
}
if (!$this->data['READ-WRITE']) {
$this->setError(self::ERROR_READONLY, "Mailbox is read-only");
return false;
}
// Clear internal status cache
if ($flag == 'SEEN') {
unset($this->data['STATUS:'.$mailbox]['UNSEEN']);
}
$flag = $this->flags[strtoupper($flag)];
$result = $this->execute('UID STORE', array(
$this->compressMessageSet($messages), $mod . 'FLAGS.SILENT', "($flag)"),
self::COMMAND_NORESPONSE);
return ($result == self::ERROR_OK);
}
/**
* Copies message(s) from one folder to another
*
* @param string|array $messages Message UID(s)
* @param string $from Mailbox name
* @param string $to Destination mailbox name
*
* @return bool True on success, False on failure
*/
function copy($messages, $from, $to)
{
// Clear last COPYUID data
unset($this->data['COPYUID']);
if (!$this->select($from)) {
return false;
}
// Clear internal status cache
unset($this->data['STATUS:'.$to]);
$result = $this->execute('UID COPY', array(
$this->compressMessageSet($messages), $this->escape($to)),
self::COMMAND_NORESPONSE);
return ($result == self::ERROR_OK);
}
/**
* Moves message(s) from one folder to another.
*
* @param string|array $messages Message UID(s)
* @param string $from Mailbox name
* @param string $to Destination mailbox name
*
* @return bool True on success, False on failure
*/
function move($messages, $from, $to)
{
if (!$this->select($from)) {
return false;
}
if (!$this->data['READ-WRITE']) {
$this->setError(self::ERROR_READONLY, "Mailbox is read-only");
return false;
}
// use MOVE command (RFC 6851)
if ($this->hasCapability('MOVE')) {
// Clear last COPYUID data
unset($this->data['COPYUID']);
// Clear internal status cache
unset($this->data['STATUS:'.$to]);
unset($this->data['STATUS:'.$from]);
$result = $this->execute('UID MOVE', array(
$this->compressMessageSet($messages), $this->escape($to)),
self::COMMAND_NORESPONSE);
return ($result == self::ERROR_OK);
}
// use COPY + STORE +FLAGS.SILENT \Deleted + EXPUNGE
$result = $this->copy($messages, $from, $to);
if ($result) {
// Clear internal status cache
unset($this->data['STATUS:'.$from]);
$result = $this->flag($from, $messages, 'DELETED');
if ($messages == '*') {
// CLOSE+SELECT should be faster than EXPUNGE
$this->close();
}
else {
$this->expunge($from, $messages);
}
}
return $result;
}
/**
* FETCH command (RFC3501)
*
* @param string $mailbox Mailbox name
* @param mixed $message_set Message(s) sequence identifier(s) or UID(s)
* @param bool $is_uid True if $message_set contains UIDs
* @param array $query_items FETCH command data items
* @param string $mod_seq Modification sequence for CHANGEDSINCE (RFC4551) query
* @param bool $vanished Enables VANISHED parameter (RFC5162) for CHANGEDSINCE query
*
* @return array List of rcube_message_header elements, False on error
* @since 0.6
*/
function fetch($mailbox, $message_set, $is_uid = false, $query_items = array(),
$mod_seq = null, $vanished = false)
{
if (!$this->select($mailbox)) {
return false;
}
$message_set = $this->compressMessageSet($message_set);
$result = array();
$key = $this->nextTag();
$request = $key . ($is_uid ? ' UID' : '') . " FETCH $message_set ";
$request .= "(" . implode(' ', $query_items) . ")";
if ($mod_seq !== null && $this->hasCapability('CONDSTORE')) {
$request .= " (CHANGEDSINCE $mod_seq" . ($vanished ? " VANISHED" : '') .")";
}
if (!$this->putLine($request)) {
$this->setError(self::ERROR_COMMAND, "Unable to send command: $request");
return false;
}
do {
$line = $this->readLine(4096);
if (!$line)
break;
// Sample reply line:
// * 321 FETCH (UID 2417 RFC822.SIZE 2730 FLAGS (\Seen)
// INTERNALDATE "16-Nov-2008 21:08:46 +0100" BODYSTRUCTURE (...)
// BODY[HEADER.FIELDS ...
if (preg_match('/^\* ([0-9]+) FETCH/', $line, $m)) {
$id = intval($m[1]);
$result[$id] = new rcube_message_header;
$result[$id]->id = $id;
$result[$id]->subject = '';
$result[$id]->messageID = 'mid:' . $id;
$headers = null;
$lines = array();
$line = substr($line, strlen($m[0]) + 2);
$ln = 0;
// get complete entry
while (preg_match('/\{([0-9]+)\}\r\n$/', $line, $m)) {
$bytes = $m[1];
$out = '';
while (strlen($out) < $bytes) {
$out = $this->readBytes($bytes);
if ($out === NULL)
break;
$line .= $out;
}
$str = $this->readLine(4096);
if ($str === false)
break;
$line .= $str;
}
// Tokenize response and assign to object properties
while (list($name, $value) = $this->tokenizeResponse($line, 2)) {
if ($name == 'UID') {
$result[$id]->uid = intval($value);
}
else if ($name == 'RFC822.SIZE') {
$result[$id]->size = intval($value);
}
else if ($name == 'RFC822.TEXT') {
$result[$id]->body = $value;
}
else if ($name == 'INTERNALDATE') {
$result[$id]->internaldate = $value;
$result[$id]->date = $value;
$result[$id]->timestamp = $this->StrToTime($value);
}
else if ($name == 'FLAGS') {
if (!empty($value)) {
foreach ((array)$value as $flag) {
$flag = str_replace(array('$', '\\'), '', $flag);
$flag = strtoupper($flag);
$result[$id]->flags[$flag] = true;
}
}
}
else if ($name == 'MODSEQ') {
$result[$id]->modseq = $value[0];
}
else if ($name == 'ENVELOPE') {
$result[$id]->envelope = $value;
}
else if ($name == 'BODYSTRUCTURE' || ($name == 'BODY' && count($value) > 2)) {
if (!is_array($value[0]) && (strtolower($value[0]) == 'message' && strtolower($value[1]) == 'rfc822')) {
$value = array($value);
}
$result[$id]->bodystructure = $value;
}
else if ($name == 'RFC822') {
$result[$id]->body = $value;
}
else if ($name == 'BODY') {
$body = $this->tokenizeResponse($line, 1);
if ($value[0] == 'HEADER.FIELDS')
$headers = $body;
else if (!empty($value))
$result[$id]->bodypart[$value[0]] = $body;
else
$result[$id]->body = $body;
}
}
// create array with header field:data
if (!empty($headers)) {
$headers = explode("\n", trim($headers));
foreach ($headers as $resln) {
if (ord($resln[0]) <= 32) {
$lines[$ln] .= (empty($lines[$ln]) ? '' : "\n") . trim($resln);
} else {
$lines[++$ln] = trim($resln);
}
}
foreach ($lines as $str) {
list($field, $string) = explode(':', $str, 2);
$field = strtolower($field);
$string = preg_replace('/\n[\t\s]*/', ' ', trim($string));
switch ($field) {
case 'date';
$result[$id]->date = $string;
$result[$id]->timestamp = $this->strToTime($string);
break;
case 'from':
$result[$id]->from = $string;
break;
case 'to':
$result[$id]->to = preg_replace('/undisclosed-recipients:[;,]*/', '', $string);
break;
case 'subject':
$result[$id]->subject = $string;
break;
case 'reply-to':
$result[$id]->replyto = $string;
break;
case 'cc':
$result[$id]->cc = $string;
break;
case 'bcc':
$result[$id]->bcc = $string;
break;
case 'content-transfer-encoding':
$result[$id]->encoding = $string;
break;
case 'content-type':
$ctype_parts = preg_split('/[; ]+/', $string);
$result[$id]->ctype = strtolower(array_shift($ctype_parts));
if (preg_match('/charset\s*=\s*"?([a-z0-9\-\.\_]+)"?/i', $string, $regs)) {
$result[$id]->charset = $regs[1];
}
break;
case 'in-reply-to':
$result[$id]->in_reply_to = str_replace(array("\n", '<', '>'), '', $string);
break;
case 'references':
$result[$id]->references = $string;
break;
case 'return-receipt-to':
case 'disposition-notification-to':
case 'x-confirm-reading-to':
$result[$id]->mdn_to = $string;
break;
case 'message-id':
$result[$id]->messageID = $string;
break;
case 'x-priority':
if (preg_match('/^(\d+)/', $string, $matches)) {
$result[$id]->priority = intval($matches[1]);
}
break;
default:
if (strlen($field) < 3) {
break;
}
if ($result[$id]->others[$field]) {
$string = array_merge((array)$result[$id]->others[$field], (array)$string);
}
$result[$id]->others[$field] = $string;
}
}
}
}
// VANISHED response (QRESYNC RFC5162)
// Sample: * VANISHED (EARLIER) 300:310,405,411
else if (preg_match('/^\* VANISHED [()EARLIER]*/i', $line, $match)) {
$line = substr($line, strlen($match[0]));
$v_data = $this->tokenizeResponse($line, 1);
$this->data['VANISHED'] = $v_data;
}
} while (!$this->startsWith($line, $key, true));
return $result;
}
/**
* Returns message(s) data (flags, headers, etc.)
*
* @param string $mailbox Mailbox name
* @param mixed $message_set Message(s) sequence identifier(s) or UID(s)
* @param bool $is_uid True if $message_set contains UIDs
* @param bool $bodystr Enable to add BODYSTRUCTURE data to the result
* @param array $add_headers List of additional headers
*
* @return bool|array List of rcube_message_header elements, False on error
*/
function fetchHeaders($mailbox, $message_set, $is_uid = false, $bodystr = false, $add_headers = array())
{
$query_items = array('UID', 'RFC822.SIZE', 'FLAGS', 'INTERNALDATE');
$headers = array('DATE', 'FROM', 'TO', 'SUBJECT', 'CONTENT-TYPE', 'CC', 'REPLY-TO',
'LIST-POST', 'DISPOSITION-NOTIFICATION-TO', 'X-PRIORITY');
if (!empty($add_headers)) {
$add_headers = array_map('strtoupper', $add_headers);
$headers = array_unique(array_merge($headers, $add_headers));
}
if ($bodystr) {
$query_items[] = 'BODYSTRUCTURE';
}
$query_items[] = 'BODY.PEEK[HEADER.FIELDS (' . implode(' ', $headers) . ')]';
$result = $this->fetch($mailbox, $message_set, $is_uid, $query_items);
return $result;
}
/**
* Returns message data (flags, headers, etc.)
*
* @param string $mailbox Mailbox name
* @param int $id Message sequence identifier or UID
* @param bool $is_uid True if $id is an UID
* @param bool $bodystr Enable to add BODYSTRUCTURE data to the result
* @param array $add_headers List of additional headers
*
* @return bool|rcube_message_header Message data, False on error
*/
function fetchHeader($mailbox, $id, $is_uid = false, $bodystr = false, $add_headers = array())
{
$a = $this->fetchHeaders($mailbox, $id, $is_uid, $bodystr, $add_headers);
if (is_array($a)) {
return array_shift($a);
}
return false;
}
function sortHeaders($a, $field, $flag)
{
if (empty($field)) {
$field = 'uid';
}
else {
$field = strtolower($field);
}
if ($field == 'date' || $field == 'internaldate') {
$field = 'timestamp';
}
if (empty($flag)) {
$flag = 'ASC';
} else {
$flag = strtoupper($flag);
}
$c = count($a);
if ($c > 0) {
// Strategy:
// First, we'll create an "index" array.
// Then, we'll use sort() on that array,
// and use that to sort the main array.
// create "index" array
$index = array();
reset($a);
while (list($key, $val) = each($a)) {
if ($field == 'timestamp') {
$data = $this->strToTime($val->date);
if (!$data) {
$data = $val->timestamp;
}
} else {
$data = $val->$field;
if (is_string($data)) {
$data = str_replace('"', '', $data);
if ($field == 'subject') {
$data = preg_replace('/^(Re: \s*|Fwd:\s*|Fw:\s*)+/i', '', $data);
}
$data = strtoupper($data);
}
}
$index[$key] = $data;
}
// sort index
if ($flag == 'ASC') {
asort($index);
} else {
arsort($index);
}
// form new array based on index
$result = array();
reset($index);
while (list($key, $val) = each($index)) {
$result[$key] = $a[$key];
}
}
return $result;
}
function fetchMIMEHeaders($mailbox, $uid, $parts, $mime=true)
{
if (!$this->select($mailbox)) {
return false;
}
$result = false;
$parts = (array) $parts;
$key = $this->nextTag();
$peeks = array();
$type = $mime ? 'MIME' : 'HEADER';
// format request
foreach ($parts as $part) {
$peeks[] = "BODY.PEEK[$part.$type]";
}
$request = "$key UID FETCH $uid (" . implode(' ', $peeks) . ')';
// send request
if (!$this->putLine($request)) {
$this->setError(self::ERROR_COMMAND, "Unable to send command: $request");
return false;
}
do {
$line = $this->readLine(1024);
if (preg_match('/^\* [0-9]+ FETCH [0-9UID( ]+BODY\[([0-9\.]+)\.'.$type.'\]/', $line, $matches)) {
$idx = $matches[1];
$headers = '';
// get complete entry
if (preg_match('/\{([0-9]+)\}\r\n$/', $line, $m)) {
$bytes = $m[1];
$out = '';
while (strlen($out) < $bytes) {
$out = $this->readBytes($bytes);
if ($out === null)
break;
$headers .= $out;
}
}
$result[$idx] = trim($headers);
}
} while (!$this->startsWith($line, $key, true));
return $result;
}
function fetchPartHeader($mailbox, $id, $is_uid=false, $part=NULL)
{
$part = empty($part) ? 'HEADER' : $part.'.MIME';
return $this->handlePartBody($mailbox, $id, $is_uid, $part);
}
function handlePartBody($mailbox, $id, $is_uid=false, $part='', $encoding=NULL, $print=NULL, $file=NULL, $formatted=false, $max_bytes=0)
{
if (!$this->select($mailbox)) {
return false;
}
switch ($encoding) {
case 'base64':
$mode = 1;
break;
case 'quoted-printable':
$mode = 2;
break;
case 'x-uuencode':
case 'x-uue':
case 'uue':
case 'uuencode':
$mode = 3;
break;
default:
$mode = 0;
}
// Use BINARY extension when possible (and safe)
$binary = $mode && preg_match('/^[0-9.]+$/', $part) && $this->hasCapability('BINARY');
$fetch_mode = $binary ? 'BINARY' : 'BODY';
$partial = $max_bytes ? sprintf('<0.%d>', $max_bytes) : '';
// format request
$key = $this->nextTag();
$request = $key . ($is_uid ? ' UID' : '') . " FETCH $id ($fetch_mode.PEEK[$part]$partial)";
$result = false;
$found = false;
// send request
if (!$this->putLine($request)) {
$this->setError(self::ERROR_COMMAND, "Unable to send command: $request");
return false;
}
if ($binary) {
// WARNING: Use $formatting argument with care, this may break binary data stream
$mode = -1;
}
do {
$line = trim($this->readLine(1024));
if (!$line) {
break;
}
// skip irrelevant untagged responses (we have a result already)
if ($found || !preg_match('/^\* ([0-9]+) FETCH (.*)$/', $line, $m)) {
continue;
}
$line = $m[2];
// handle one line response
if ($line[0] == '(' && substr($line, -1) == ')') {
// tokenize content inside brackets
$tokens = $this->tokenizeResponse(preg_replace('/(^\(|\)$)/', '', $line));
for ($i=0; $i<count($tokens); $i+=2) {
if (preg_match('/^(BODY|BINARY)/i', $tokens[$i])) {
$result = $tokens[$i+1];
$found = true;
break;
}
}
if ($result !== false) {
if ($mode == 1) {
$result = base64_decode($result);
}
else if ($mode == 2) {
$result = quoted_printable_decode($result);
}
else if ($mode == 3) {
$result = convert_uudecode($result);
}
}
}
// response with string literal
else if (preg_match('/\{([0-9]+)\}$/', $line, $m)) {
$bytes = (int) $m[1];
$prev = '';
$found = true;
while ($bytes > 0) {
$line = $this->readLine(8192);
if ($line === NULL) {
break;
}
$len = strlen($line);
if ($len > $bytes) {
$line = substr($line, 0, $bytes);
$len = strlen($line);
}
$bytes -= $len;
// BASE64
if ($mode == 1) {
$line = rtrim($line, "\t\r\n\0\x0B");
// create chunks with proper length for base64 decoding
$line = $prev.$line;
$length = strlen($line);
if ($length % 4) {
$length = floor($length / 4) * 4;
$prev = substr($line, $length);
$line = substr($line, 0, $length);
}
else {
$prev = '';
}
$line = base64_decode($line);
}
// QUOTED-PRINTABLE
else if ($mode == 2) {
$line = rtrim($line, "\t\r\0\x0B");
$line = quoted_printable_decode($line);
}
// UUENCODE
else if ($mode == 3) {
$line = rtrim($line, "\t\r\n\0\x0B");
if ($line == 'end' || preg_match('/^begin\s+[0-7]+\s+.+$/', $line)) {
continue;
}
$line = convert_uudecode($line);
}
// default
else if ($formatted) {
$line = rtrim($line, "\t\r\n\0\x0B") . "\n";
}
if ($file) {
if (fwrite($file, $line) === false) {
break;
}
}
else if ($print) {
echo $line;
}
else {
$result .= $line;
}
}
}
} while (!$this->startsWith($line, $key, true));
if ($result !== false) {
if ($file) {
return fwrite($file, $result);
}
else if ($print) {
echo $result;
return true;
}
return $result;
}
return false;
}
/**
* Handler for IMAP APPEND command
*
* @param string $mailbox Mailbox name
* @param string $message Message content
* @param array $flags Message flags
* @param string $date Message internal date
* @param bool $binary Enable BINARY append (RFC3516)
*
* @return string|bool On success APPENDUID response (if available) or True, False on failure
*/
function append($mailbox, &$message, $flags = array(), $date = null, $binary = false)
{
unset($this->data['APPENDUID']);
if ($mailbox === null || $mailbox === '') {
return false;
}
$binary = $binary && $this->getCapability('BINARY');
$literal_plus = !$binary && $this->prefs['literal+'];
if (!$binary) {
$message = str_replace("\r", '', $message);
$message = str_replace("\n", "\r\n", $message);
}
$len = strlen($message);
if (!$len) {
return false;
}
// build APPEND command
$key = $this->nextTag();
$request = "$key APPEND " . $this->escape($mailbox) . ' (' . $this->flagsToStr($flags) . ')';
if (!empty($date)) {
$request .= ' ' . $this->escape($date);
}
$request .= ' ' . ($binary ? '~' : '') . '{' . $len . ($literal_plus ? '+' : '') . '}';
// send APPEND command
if ($this->putLine($request)) {
// Do not wait when LITERAL+ is supported
if (!$literal_plus) {
$line = $this->readReply();
if ($line[0] != '+') {
$this->parseResult($line, 'APPEND: ');
return false;
}
}
if (!$this->putLine($message)) {
return false;
}
do {
$line = $this->readLine();
} while (!$this->startsWith($line, $key, true, true));
// Clear internal status cache
unset($this->data['STATUS:'.$mailbox]);
if ($this->parseResult($line, 'APPEND: ') != self::ERROR_OK)
return false;
else if (!empty($this->data['APPENDUID']))
return $this->data['APPENDUID'];
else
return true;
}
else {
$this->setError(self::ERROR_COMMAND, "Unable to send command: $request");
}
return false;
}
/**
* Handler for IMAP APPEND command.
*
* @param string $mailbox Mailbox name
* @param string $path Path to the file with message body
* @param string $headers Message headers
* @param array $flags Message flags
* @param string $date Message internal date
* @param bool $binary Enable BINARY append (RFC3516)
*
* @return string|bool On success APPENDUID response (if available) or True, False on failure
*/
function appendFromFile($mailbox, $path, $headers=null, $flags = array(), $date = null, $binary = false)
{
unset($this->data['APPENDUID']);
if ($mailbox === null || $mailbox === '') {
return false;
}
// open message file
$in_fp = false;
if (file_exists(realpath($path))) {
$in_fp = fopen($path, 'r');
}
if (!$in_fp) {
$this->setError(self::ERROR_UNKNOWN, "Couldn't open $path for reading");
return false;
}
$body_separator = "\r\n\r\n";
$len = filesize($path);
if (!$len) {
return false;
}
if ($headers) {
$headers = preg_replace('/[\r\n]+$/', '', $headers);
$len += strlen($headers) + strlen($body_separator);
}
$binary = $binary && $this->getCapability('BINARY');
$literal_plus = !$binary && $this->prefs['literal+'];
// build APPEND command
$key = $this->nextTag();
$request = "$key APPEND " . $this->escape($mailbox) . ' (' . $this->flagsToStr($flags) . ')';
if (!empty($date)) {
$request .= ' ' . $this->escape($date);
}
$request .= ' ' . ($binary ? '~' : '') . '{' . $len . ($literal_plus ? '+' : '') . '}';
// send APPEND command
if ($this->putLine($request)) {
// Don't wait when LITERAL+ is supported
if (!$literal_plus) {
$line = $this->readReply();
if ($line[0] != '+') {
$this->parseResult($line, 'APPEND: ');
return false;
}
}
// send headers with body separator
if ($headers) {
$this->putLine($headers . $body_separator, false);
}
// send file
while (!feof($in_fp) && $this->fp) {
$buffer = fgets($in_fp, 4096);
$this->putLine($buffer, false);
}
fclose($in_fp);
if (!$this->putLine('')) { // \r\n
return false;
}
// read response
do {
$line = $this->readLine();
} while (!$this->startsWith($line, $key, true, true));
// Clear internal status cache
unset($this->data['STATUS:'.$mailbox]);
if ($this->parseResult($line, 'APPEND: ') != self::ERROR_OK)
return false;
else if (!empty($this->data['APPENDUID']))
return $this->data['APPENDUID'];
else
return true;
}
else {
$this->setError(self::ERROR_COMMAND, "Unable to send command: $request");
}
return false;
}
/**
* Returns QUOTA information
*
* @return array Quota information
*/
function getQuota()
{
/*
* GETQUOTAROOT "INBOX"
* QUOTAROOT INBOX user/rchijiiwa1
* QUOTA user/rchijiiwa1 (STORAGE 654 9765)
* OK Completed
*/
$result = false;
$quota_lines = array();
$key = $this->nextTag();
$command = $key . ' GETQUOTAROOT INBOX';
// get line(s) containing quota info
if ($this->putLine($command)) {
do {
$line = rtrim($this->readLine(5000));
if (preg_match('/^\* QUOTA /', $line)) {
$quota_lines[] = $line;
}
} while (!$this->startsWith($line, $key, true, true));
}
else {
$this->setError(self::ERROR_COMMAND, "Unable to send command: $command");
}
// return false if not found, parse if found
$min_free = PHP_INT_MAX;
foreach ($quota_lines as $key => $quota_line) {
$quota_line = str_replace(array('(', ')'), '', $quota_line);
$parts = explode(' ', $quota_line);
$storage_part = array_search('STORAGE', $parts);
if (!$storage_part) {
continue;
}
$used = intval($parts[$storage_part+1]);
$total = intval($parts[$storage_part+2]);
$free = $total - $used;
// return lowest available space from all quotas
if ($free < $min_free) {
$min_free = $free;
$result['used'] = $used;
$result['total'] = $total;
$result['percent'] = min(100, round(($used/max(1,$total))*100));
$result['free'] = 100 - $result['percent'];
}
}
return $result;
}
/**
* Send the SETACL command (RFC4314)
*
* @param string $mailbox Mailbox name
* @param string $user User name
* @param mixed $acl ACL string or array
*
* @return boolean True on success, False on failure
*
* @since 0.5-beta
*/
function setACL($mailbox, $user, $acl)
{
if (is_array($acl)) {
$acl = implode('', $acl);
}
$result = $this->execute('SETACL', array(
$this->escape($mailbox), $this->escape($user), strtolower($acl)),
self::COMMAND_NORESPONSE);
return ($result == self::ERROR_OK);
}
/**
* Send the DELETEACL command (RFC4314)
*
* @param string $mailbox Mailbox name
* @param string $user User name
*
* @return boolean True on success, False on failure
*
* @since 0.5-beta
*/
function deleteACL($mailbox, $user)
{
$result = $this->execute('DELETEACL', array(
$this->escape($mailbox), $this->escape($user)),
self::COMMAND_NORESPONSE);
return ($result == self::ERROR_OK);
}
/**
* Send the GETACL command (RFC4314)
*
* @param string $mailbox Mailbox name
*
* @return array User-rights array on success, NULL on error
* @since 0.5-beta
*/
function getACL($mailbox)
{
list($code, $response) = $this->execute('GETACL', array($this->escape($mailbox)));
if ($code == self::ERROR_OK && preg_match('/^\* ACL /i', $response)) {
// Parse server response (remove "* ACL ")
$response = substr($response, 6);
$ret = $this->tokenizeResponse($response);
$mbox = array_shift($ret);
$size = count($ret);
// Create user-rights hash array
// @TODO: consider implementing fixACL() method according to RFC4314.2.1.1
// so we could return only standard rights defined in RFC4314,
// excluding 'c' and 'd' defined in RFC2086.
if ($size % 2 == 0) {
for ($i=0; $i<$size; $i++) {
$ret[$ret[$i]] = str_split($ret[++$i]);
unset($ret[$i-1]);
unset($ret[$i]);
}
return $ret;
}
$this->setError(self::ERROR_COMMAND, "Incomplete ACL response");
return NULL;
}
return NULL;
}
/**
* Send the LISTRIGHTS command (RFC4314)
*
* @param string $mailbox Mailbox name
* @param string $user User name
*
* @return array List of user rights
* @since 0.5-beta
*/
function listRights($mailbox, $user)
{
list($code, $response) = $this->execute('LISTRIGHTS', array(
$this->escape($mailbox), $this->escape($user)));
if ($code == self::ERROR_OK && preg_match('/^\* LISTRIGHTS /i', $response)) {
// Parse server response (remove "* LISTRIGHTS ")
$response = substr($response, 13);
$ret_mbox = $this->tokenizeResponse($response, 1);
$ret_user = $this->tokenizeResponse($response, 1);
$granted = $this->tokenizeResponse($response, 1);
$optional = trim($response);
return array(
'granted' => str_split($granted),
'optional' => explode(' ', $optional),
);
}
return NULL;
}
/**
* Send the MYRIGHTS command (RFC4314)
*
* @param string $mailbox Mailbox name
*
* @return array MYRIGHTS response on success, NULL on error
* @since 0.5-beta
*/
function myRights($mailbox)
{
list($code, $response) = $this->execute('MYRIGHTS', array($this->escape($mailbox)));
if ($code == self::ERROR_OK && preg_match('/^\* MYRIGHTS /i', $response)) {
// Parse server response (remove "* MYRIGHTS ")
$response = substr($response, 11);
$ret_mbox = $this->tokenizeResponse($response, 1);
$rights = $this->tokenizeResponse($response, 1);
return str_split($rights);
}
return NULL;
}
/**
* Send the SETMETADATA command (RFC5464)
*
* @param string $mailbox Mailbox name
* @param array $entries Entry-value array (use NULL value as NIL)
*
* @return boolean True on success, False on failure
* @since 0.5-beta
*/
function setMetadata($mailbox, $entries)
{
if (!is_array($entries) || empty($entries)) {
$this->setError(self::ERROR_COMMAND, "Wrong argument for SETMETADATA command");
return false;
}
foreach ($entries as $name => $value) {
$entries[$name] = $this->escape($name) . ' ' . $this->escape($value);
}
$entries = implode(' ', $entries);
$result = $this->execute('SETMETADATA', array(
$this->escape($mailbox), '(' . $entries . ')'),
self::COMMAND_NORESPONSE);
return ($result == self::ERROR_OK);
}
/**
* Send the SETMETADATA command with NIL values (RFC5464)
*
* @param string $mailbox Mailbox name
* @param array $entries Entry names array
*
* @return boolean True on success, False on failure
*
* @since 0.5-beta
*/
function deleteMetadata($mailbox, $entries)
{
if (!is_array($entries) && !empty($entries)) {
$entries = explode(' ', $entries);
}
if (empty($entries)) {
$this->setError(self::ERROR_COMMAND, "Wrong argument for SETMETADATA command");
return false;
}
foreach ($entries as $entry) {
$data[$entry] = NULL;
}
return $this->setMetadata($mailbox, $data);
}
/**
* Send the GETMETADATA command (RFC5464)
*
* @param string $mailbox Mailbox name
* @param array $entries Entries
* @param array $options Command options (with MAXSIZE and DEPTH keys)
*
* @return array GETMETADATA result on success, NULL on error
*
* @since 0.5-beta
*/
function getMetadata($mailbox, $entries, $options=array())
{
if (!is_array($entries)) {
$entries = array($entries);
}
// create entries string
foreach ($entries as $idx => $name) {
$entries[$idx] = $this->escape($name);
}
$optlist = '';
$entlist = '(' . implode(' ', $entries) . ')';
// create options string
if (is_array($options)) {
$options = array_change_key_case($options, CASE_UPPER);
$opts = array();
if (!empty($options['MAXSIZE'])) {
$opts[] = 'MAXSIZE '.intval($options['MAXSIZE']);
}
if (!empty($options['DEPTH'])) {
$opts[] = 'DEPTH '.intval($options['DEPTH']);
}
if ($opts) {
$optlist = '(' . implode(' ', $opts) . ')';
}
}
$optlist .= ($optlist ? ' ' : '') . $entlist;
list($code, $response) = $this->execute('GETMETADATA', array(
$this->escape($mailbox), $optlist));
if ($code == self::ERROR_OK) {
$result = array();
$data = $this->tokenizeResponse($response);
// The METADATA response can contain multiple entries in a single
// response or multiple responses for each entry or group of entries
if (!empty($data) && ($size = count($data))) {
for ($i=0; $i<$size; $i++) {
if (isset($mbox) && is_array($data[$i])) {
$size_sub = count($data[$i]);
for ($x=0; $x<$size_sub; $x++) {
$result[$mbox][$data[$i][$x]] = $data[$i][++$x];
}
unset($data[$i]);
}
else if ($data[$i] == '*') {
if ($data[$i+1] == 'METADATA') {
$mbox = $data[$i+2];
unset($data[$i]); // "*"
unset($data[++$i]); // "METADATA"
unset($data[++$i]); // Mailbox
}
// get rid of other untagged responses
else {
unset($mbox);
unset($data[$i]);
}
}
else if (isset($mbox)) {
$result[$mbox][$data[$i]] = $data[++$i];
unset($data[$i]);
unset($data[$i-1]);
}
else {
unset($data[$i]);
}
}
}
return $result;
}
return NULL;
}
/**
* Send the SETANNOTATION command (draft-daboo-imap-annotatemore)
*
* @param string $mailbox Mailbox name
* @param array $data Data array where each item is an array with
* three elements: entry name, attribute name, value
*
* @return boolean True on success, False on failure
* @since 0.5-beta
*/
function setAnnotation($mailbox, $data)
{
if (!is_array($data) || empty($data)) {
$this->setError(self::ERROR_COMMAND, "Wrong argument for SETANNOTATION command");
return false;
}
foreach ($data as $entry) {
// ANNOTATEMORE drafts before version 08 require quoted parameters
$entries[] = sprintf('%s (%s %s)', $this->escape($entry[0], true),
$this->escape($entry[1], true), $this->escape($entry[2], true));
}
$entries = implode(' ', $entries);
$result = $this->execute('SETANNOTATION', array(
$this->escape($mailbox), $entries), self::COMMAND_NORESPONSE);
return ($result == self::ERROR_OK);
}
/**
* Send the SETANNOTATION command with NIL values (draft-daboo-imap-annotatemore)
*
* @param string $mailbox Mailbox name
* @param array $data Data array where each item is an array with
* two elements: entry name and attribute name
*
* @return boolean True on success, False on failure
*
* @since 0.5-beta
*/
function deleteAnnotation($mailbox, $data)
{
if (!is_array($data) || empty($data)) {
$this->setError(self::ERROR_COMMAND, "Wrong argument for SETANNOTATION command");
return false;
}
return $this->setAnnotation($mailbox, $data);
}
/**
* Send the GETANNOTATION command (draft-daboo-imap-annotatemore)
*
* @param string $mailbox Mailbox name
* @param array $entries Entries names
* @param array $attribs Attribs names
*
* @return array Annotations result on success, NULL on error
*
* @since 0.5-beta
*/
function getAnnotation($mailbox, $entries, $attribs)
{
if (!is_array($entries)) {
$entries = array($entries);
}
// create entries string
// ANNOTATEMORE drafts before version 08 require quoted parameters
foreach ($entries as $idx => $name) {
$entries[$idx] = $this->escape($name, true);
}
$entries = '(' . implode(' ', $entries) . ')';
if (!is_array($attribs)) {
$attribs = array($attribs);
}
// create entries string
foreach ($attribs as $idx => $name) {
$attribs[$idx] = $this->escape($name, true);
}
$attribs = '(' . implode(' ', $attribs) . ')';
list($code, $response) = $this->execute('GETANNOTATION', array(
$this->escape($mailbox), $entries, $attribs));
if ($code == self::ERROR_OK) {
$result = array();
$data = $this->tokenizeResponse($response);
// Here we returns only data compatible with METADATA result format
if (!empty($data) && ($size = count($data))) {
for ($i=0; $i<$size; $i++) {
$entry = $data[$i];
if (isset($mbox) && is_array($entry)) {
$attribs = $entry;
$entry = $last_entry;
}
else if ($entry == '*') {
if ($data[$i+1] == 'ANNOTATION') {
$mbox = $data[$i+2];
unset($data[$i]); // "*"
unset($data[++$i]); // "ANNOTATION"
unset($data[++$i]); // Mailbox
}
// get rid of other untagged responses
else {
unset($mbox);
unset($data[$i]);
}
continue;
}
else if (isset($mbox)) {
$attribs = $data[++$i];
}
else {
unset($data[$i]);
continue;
}
if (!empty($attribs)) {
for ($x=0, $len=count($attribs); $x<$len;) {
$attr = $attribs[$x++];
$value = $attribs[$x++];
if ($attr == 'value.priv') {
$result[$mbox]['/private' . $entry] = $value;
}
else if ($attr == 'value.shared') {
$result[$mbox]['/shared' . $entry] = $value;
}
}
}
$last_entry = $entry;
unset($data[$i]);
}
}
return $result;
}
return NULL;
}
/**
* Returns BODYSTRUCTURE for the specified message.
*
* @param string $mailbox Folder name
* @param int $id Message sequence number or UID
* @param bool $is_uid True if $id is an UID
*
* @return array/bool Body structure array or False on error.
* @since 0.6
*/
function getStructure($mailbox, $id, $is_uid = false)
{
$result = $this->fetch($mailbox, $id, $is_uid, array('BODYSTRUCTURE'));
if (is_array($result)) {
$result = array_shift($result);
return $result->bodystructure;
}
return false;
}
/**
* Returns data of a message part according to specified structure.
*
* @param array $structure Message structure (getStructure() result)
* @param string $part Message part identifier
*
* @return array Part data as hash array (type, encoding, charset, size)
*/
static function getStructurePartData($structure, $part)
{
$part_a = self::getStructurePartArray($structure, $part);
$data = array();
if (empty($part_a)) {
return $data;
}
// content-type
if (is_array($part_a[0])) {
$data['type'] = 'multipart';
}
else {
$data['type'] = strtolower($part_a[0]);
// encoding
$data['encoding'] = strtolower($part_a[5]);
// charset
if (is_array($part_a[2])) {
while (list($key, $val) = each($part_a[2])) {
if (strcasecmp($val, 'charset') == 0) {
$data['charset'] = $part_a[2][$key+1];
break;
}
}
}
}
// size
$data['size'] = intval($part_a[6]);
return $data;
}
static function getStructurePartArray($a, $part)
{
if (!is_array($a)) {
return false;
}
if (empty($part)) {
return $a;
}
$ctype = is_string($a[0]) && is_string($a[1]) ? $a[0] . '/' . $a[1] : '';
if (strcasecmp($ctype, 'message/rfc822') == 0) {
$a = $a[8];
}
if (strpos($part, '.') > 0) {
$orig_part = $part;
$pos = strpos($part, '.');
$rest = substr($orig_part, $pos+1);
$part = substr($orig_part, 0, $pos);
return self::getStructurePartArray($a[$part-1], $rest);
}
else if ($part > 0) {
return (is_array($a[$part-1])) ? $a[$part-1] : $a;
}
}
/**
* Creates next command identifier (tag)
*
* @return string Command identifier
* @since 0.5-beta
*/
function nextTag()
{
$this->cmd_num++;
$this->cmd_tag = sprintf('A%04d', $this->cmd_num);
return $this->cmd_tag;
}
/**
* Sends IMAP command and parses result
*
* @param string $command IMAP command
* @param array $arguments Command arguments
* @param int $options Execution options
*
* @return mixed Response code or list of response code and data
* @since 0.5-beta
*/
function execute($command, $arguments=array(), $options=0)
{
$tag = $this->nextTag();
$query = $tag . ' ' . $command;
$noresp = ($options & self::COMMAND_NORESPONSE);
$response = $noresp ? null : '';
if (!empty($arguments)) {
foreach ($arguments as $arg) {
$query .= ' ' . self::r_implode($arg);
}
}
// Send command
if (!$this->putLineC($query)) {
$this->setError(self::ERROR_COMMAND, "Unable to send command: $query");
return $noresp ? self::ERROR_COMMAND : array(self::ERROR_COMMAND, '');
}
// Parse response
do {
$line = $this->readLine(4096);
if ($response !== null) {
$response .= $line;
}
} while (!$this->startsWith($line, $tag . ' ', true, true));
$code = $this->parseResult($line, $command . ': ');
// Remove last line from response
if ($response) {
$line_len = min(strlen($response), strlen($line) + 2);
$response = substr($response, 0, -$line_len);
}
// optional CAPABILITY response
if (($options & self::COMMAND_CAPABILITY) && $code == self::ERROR_OK
&& preg_match('/\[CAPABILITY ([^]]+)\]/i', $line, $matches)
) {
$this->parseCapability($matches[1], true);
}
// return last line only (without command tag, result and response code)
if ($line && ($options & self::COMMAND_LASTLINE)) {
$response = preg_replace("/^$tag (OK|NO|BAD|BYE|PREAUTH)?\s*(\[[a-z-]+\])?\s*/i", '', trim($line));
}
return $noresp ? $code : array($code, $response);
}
/**
* Splits IMAP response into string tokens
*
* @param string &$str The IMAP's server response
* @param int $num Number of tokens to return
*
* @return mixed Tokens array or string if $num=1
* @since 0.5-beta
*/
static function tokenizeResponse(&$str, $num=0)
{
$result = array();
while (!$num || count($result) < $num) {
// remove spaces from the beginning of the string
$str = ltrim($str);
switch ($str[0]) {
// String literal
case '{':
if (($epos = strpos($str, "}\r\n", 1)) == false) {
// error
}
if (!is_numeric(($bytes = substr($str, 1, $epos - 1)))) {
// error
}
$result[] = $bytes ? substr($str, $epos + 3, $bytes) : '';
// Advance the string
$str = substr($str, $epos + 3 + $bytes);
break;
// Quoted string
case '"':
$len = strlen($str);
for ($pos=1; $pos<$len; $pos++) {
if ($str[$pos] == '"') {
break;
}
if ($str[$pos] == "\\") {
if ($str[$pos + 1] == '"' || $str[$pos + 1] == "\\") {
$pos++;
}
}
}
if ($str[$pos] != '"') {
// error
}
// we need to strip slashes for a quoted string
$result[] = stripslashes(substr($str, 1, $pos - 1));
$str = substr($str, $pos + 1);
break;
// Parenthesized list
case '(':
case '[':
$str = substr($str, 1);
$result[] = self::tokenizeResponse($str);
break;
case ')':
case ']':
$str = substr($str, 1);
return $result;
break;
// String atom, number, NIL, *, %
default:
// empty string
if ($str === '' || $str === null) {
break 2;
}
// excluded chars: SP, CTL, ), [, ]
if (preg_match('/^([^\x00-\x20\x29\x5B\x5D\x7F]+)/', $str, $m)) {
$result[] = $m[1] == 'NIL' ? NULL : $m[1];
$str = substr($str, strlen($m[1]));
}
break;
}
}
return $num == 1 ? $result[0] : $result;
}
static function r_implode($element)
{
$string = '';
if (is_array($element)) {
reset($element);
foreach ($element as $value) {
$string .= ' ' . self::r_implode($value);
}
}
else {
return $element;
}
return '(' . trim($string) . ')';
}
/**
* Converts message identifiers array into sequence-set syntax
*
* @param array $messages Message identifiers
* @param bool $force Forces compression of any size
*
* @return string Compressed sequence-set
*/
static function compressMessageSet($messages, $force=false)
{
// given a comma delimited list of independent mid's,
// compresses by grouping sequences together
if (!is_array($messages)) {
// if less than 255 bytes long, let's not bother
if (!$force && strlen($messages)<255) {
return $messages;
}
// see if it's already been compressed
if (strpos($messages, ':') !== false) {
return $messages;
}
// separate, then sort
$messages = explode(',', $messages);
}
sort($messages);
$result = array();
$start = $prev = $messages[0];
foreach ($messages as $id) {
$incr = $id - $prev;
if ($incr > 1) { // found a gap
if ($start == $prev) {
$result[] = $prev; // push single id
} else {
$result[] = $start . ':' . $prev; // push sequence as start_id:end_id
}
$start = $id; // start of new sequence
}
$prev = $id;
}
// handle the last sequence/id
if ($start == $prev) {
$result[] = $prev;
} else {
$result[] = $start.':'.$prev;
}
// return as comma separated string
return implode(',', $result);
}
/**
* Converts message sequence-set into array
*
* @param string $messages Message identifiers
*
* @return array List of message identifiers
*/
static function uncompressMessageSet($messages)
{
if (empty($messages)) {
return array();
}
$result = array();
$messages = explode(',', $messages);
foreach ($messages as $idx => $part) {
$items = explode(':', $part);
$max = max($items[0], $items[1]);
for ($x=$items[0]; $x<=$max; $x++) {
$result[] = (int)$x;
}
unset($messages[$idx]);
}
return $result;
}
private function _xor($string, $string2)
{
$result = '';
$size = strlen($string);
for ($i=0; $i<$size; $i++) {
$result .= chr(ord($string[$i]) ^ ord($string2[$i]));
}
return $result;
}
/**
* Converts flags array into string for inclusion in IMAP command
*
* @param array $flags Flags (see self::flags)
*
* @return string Space-separated list of flags
*/
private function flagsToStr($flags)
{
foreach ((array)$flags as $idx => $flag) {
if ($flag = $this->flags[strtoupper($flag)]) {
$flags[$idx] = $flag;
}
}
return implode(' ', (array)$flags);
}
/**
* Converts datetime string into unix timestamp
*
* @param string $date Date string
*
* @return int Unix timestamp
*/
static function strToTime($date)
{
// Clean malformed data
$date = preg_replace(
array(
'/GMT\s*([+-][0-9]+)/', // support non-standard "GMTXXXX" literal
'/[^a-z0-9\x20\x09:+-]/i', // remove any invalid characters
'/\s*(Mon|Tue|Wed|Thu|Fri|Sat|Sun)\s*/i', // remove weekday names
),
array(
'\\1',
'',
'',
), $date);
$date = trim($date);
// if date parsing fails, we have a date in non-rfc format
// remove token from the end and try again
while (($ts = intval(@strtotime($date))) <= 0) {
$d = explode(' ', $date);
array_pop($d);
if (empty($d)) {
break;
}
$date = implode(' ', $d);
}
return $ts < 0 ? 0 : $ts;
}
/**
* CAPABILITY response parser
*/
private function parseCapability($str, $trusted=false)
{
$str = preg_replace('/^\* CAPABILITY /i', '', $str);
$this->capability = explode(' ', strtoupper($str));
if (!isset($this->prefs['literal+']) && in_array('LITERAL+', $this->capability)) {
$this->prefs['literal+'] = true;
}
if ($trusted) {
$this->capability_readed = true;
}
}
/**
* Escapes a string when it contains special characters (RFC3501)
*
* @param string $string IMAP string
* @param boolean $force_quotes Forces string quoting (for atoms)
*
* @return string String atom, quoted-string or string literal
* @todo lists
*/
static function escape($string, $force_quotes=false)
{
if ($string === null) {
return 'NIL';
}
if ($string === '') {
return '""';
}
// atom-string (only safe characters)
if (!$force_quotes && !preg_match('/[\x00-\x20\x22\x25\x28-\x2A\x5B-\x5D\x7B\x7D\x80-\xFF]/', $string)) {
return $string;
}
// quoted-string
if (!preg_match('/[\r\n\x00\x80-\xFF]/', $string)) {
return '"' . addcslashes($string, '\\"') . '"';
}
// literal-string
return sprintf("{%d}\r\n%s", strlen($string), $string);
}
/**
* Set the value of the debugging flag.
*
* @param boolean $debug New value for the debugging flag.
*
* @since 0.5-stable
*/
function setDebug($debug, $handler = null)
{
$this->_debug = $debug;
$this->_debug_handler = $handler;
}
/**
* Write the given debug text to the current debug output handler.
*
* @param string $message Debug mesage text.
*
* @since 0.5-stable
*/
private function debug($message)
{
if ($this->resourceid) {
$message = sprintf('[%s] %s', $this->resourceid, $message);
}
if ($this->_debug_handler) {
call_user_func_array($this->_debug_handler, array(&$this, $message));
} else {
echo "DEBUG: $message\n";
}
}
}

File Metadata

Mime Type
text/x-diff
Expires
Sat, Mar 1, 4:50 AM (1 d, 15 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
165908
Default Alt Text
(217 KB)

Event Timeline