package.xml0000664000076500000240000037613512654565405010146 0ustar Horde_ActiveSync pear.horde.org Horde ActiveSync Server Library Libraries for implementing an ActiveSync server. Michael J Rubinsky mrubinsk mrubinsk@horde.org yes 2016-02-04 2.31.1 2.31.0 stable stable GPL-2.0 * [mjr] Another fix for GHOSTED support on certain clients. 5.3.0 8.0.0alpha1 8.0.0alpha1 1.7.0 Horde_Compress pear.horde.org 2.0.0 3.0.0alpha1 3.0.0alpha1 Horde_Date pear.horde.org 2.0.0 3.0.0alpha1 3.0.0alpha1 Horde_Exception pear.horde.org 2.0.0 3.0.0alpha1 3.0.0alpha1 Horde_Icalendar pear.horde.org 2.0.0 3.0.0alpha1 3.0.0alpha1 Horde_Mapi pear.horde.org 1.0.0 2.0.0alpha1 2.0.0alpha1 Horde_Mime pear.horde.org 2.5.0 3.0.0alpha1 3.0.0alpha1 Horde_Stream pear.horde.org 1.4.0 2.0.0alpha1 2.0.0alpha1 Horde_Support pear.horde.org 2.0.0 3.0.0alpha1 3.0.0alpha1 Horde_Translation pear.horde.org 2.2.0 3.0.0alpha1 3.0.0alpha1 Horde_Util pear.horde.org 2.0.0 3.0.0alpha1 3.0.0alpha1 ctype Horde_Db pear.horde.org 2.1.0 3.0.0alpha1 3.0.0alpha1 Horde_Imap_Client pear.horde.org 2.28.1 3.0.0alpha1 3.0.0alpha1 Horde_Mail pear.horde.org 2.0.0 3.0.0alpha1 3.0.0alpha1 Horde_Mongo pear.horde.org 1.0.2 2.0.0alpha1 2.0.0alpha1 Horde_Text_Filter pear.horde.org 2.0.0 3.0.0alpha1 3.0.0alpha1 Horde_Test pear.horde.org 2.1.0 3.0.0alpha1 3.0.0alpha1 1.0.0alpha1 1.0.0 alpha alpha 2011-03-08 GPL-2.0 * First alpha release for Horde 4. 1.0.0beta1 1.0.0 beta beta 2011-03-16 GPL-2.0 * First beta release for Horde 4. 1.0.0RC1 1.0.0 beta beta 2011-03-22 GPL-2.0 * First release candidate for Horde 4. 1.0.0RC2 1.0.0 beta beta 2011-03-29 GPL-2.0 * Second release candidate for Horde 4. 1.0.0 1.0.0 stable stable 2011-04-06 GPL-2.0 * First stable release for Horde 4. * [jan] Use Horde_Support_Uuid to generate UUIDs. 1.0.1 1.0.0 stable stable 2011-07-05 GPL-2.0 * [mjr] Fix race conditions caused by broken clients sending an inappropriate PING request. * [mjr] Fix issue with initial Android pairing 1.1.0 1.1.0 stable stable 2011-07-27 GPL-2.0 * [mjr] Policykey is now per user, not per device * [mjr] use AS in column aliases to make postgres happy (Bug #10346). * [mjr] Prevent a sync loop in some fringe cases. * [mjr] Improve calendar syncing for iOS devices. 1.1.1 1.1.0 stable stable 2011-07-27 GPL-2.0 * [mjr] Add missing migration file to package.xml 1.1.2 1.1.0 stable stable 2011-08-02 GPL-2.0 * [mjr] Fix sending unnecessary FOLDERSYNC changes. * [mjr] Fix issue with FOLDERSYNC requests that was causing Android clients to PING and SYNC the same collection an unlimited number of times. 1.1.3 1.1.0 stable stable 2011-08-10 GPL-2.0 * [mjr] Fix bug with certain recurrence types due to erroneously outputting a zero as an empty tag (Bug# 10416). * [mjr] Support POOMCONTACTS2 schema in AS 2.5 clients also. 1.1.4 1.1.0 stable stable 2011-08-30 GPL-2.0 * [mjr] Fix sending MONTHLY_NTH and YEARLY_NTH recurrence types (Bug# 10416). * [mjr] Fix synchronization loop due to messages being deleted on server then attempting a change from the client. 1.1.5 1.1.0 stable stable 2011-09-15 GPL-2.0 * [mjr] Fix syncing daily recurrence types. * [mjr] Support additional GAL fields (Bug #10504). * [mjr] Prevent broken clients from sending incoming changes with initial SYNC request (Bug #10461). 1.1.6 1.1.0 stable stable 2011-10-19 GPL-2.0 * [mjr] Prevent duplicate incoming message additions (Bug #10644). 1.1.7 1.1.0 stable stable 2011-11-08 GPL-2.0 * [jan] Allow to run unit tests from installed package. 1.1.8 1.1.0 stable stable 2011-11-13 GPL-2.0 * [mjr] Fix duplicate entries being sent to client when client does not issue GETCHANGES request (Bug #10731). 1.1.9 1.1.0 stable stable 2011-11-16 GPL-2.0 * [mjr] Fix issue with timezones caused by buggy PHP timezone data (Bug #10760). 1.1.10 1.1.0 stable stable 2011-12-13 GPL-2.0 * [mjr] Increase field length for sync_data to support syncing large collections (Bug #10822). * [jan] Fix tests to work with PHPUnit 3.6. 1.1.11 1.1.0 stable stable 2011-12-13 GPL-2.0 * [mjr] Improve support for Android device provisioning. * [mjr] Improve support for timezones in appointments originating on the client. * [mjr] Improve performance when working with timezones on PHP 5.3+ 1.2.0 1.2.0 stable stable 2011-12-23 GPL-2.0 * [mjr] Fix sending meeting request emails when creating a meeting. * [mjr] Fix retrieving the body of SENDMAIL requests. * [mjr] Fix parsing birthday strings on certain android 2.2 clients. 1.2.1 1.2.0 stable stable 2012-01-02 GPL-2.0 * [mjr] Fix REMOTEWIPE functionality for a number of android and iOS devices. 1.2.2 1.2.0 stable stable 2012-01-30 GPL-2.0 * [mjr] Allow disabling of certain policies. * [mjr] Honor PROVISIONING_LOOSE if the device supports PROVISIONING, but not all of our requested policies. * [mjr] Fix issue that could cause PING failure in certain server environments. 1.2.3 1.2.0 stable stable 2012-01-30 GPL-2.0 * [mjr] Tweak policy defaults. Minor compatibility improvements for WAP-Provisioning-XML strings. 1.2.4 1.2.0 stable stable 2012-02-22 GPL-2.0 * [mjr] Added WP7 devices to the list of devices with broken provisioning. * [mjr] Fix issue that could cause improper timezone handling in events passed from device to server in the beginning of a calendar year (Bug #10960). * [mjr] More improvements to security policies and device compatibility. 1.2.5 1.2.0 stable stable 2012-03-21 GPL-2.0 * [mjr] Fix parsing incoming timezones under certain conditions (Bug #11068). 1.2.6 1.2.0 stable stable 2012-05-01 GPL-2.0 * [jan] Fix class name (software-horde@interfasys.ch, Bug #11164). 2.0.0alpha1 2.0.0alpha1 alpha alpha 2012-07-05 GPL-2.0 * First alpha release for Horde 5. * [mjr] Add support for ActiveSync native meeting requests and responses. * [mjr] Security policy support now allows for per user configuration. * [mjr] Add basic Autoconfigure support. * [mjr] Add EAS version 12.1 support. * [mjr] Add EAS version 12.0 support. * [mjr] Add email support. 2.0.0beta1 2.0.0beta1 beta beta 2012-07-19 GPL-2.0 * First beta release for Horde 5. * [mjr] Fix some issues that were causing infinite loops on iOS * [jan] Fix finding locale directory if installed with PEAR. * [mjr] Fix issue where FILTERTYPE parameter was not being honored on some clients. 2.0.0beta2 2.0.0beta1 beta beta 2012-08-07 GPL-2.0 * [mjr] Properly send 401 responses in certain circumstances. * [mjr] Improvements to autoconfigure support. 2.0.0beta3 2.0.0beta1 beta beta 2012-08-29 GPL-2.0 * [mjr] SyncKey is no longer incremented when there are no imported or exported changes after the initial SYNC. * [mjr] No longer send unnecessary SYNC_COMMANDS tag. * [mjr] Build a proper nested folder hierarchy when appropriate. 2.0.0beta4 2.0.0beta1 beta beta 2012-10-12 GPL-2.0 * [mjr] Fix issue causing FOLDERSYNC to fail on postgres. * [mjr] Fix erroneous truncation of CC addresses (Bug #11446). 2.0.0RC1 2.0.0beta1 beta beta 2012-10-26 GPL-2.0 * 2.0.0 2.0.0 stable stable 2012-10-30 GPL-2.0 * First stable release for Horde 5. 2.0.1 2.0.0 stable stable 2012-10-31 GPL-2.0 * [mjr] Fix moving/deleting messages on some devices. 2.0.2 2.0.0 stable stable 2012-11-05 GPL-2.0 * [mjr] Increase size of cache_data field. * [mjr] Add more complete error and debug output when saving syncCache. * [mjr] Attempt to detect infinite sync loops caused by server side errors. 2.0.3 2.0.0 stable stable 2012-11-10 GPL-2.0 * [mjr] Fix detecting mailbox changes on servers that don't return UIDNEXT data. * [mjr] Fix issue in detecting certain text attachments. 2.0.4 2.0.0 stable stable 2012-11-11 GPL-2.0 * [mjr] Fix issue causing a loop during FETCH requests caused by the non-incrementing syncKey. * [mjr] Reduce number of IMAP server calls required when syncronizing email data. * [mjr] Fix issue causing iOS Mail to crash when receiving truncated body data. * [mjr] Improvements to detecting expunged messages. 2.0.5 2.0.0 stable stable 2012-11-11 GPL-2.0 * [mjr] Fix fatal error due to abstract serialize methods. 2.0.6 2.0.0 stable stable 2012-11-16 GPL-2.0 * [jan] Add Dutch translation (Arjen de Korte <build+horde@de-korte.org>). * [mjr] Answer Microsoft Outlook Autodiscover requests (Request # 11639). 2.0.7 2.0.0 stable stable 2012-11-27 GPL-2.0 * [jan] Use binary column for serialized sync state (Bug #11743). * [jan] Fix boolean table columns (Bug #11742). * [mms] Use new Horde_Test layout. 2.0.8 2.0.0 stable stable 2012-12-07 GPL-2.0 * [mjr] Fix issue causing SYNC/PING loop in empty mail folders (Bug #11823). * [mjr] Fix importing message read flag (Bug #11741). * [mjr] Fix charset conversion of HTML messages when original body is plaintext (Bug #11745). * [mjr] Use integer column for message_uid field for IMAP messages (Bug #11742) 2.0.9 2.0.0 stable stable 2012-12-12 GPL-2.0 * [mjr] Improve loop detection logic, preventing unnecessary state resets. * [mjr] Fix charset issues with certain broken emails which can cause iOS to crash. 2.0.10 2.0.0 stable stable 2012-12-12 GPL-2.0 * [mjr] Check for invalid UTF-8 data before sending to client. 2.0.11 2.0.0 stable stable 2012-12-17 GPL-2.0 * [mjr] Fix updating the read flag on iOS. * [jan] Add Basque translation (Ibon Igartua <ibon.igartua@ehu.es>). * [mjr] Set the importance flag on mail messages. * [mjr] Fix detecting mailbox changes on IMAP servers that support CONDSTORE but not per mailbox MODSEQ (Bug #11754). 2.0.12 2.0.0 stable stable 2012-12-23 GPL-2.0 * [mjr] Fix non empty PING requests with empty FOLDERS element. * [mjr] Fix exporting folder deletions (Bug #11918). * [mjr] Improvements to fetching and sending message body. * [mjr] Fix searching mailboxes on certain devices that send MIMESUPPORT flag, like iOS (Bug #11900). 2.0.13 2.0.0 stable stable 2012-12-25 GPL-2.0 * [mjr] Fix regression caused by incorrectly named variable (martin.ament@gmx.de, Bug #11919) 2.0.14 2.0.0 stable stable 2012-12-27 GPL-2.0 * [mjr] Fix DB migration for some RDBMS (Bug #11877). * [mjr] Fix authentication related errors. 2.1.0 2.1.0 stable stable 2013-01-04 GPL-2.0 * [mjr] Fix some possible character set related issues with HTML email. * [mjr] Fix sending boolean policy values. 2.1.1 2.1.0 stable stable 2013-01-04 GPL-2.0 * [mjr] Fix regression caused by fixing some logging in the sync cache. 2.1.2 2.1.0 stable stable 2013-01-15 GPL-2.0 * [mjr] Fix handling missing state when processing COMMANDS. * [mjr] Fix loading state for FOLDERSYNC requests for certain clients. 2.1.3 2.1.0 stable stable 2013-01-19 GPL-2.0 * [mjr] Fix behavior when attempting to fetch attachment of a vanished message. * [mjr] Fix multiple accounts on a single client (Bug #11963). * [mjr] Fix setting PINGable collections on non-empty PING requests. 2.1.4 2.1.0 stable stable 2013-01-20 GPL-2.0 * [mjr] Fix existence checking, again. 2.1.5 2.1.0 stable stable 2013-01-29 GPL-2.0 * [jan] Add French translation (Paul De Vlieger <paul.de_vlieger@moniut.univ-bpclermont.fr>). 2.2.0 2.2.0 stable stable 2013-02-02 GPL-2.0 * [mjr] Fix handling of deleting or moving messages from the client that no longer exist on the server (Bug #12013). * [mjr] Fix setting flags on messages marked for deletion (Bug #12007). 2.2.1 2.2.0 stable stable 2013-02-21 GPL-2.0 * [mjr] Always output the optional POOMCAL_MEETINGSTATUS property to avoid issues with some versions of iOS (Bug #12056). 2.3.0 2.3.0 stable stable 2013-03-01 GPL-2.0 * [mjr] Fix updating state when a PING detected changed has been removed before SYNC runs (Bug #12075). 2.3.1 2.3.0 stable stable 2013-03-03 GPL-2.0 * [mjr] Fix regression introduced in 2.2.1 caused by fix for Bug #12056. * [mjr] Fix synchronizing email messages containing ICalendar attachments with no METHOD parameter (Bug #12083). 2.3.2 2.3.0 stable stable 2013-03-11 GPL-2.0 * [mjr] Various improvements and code cleanup. * [mjr] Some fixes to GETITEMESTIMATE requests. * [mjr] Logging improvements. 2.3.3 2.3.0 stable stable 2013-03-12 GPL-2.0 * [mjr] Work around broken HTC clients when creating appointments with no reminders (Bug #12155). * [mjr] Fix setting User Agent value for certain clients in EAS 12.1 or greater (Bug #12154). * [mjr] Properly set disposition and content-id properties when building attachments from multipart/related parts (Bug #12123). 2.3.4 2.3.0 stable stable 2013-04-12 GPL-2.0 * [mjr] Fix typo that could cause device information to not be saved correctly. * [mjr] Fix authentication clean up during certain request types. 2.3.5 2.3.0 stable stable 2013-04-16 GPL-2.0 * [mjr] Fix issue with importing messages due to bad merge (Bug #12185). 2.3.6 2.3.0 stable stable 2013-04-19 GPL-2.0 * [mjr] Fix returning message property values when the value is defined, but empty (Bug #12197). * [mjr] Fix saving IMAP folder state when CONDSTORE is available but no per-mailbox MODSEQ. * [mjr] Fix mail synch failure due to messages being deleted on server during initial synch. 2.4.0beta1 2.4.0 beta stable 2013-05-02 GPL-2.0 * [mjr] Flag changes will now trigger a positive PING/SYNC if the IMAP server supports per-mailbox MODSEQ and CONDSTORE. * [mjr] Add support for EAS 14.0 and 14.1 (see http://wiki.horde.org/ActiveSync/FeatureGrid for complete feature list). * [mjr] Fix synchronizing certain recurring tasks (Bug #12223, Thomas Jarosch <thomas.jarosch@intra2net.com>) * [mjr] Fix issue that could cause some attachments to become corrupt on the client. 2.4.0beta2 2.4.0 beta stable 2013-05-07 GPL-2.0 * [mjr] Fix building initial message state in certain cases for email collections when IMAP server does not support per mailbox MODSEQ values. * [mjr] Fix fatal error when using the deviceCallback functionality (Bug #12236). * [mjr] Fix creating/deleting mailboxes with non-empty default namespaces (Bug #12224). * [mjr] Fix non-BC change in abstract method signature. 2.4.0beta3 2.4.0 beta stable 2013-05-10 GPL-2.0 * [mjr] Fix mirroring new entries created on certain clients back to the client. * [mjr] Fix creating new objects from Outlook clients. * [mjr] Fix setting incoming heartbeat value. * [mjr] Fix sending updated heartbeat interval when requested heartbeat is out of bounds. 2.4.0beta4 2.4.0 beta stable 2013-05-11 GPL-2.0 * [mjr] Improvements to synchronizing reply/forward state and maillog entries when using Outlook. * [mjr] Improvements and various fixes to detecting client originated changes. * [mjr] Fix numerous issues related to incoming changes with GETCHANGES explicitly set to false. * [mjr] Fix mirroring back changes when an object created on the client is edited on the same client within 1 sync cycle. 2.4.0beta5 2.4.0 beta stable 2013-05-29 GPL-2.0 * [mjr] Fix bug that caused S/MIME messages to always be marked read when synchronized. * [mjr] Rewrite hierarchy handling to allow for client renaming of folders. * [mjr] Fix saving sent mail in EAS >= 14 SENDMAIL requests. 2.4.0RC1 2.4.0 beta stable 2013-05-30 GPL-2.0 * [mjr] Fix issue that could cause some emails to be ignored when sending changes if the email has the same UID as another email from another collection synced in the same request. * [mjr] Fix issue that caused synching to fail due to COLLECTION_TYPE being sent in OPTIONS node in EAS 14+. * [mjr] Fix enforcing forced provisioning. * [mjr] Allow Outlook 2013 to connect even when provisioning is forced. 2.4.0RC2 2.4.0 beta stable 2013-05-31 GPL-2.0 * [mjr] Fixes for broken clients like the S3 that request SMS sync even when told there is no SMS collection. 2.4.0 2.4.0 stable stable 2013-06-04 GPL-2.0 * [mjr] Fix some issues caused by broken non-mime part truncation. * [mjr] Fix handling MIMETRUNCATION for emails with attachments (Bug #12289). * [mjr] Fix missing email body data for certain clients (Bug #12203). * [mjr] Fix requesting email data via ITEMOPERATION command (Bug #12292). * [mjr] Fix a BC break that was causing FOLDERSYNC requests to f fail. 2.5.0 2.5.0 stable stable 2013-06-17 GPL-2.0 * [mjr] Filter out email that falls outside of FILTERTYPE on CONDSTORE servers as well (Bug #12360). * [mjr] Fix detecting changes for servers without CONDSTORE and a non-zero FILTERTYPE (Bug #12316). * [mjr] Fix fetching email messages from search results using ITEMOPERATIONS command (Bug #12212). * [jan] Add Spanish translation (Manuel P. Ayala <mayala@unex.es>). * [mjr] Add support for SyncStamps - either timestamps or backend controlled modification sequences. 2.5.1 2.5.0 stable stable 2013-06-20 GPL-2.0 * [mjr] Fix issue the could cause sync to fail if an incorrectly formatted email date header is encountered. 2.5.2 2.5.0 stable stable 2013-06-20 GPL-2.0 * 2.5.3 2.5.0 stable stable 2013-07-01 GPL-2.0 * [mjr] Fix SmartReply/SmartForward commands when the client does not send Wbxml. * [mjr] Fix some fringe cases where client initiated message deletion may fail. * [mjr] Work around some broken behavior in Blackberry clients (Bug #12370). * [mjr] Fix regression that caused MEETINGRESPONSE requests to fail (Bug #12403). * [mjr] Fix ITEMOPERATIONS requests under 14.1. 2.5.4 2.5.0 stable stable 2013-07-08 GPL-2.0 * [mjr] Fix bug that causes incorrect MIME truncation when the client requests no MIME truncation (Bug #12437). * [mjr] Fix returning proper values for the Horde_ActiveSync_Message_SendMail::replacemime property. The could cause replying to email with some EAS 14+ clients to fail. 2.5.5 2.5.0 stable stable 2013-07-15 GPL-2.0 * [mjr] Fix off by one error when checking WINDOWSIZE, which breaks syncing with clients that have broken WINDOWSIZE implementations (Bug #12443). 2.5.6 2.5.0 stable stable 2013-07-21 GPL-2.0 * [mjr] Fix filtering out changes due to incoming client email requests in certain cases. * [mjr] Send empty POOMMAIL_CATEGORIES tag as Exchange does. * [mjr] Fix sending deletion commands to clients when an item is created on one EAS client, but deleted on another EAS client. 2.6.0 2.6.0 stable stable 2013-08-08 GPL-2.0 * [mjr] Fix obtaining the timestamp of the last successful sync when backend uses modification sequences (Bug #12529). * [mjr] Fix broken Email sync due to clients that send unsolicited SMS data (Bug #12544). * [mjr] Fix returning incorrect syncstamp when adding, editing, or deleting messages and backend uses modification sequences (Bug #12523). * [mjr] Fix bug that could cause a sync loop if history data is removed or a collection is empty and therefore has no history data (Bug #12531). 2.6.1 2.6.0 stable stable 2013-08-08 GPL-2.0 * [mjr] Do not ask the IMAP server to fetch flags if we don't have an empty imap search result (Bug #12551). * [mjr] Fix typos that could cause some sync actions to fail (Bug #12548 and #12523, Thomas Jarosch <thomas.jarosch@intra2net.com>). * [mjr] Ensure proper failure codes are returned when a client addition fails to be added to the backend during a PARTIAL sync. 2.6.2 2.6.0 stable stable 2013-08-18 GPL-2.0 * [mjr] Fix issue that could cause corrupt email attachment downloads on certain webservers due to PHP flushing the output buffer (Bug #12486). * [mjr] Output X-MS-RP header to advertise to clients that a more recent EAS version is supported on the server. * [mjr] Fix issue that could lead to email being duplicated on client due to incorrect Collection Type being detected for unsolicited SMS data (Bug #12544). * [mjr] Fix decoding EAS Timezone blobs on Big Endian systems. * [mjr] Fix race condition where changes may not be sent to the client due to a combination of a long running MOREAVAILABLE sync and server side changes. 2.7.0 2.7.0 stable stable 2013-08-30 GPL-2.0 * [mjr] Fix Wbxml output of SEARCH_TOTAL and SEARCH_RANGE values in SEARCH responses (Bug #12624). * [mjr] Allow reducing the amount of tag content that is logged to the Wbxml log. * [mjr] Fix broken wbxml in certain cases due to incorrect handling of encoding empty array values (Bug #12595). * [mjr] Detect IMAP message deletions on non-CONDSTORE servers during PING requests (Request #12597, Thomas Jarosch <thomas.jarosch@intra2net.com>). * [mjr] Do not initiate a looping SYNC if there were any incoming changes, this prevents an unnecessary backend ping for each collection. * [mjr] Fix issue that could cause message deletions on the server to not be trasmitted to the client if the message falls outside of the FILTER window and the IMAP server does not support CONDSTORE. * [mjr] Don't filter out messages flagged as DELETED during initial sync request. 2.7.1 2.7.0 stable stable 2013-09-01 GPL-2.0 * [mjr] Allow authentication to still be attempted without an available password, allowing for the use of setups using client certificates. * [mjr] Fix detecting EAS version on clients that only support up to 14.0, and send the version encoded in compressed parameters. * [mjr] Fix OPTIONS, SYNC loop due to incorrect determination of when to send the MS-RP header. 2.7.2 2.7.0 stable stable 2013-09-02 GPL-2.0 * [mjr] Fix definition of SMIME_ENCRYPTED and DEVICE_ENCRYPTION policies. * [mjr] Always try to send up to WINDOW_SIZE changes, even if some messages had errors. * [mjr] Disable (broken) sync loop detection. 2.7.3 2.7.0 stable stable 2013-09-05 GPL-2.0 * [mjr] Fix possible infinite loop when a requested message is not found on the serer during a SYNC request. 2.8.0 2.8.0 stable stable 2013-09-27 GPL-2.0 * [mjr] Remove loop counter code that was incorrectly growing sync_cache data (Bug #12707). * [mjr] Ignore incoming SMS delete command. Prevents breaking sync on clients that send SMS data, even though we do not return it as an available collection. * [mjr] Prevent sending invalid UTF-8 data in the Subject header, which could break sync in some clients. * [mjr] Support for SOFTDELETE. 2.8.1 2.8.0 stable stable 2013-09-27 GPL-2.0 * [mjr] Work around for PHP bug 65776, preventing segfaults with certain emails. 2.8.2 2.8.0 stable stable 2013-10-01 GPL-2.0 * [mjr] Remove dependency on unreleased Horde_Stream code. 2.8.3 2.8.0 stable stable 2013-10-08 GPL-2.0 * [mjr] Fix returning contact image in GAL search for those clients that support it. * [mjr] Fix GAL searching for clients that depend on SEARCH_RANGE values. * [mjr] Fix some authentication issues when certain clients issue broken AutoDiscover requests. * [mjr] Fix some minor issues with Autodiscover requests caught by unit testing. 2.8.4 2.8.0 stable stable 2013-10-15 GPL-2.0 * [mjr] Return proper status codes when OOF is unavailable (Bug #12757). * [mjr] Fix fatal crash when conflict is detected (Thomas Jarosch <thomas.jarosch@intra2net.com>, Bug #12755) 2.8.5 2.8.0 stable stable 2013-10-22 GPL-2.0 * [mjr] Improve performance related to conflict detection and change mirroring protection. 2.9.0 2.9.0 stable stable 2013-11-18 GPL-2.0 * [mjr] Fix generating EAS folder uids for devices that can't tell the difference between a string and an integer. * [mjr] Changes to support multiple non-email collections of the same type. * [mjr] Improvements to memory and database usage. * [mjr] Add MongoDB state driver. 2.9.1 2.9.0 stable stable 2013-11-20 GPL-2.0 * [mjr] Add ability to flag a specific device as blocked. * [mjr] Add ability to filter the list of devices on more than username. * [mjr] Attempt to recover old serialized data before forcing a repairing of Email collections. 2.9.2 2.9.0 stable stable 2013-12-17 GPL-2.0 * [mjr] Fix typo that was causing server side searching of mailboxes to fail (Bug #12882). * [mjr] Add workaround for inconsistent handling of POOMCONTACTS date fields that causes fields like birthdays to become offset. 2.10.0 2.10.0 stable stable 2013-12-27 GPL-2.0 * [mjr] Some fixes for EAS 14.0/14.1 ITEMOPERATIONS request parsing. Fixes issues on some clients with broken email sync on Android 4.3 and 4.4. * [mjr] Fix issue that causes sync to break when SMS messages are deleted on the client (Bug #12887). * [mjr] Add support for parsing TNEF data when possible (Bug #12882). 2.10.1 2.10.0 stable stable 2013-12-31 GPL-2.0 * [mjr] Fix issue where attachments are not downloaded in certain situations on EAS version 14+. 2.11.0 2.11.0 stable stable 2014-01-17 GPL-2.0 * [mjr] Fix issue with synchronizing recurring event series exceptions due to missing properties in the exception object. * [mjr] Fixes to S/MIME support for issues that prevent the decrypting of S/MIME encrypted email on certain clients. * [mjr] Improve error handling when state is lost and device continues to request the same state despite being sent the proper status code. * [mjr] Fix returning device information from Mongo driver when multiple user accounts exist on the same device. * [mjr] Improve detection of devices that cannot support non-multiplexed collections. * [mjr] Add support for DocumentLibrary. * [mjr] Fix mailbox searching when number of results exceeds the requested RANGE value. 2.12.0 2.12.0 stable stable 2014-01-23 GPL-2.0 * [mjr] Add support for Recipient Information Cache collection. 2.12.1 2.12.0 stable stable 2014-01-27 GPL-2.0 * [mjr] Fix some edge case issues that could cause issues in sending changes to the client. * [mjr] Fix handling MEETING_RESPONSE requests for certain broken clients (Bug #12934). 2.12.2 2.12.0 stable stable 2014-01-28 GPL-2.0 * [mjr] More fixes for handling meeting responses sent by Outlook (Bug #12934). 2.12.3 2.12.0 stable stable 2014-02-02 GPL-2.0 * [mjr] Improve device property management and forced-multiplex detecection. * [mjr] Attempt a fix for clients that duplicate calendar entries when tags are received in a certain order (Bug #12741). * [mjr] Transparently handle moving from a non-MODSEQ IMAP server to a MODSEQ server (Thomas Jarosch <thomas.jarosch@intra2net.com>, Bug #12941). * [mjr] Fix handling of empty OPTIONS tags. 2.12.4 2.12.0 stable stable 2014-02-05 GPL-2.0 * [mjr] Fix some issues caused by attempting to process iTip responses when saving to sent mail folder (Bug #12956). * [mjr] Work around issues with clients that crash when receiving an empty body element (Bug #12956). 2.13.0 2.13.0 stable stable 2014-03-10 GPL-2.0 * [mjr] More workarounds for PHP 65776, preventing segfaults for text attachments that contain inconsistent line endings. * [mjr] Rely on the IMAP adapter for returning accurate UID values after a message move (Bug #13010). * [mjr] Expose public Horde_ActiveSync_Utils::ensureUtf8() method. 2.13.1 2.13.0 stable stable 2014-03-11 GPL-2.0 * [mjr] Fix regression in sending non Mime encoded messages (Bug #13029). 2.13.2 2.13.0 stable stable 2014-03-13 GPL-2.0 * [mjr] Fix regression that could prevent stale sync requests to persist (Bug #13032). 2.13.3 2.13.0 stable stable 2014-03-24 GPL-2.0 * [mjr] Fix regression that broke synchronization in clients that don't support EAS 14.1 (Bug #13038). * [jan] Optimize SQL query for garbage collection (hannes.brunssen@ewetel.de, Bug #13043). 2.13.4 2.13.0 stable stable 2014-04-03 GPL-2.0 * [jan] Add Danish translation (Erling Preben Hansen <erling@eph.dk>). * [mjr] Support sending PRIMARYSMTPADDRESS in EAS 14.1 (Request #13062). * [mjr] Change the order of WBXML tags for Horde_ActiveSync_Message_Exception to make some older clients happy. * [mjr] Fix some cases where removing a device's state could cause a PING and/or SYNC loop on some older clients. 2.14.0 2.14.0 stable stable 2014-04-16 GPL-2.0 * [mjr] Add Horde_ActiveSync_Rfc822::addStandardHeaders(). 2.14.1 2.14.0 stable stable 2014-04-27 GPL-2.0 * [mjr] Improve detection of Android devices for the purposes of determining support for non-multiplexed collections. * [mjr] Prevent possible PING loop due to PING requests incorrectly containing collections that haven't had a SYNC issued yet. 2.15.0 2.15.0 stable stable 2014-05-15 GPL-2.0 * [mjr] Fix issue that would cause a SYNC loop when changing FILTERTYPE in certain clients (Bug #13182). * [mjr] Fix regression with renaming email folders. * [mjr] Improve workarounds for certain clients that send broken authentication information (Bug #13184). * [jan] Add Hungarian translation (Andras Galos <galosa@netinform.hu>). * [jan] Make SQL state backend compatible with Oracle. 2.15.1 2.15.0 stable stable 2014-05-16 GPL-2.0 * [mjr] Fix issue with clients that support EAS 12.1+ but still use PING commands that would cause the cached heartbeat interval to be cleared. * [mjr] Fix issue where the device managed heartbeat interval would be ignored during PING requests. 2.16.0 2.16.0 stable stable 2014-05-19 GPL-2.0 * [mjr] Allow saving just dirty deviceProperties, and not deviceInfo. * [mjr] Fix return status for SETTINGS_DEVICEINFORMATION requests (Bug #13191). * [mjr] Fix regression that could cause some clients to no longer sync. 2.16.1 2.16.0 stable stable 2014-05-19 GPL-2.0 * [mjr] Fix duplicate items in mailboxes after handling MOVEITEMS requests. 2.16.2 2.16.0 stable stable 2014-05-26 GPL-2.0 * [mjr] Fix detecting forced multiplex values for Android 4.4.0 and greater (Bug #13199). * [mjr] Fix issue with renaming folders (Bug #13196). 2.16.3 2.16.0 stable stable 2014-06-02 GPL-2.0 * [mjr] Truly fix duplicate email in Outlook 2013 when moving/deleting messages. 2.16.4 2.16.0 stable stable 2014-06-06 GPL-2.0 * [mjr] Fix requesting remote wipe when using the MongoDB state driver. 2.16.5 2.16.0 stable stable 2014-06-12 GPL-2.0 * [mjr] Prevent sending non-UTF8 data due to meeting response emails. * [mjr] Remove Horde::debug output. 2.16.6 2.16.0 stable stable 2014-06-22 GPL-2.0 * [mjr] Fix removing orphaned device entries (Bug #13277). 2.16.7 2.16.0 stable stable 2014-06-25 GPL-2.0 * [mjr] Fix ignoring incoming SMS CHANGE commands. 2.16.8 2.16.0 stable stable 2014-06-30 GPL-2.0 * [mjr] Fix issue related to clients with enabled SMS sync causing broken email deletion behavior. 2.16.9 2.16.0 stable stable 2014-07-01 GPL-2.0 * [mjr] Force multiplex contact collections for Outlook 2013. * [mjr] Fix issue sending email when the imap client throws an error when synchronizing the maillog (Bug #13276). 2.16.10 2.16.0 stable stable 2014-07-10 GPL-2.0 * [mjr] Prevent error caused by attempting to update a non-existent state (Bug #13338). 2.16.11 2.16.0 stable stable 2014-07-12 GPL-2.0 * [mjr] Prevent another issue that could cause an error when attempting to update a non-existent state (Bug #13338). 2.17.0 2.17.0 stable stable 2014-08-11 GPL-2.0 * [mjr] Add unofficial WindowsLive codepage extensions. * [mjr] Fix yearly recurrences appearing on incorrect day in certain cases. * [mjr] Add support for synchronizing user-defined IMAP flags as EAS Email categories. * [mjr] Prevent infinite loops due to broken clients sending invalid OPTIONS data (Bug #13405). * [mjr] Prevent fatal WBXML parsing error due to broken BlackBerry clients (Bug #13351). 2.18.0 2.18.0 stable stable 2014-08-21 GPL-2.0 * [mjr] Fix POOMTASKS_SUBORDINALDATE parsing (Bug #13461). * [mjr] Prevent folder cache from being overwritten (Bug #13273). * [mjr] Add Horde_ActiveSync_Imap_Adapter::emptyMailbox(). * [mjr] Fix sending ItemSettings responses for EAS 14.1. 2.18.1 2.18.0 stable stable 2014-09-02 GPL-2.0 * [mjr] Fix issues with 8-bit characters in Subject header in some cases (Bug #13456). 2.19.0 2.19.0 stable stable 2014-09-08 GPL-2.0 * [mjr] Added Horde_ActiveSync_Rfc822::replaceMime(). * [mjr] Added Horde_ActiveSync_Mime class. * [mjr] Added Horde_ActiveSync_Exception_EmailFatalFailure. 2.19.1 2.19.0 stable stable 2014-09-18 GPL-2.0 * [mjr] Fix handling of EAS categories containing spaces in the name. 2.19.2 2.19.0 stable stable 2014-10-02 GPL-2.0 * [mjr] Fix issue with returning proper RI cache data in certain cases. * [mjr] Added ability to sniff out Nine and Samsung Android clients. * [mjr] Improvements to the workaround for broken POOMCONTACTS date field behavior. 2.19.3 2.19.0 stable stable 2014-10-06 GPL-2.0 * [mjr] Some fixes for POOMCONTACT date fields on Samsung Android clients. 2.19.4 2.19.0 stable stable 2014-10-21 GPL-2.0 * [mjr] Fix adding new tasklists from EAS client (Bug #13642). * [mjr] Fix some issues related to synchronizing recurring tasks (Bug #13636). 2.20.0 2.20.0 stable stable 2014-11-04 GPL-2.0 * [mjr] Provisioning requirement is now set after the device object is obtained. * [mjr] Fix handling of MIME truncation in certain cases. * [mjr] Change the order various SYNC reply structures are sent to be more in-line with the spcification. * [mjr] Fix issue where sticky OPTIONS values were lost resulting in incorrect values for MIMESUPPORT and other values (Bug #12970). * [mjr] Added support for differentiating between S/MIME signed and S/MIME encrypted so the content-class property can be properly set (Bug #12970). 2.20.1 2.20.0 stable stable 2014-11-18 GPL-2.0 * [mjr] Fix limiting To: field in email sent to ActiveSync clients. 2.20.2 2.20.0 stable stable 2014-11-21 GPL-2.0 * [mjr] Improvements and fixes to MIME truncation truncation handling. 2.20.3 2.20.0 stable stable 2014-11-24 GPL-2.0 * [mjr] Fix regression in sending MIME messages as a result of a FETCH operation (Bug #13714). * [mjr] Fix issue that could cause mime boundry strings to leak into the message body when the message contained only a single part. 2.20.4 2.20.0 stable stable 2014-11-25 GPL-2.0 * [mjr] Fix incorrect default values for BODYPREF TRUNCATIONSIZE values (Bug #13711). 2.20.5 2.20.0 stable stable 2014-12-05 GPL-2.0 * [mjr] Fix issue that could cause broken sync when an email contains no text parts (Bug #13711). 2.21.0 2.21.0 stable stable 2014-12-15 GPL-2.0 * [mjr] Workaround some broken clients that send null content inside open/close tags instead of an empty tag (Bug #13719). * [mjr] Fix regression in detecting iTip requests (Bug #13739). * [mjr] Add support for clients that issue BODYPARTPREFERENCE options (Bug #13729). * [mjr] Fix handling POOMCALENDAR_UID values to prevent broken meeting request behavior. * [mjr] Fix handling of certain types of broken MIME messages (Bug #13728). 2.22.0 2.22.0 stable stable 2014-12-21 GPL-2.0 * [mjr] Fix some WBXML parsing issues due to empty GET tags in SETTINGS requests. * [mjr] Decode RIGHTSMANAGEMENTINFORMATION nodes. * [mjr] Correctly implement BODYPART handling (Bug #13729). 2.23.0 2.23.0 stable stable 2014-12-24 GPL-2.0 * [mjr] Fix incorrect converting of HTML to plaintext causing incorrect truncation size. * [mjr] Fix BC break due to MessageBodyData improvements (Bug #13768). 2.23.1 2.23.0 stable stable 2015-01-03 GPL-2.0 * [mjr] Add some more TRUNCATION related workarounds/fixes. * [mjr] Improve/fix support for handling OOF messages (Bug #13719). 2.24.0 2.24.0 stable stable 2015-01-09 GPL-2.0 * [mjr] Improve handling of FILTERTYPE changes after initial SYNC (Bug #13781). * [mjr] Add Date header to emails sent from broken HTC clients (Bug #13784). 2.24.1 2.24.0 stable stable 2015-02-05 GPL-2.0 * [mjr] Fix moving a mailbox to a new parent (Bug #13839). * [mjr] Always send the attachmentsenabled policy, even when it's the default value of 'enabled' (Bug #13827). 2.25.0 2.25.0 stable stable 2015-02-18 GPL-2.0 * [mjr] Fix handling of PARTIAL and EMPTY sync requests in certain cases (Bug #13863). 2.26.0 2.26.0 stable stable 2015-02-23 GPL-2.0 * [mjr] Return email search results in descending order by arrival date. * [mjr] Add HTCOne Mini to device detection and set multiplex ability accordingly. 2.26.1 2.26.0 stable stable 2015-03-04 GPL-2.0 * [mjr] Detect stock Android 5.0 multiplex settings. * [mjr] Fix broken sync due to clients that send badly formatted date strings. * [mjr] Fix fatal error when dealing with iTip replies from vTodo requests. 2.27.0 2.27.0 stable stable 2015-04-02 GPL-2.0 * [mjr] Fix issue where badly formed emails could cause out of memory error. * [mjr] Fix issue that could break sync if a badly formed email address could not be parsed. * [mjr] Fix PARTIAL SYNC handling when no COLLECTIONS are provided (Bug #13912). 2.27.1 2.27.0 stable stable 2015-04-28 GPL-2.0 * [jan] Fix issues with certain locales like Turkish. 2.28.0 2.28.0 stable stable 2015-05-18 GPL-2.0 * [mjr] Fix issue that could cause sending the same email to the client multiple times (Bug #13985). * [mjr] Performance and memory improvements, especially during initial sync to Outlook clients. 2.28.1 2.28.0 stable stable 2015-05-19 GPL-2.0 * [mjr] Fix regression that could cause loss of mailbox sync. 2.28.2 2.28.0 stable stable 2015-05-25 GPL-2.0 * [mjr] Fix regression causing useless backend query. * [mjr] Fix bug causing changes to be sent as new messages, thus causing them to be ignored by most clients. * [mjr] Fix sending SMIME signed/encrypted mail when using streams. 2.28.3 2.28.0 stable stable 2015-05-27 GPL-2.0 * [mjr] Fix off by one error that could cause some errors in downstream client code. * [mjr] Fix detecting changes in email message "Flag" sent from certain clients. 2.28.4 2.28.0 stable stable 2015-06-01 GPL-2.0 * [mjr] Fix issue when using REPLACEMIME feature of SENDMAIL and some other edge cases (Bug #13901). 2.28.5 2.28.0 stable stable 2015-06-11 GPL-2.0 * [mjr] Reduce memory usage during IMAP FETCH with very large mailboxes. * [mjr] Workaround broken CGI behavior of sending incorrect authentication headers. * [mjr] Fix issues with detecting changes in FILTERTYPE. * [mjr] Performance improvements during IMAP server change detection for CONDSTORE servers, especially during FILTERTYPE changes. 2.28.6 2.28.0 stable stable 2015-07-22 GPL-2.0 * [mjr] Fix display of certain Multipart/Report messages, on certain clients. * [mjr] Improve handling of authentication/policy errors. * [mjr] Fix renaming email folder when using PostgreSQL state storage (Bug #14019, horde@albasoft.com). 2.29.0 2.29.0 stable stable 2015-08-20 GPL-2.0 * [mjr] Work around bug in various versions of PHP 5.5 and 5.6 regarding base64-encode stream filters (Bug #14086). * [mjr] Fix issue where incorrect text/html part is choosen as the main body part. 2.29.1 2.29.0 stable stable 2015-09-01 GPL-2.0 * [mjr] Explicitly request a FOLDERSYNC when backend id is not found. * [mjr] Fix corruption of base64 encoded text parts in certain cases (Bug #14092). 2.29.2 2.29.0 stable stable 2015-09-22 GPL-2.0 * [mjr] Don't take sub-parts of rfc822/message parts as attachments. * [mjr] Fix handling of meeting requests that contain no DTEND attribute. * [mjr] Fix parsing of SETTINGS request that was causing a harmless WBXML parsing error. 2.30.0 2.30.0 stable stable 2015-10-20 GPL-2.0 * [mjr] Fix issue in sending email from client due to input stream not being seekable on some systems (Bug #13160). * [mjr] Work around bug in early versions of iOS that could cause a contact's picture to be removed on the server. * [mjr] Improved client and version detection, especially for iOS devices. * [mjr] Fixed bug that was causing SUPPORTED data to be overwritten. * [mjr] Change some ERR log level entries to WARN and some other small logging tweaks. 2.30.1 2.30.0 stable stable 2015-10-28 GPL-2.0 * [mjr] Improved workaround for clients with broken picture tag handling. 2.30.2 2.30.0 stable stable 2015-11-22 GPL-2.0 * [mjr] Fix issue that prevents new device being added if an entry exists in the device user table but not the device table (Bug #14147). * [mjr] OL2016 added to device multiplex settings. 2.30.3 2.30.0 stable stable 2015-12-12 GPL-2.0 * [mjr] Allow global request window size limit to be overridden. * [mjr] Fix issue where global request window size was being reached prematurely. * [mjr] Fix potential fatal error due to incorrect exception name. 2.30.4 2.30.0 stable stable 2016-01-12 GPL-2.0 * [mjr] Fix mailbox searches with non 7-bit characters. * [jan] Mark PHP 7 as supported. * [jan] Improve Oracle compatibility. 2.30.5 2.30.0 stable stable 2016-01-14 GPL-2.0 * [mjr] Fix regression introduced by checking device remote wipe during PING requests. 2.30.6 2.30.0 stable stable 2016-01-20 GPL-2.0 * [mjr] Fix RECUR_YEARLY_DAY recurrences showing up every day. 2.31.0 2.31.0 stable stable 2016-02-04 GPL-2.0 * [mjr] Fix possible leaking of Horde_Imap_Client Exceptions. * [mjr] Fix reversed login in GHOSTED element handling. * [mjr] Changes for EAS 16.0 support. * [mjr] Fix error when attempting to rewrite previous syncstate. 2.31.1 2.31.0 stable stable 2016-02-04 GPL-2.0 * [mjr] Another fix for GHOSTED support on certain clients. Horde_ActiveSync-2.31.1/doc/Horde/ActiveSync/COPYING0000664000076500000240000003610012654565405016634 0ustar NOTE: According to sec. 8 of the GNU GENERAL PUBLIC LICENSE, Version 2, the distribution of the Horde_ActiveSync module in or to the United States of America is excluded from the scope of this license. The Horde_ActiveSync module is licensed under the GPL Version 2 only. GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS Horde_ActiveSync-2.31.1/doc/Horde/ActiveSync/TODO0000664000076500000240000001502412654565405016273 0ustar TODO ===== - After kronolith gets support for different organizer/owner fields, must properly set the organizer for meeting responses. Currently, the event created from a meeting response does not contain the organizer of the original event. So when the event is sync'd back to the device, it allows the user to cancel the event and send updates to all attendees. Also, it will then be possible to automatically add responses to the kronolith event from the incoming activesync response message. - Email recurrence related properties for recurring meeting requests. - SCHEMA support in ITEMOPERATIONS requests. Don't have client that supports currently. - Implement some sort of issue tracking/counting to prevent loops due to things like clients not supporting standard status codes, like FOLDERSYNC_REQUIRED. Send the error code up to a maximum number of times, and after that send a server 500-ish error code indicating the client should stop trying. Probably store data in device object, but need to figure out how to prevent race conditions since multiple requests can be in progress. Perhaps some sort of shared memory cache? - Perhaps if we use a shared memory solution, we can also possibly create some sort of top-like application to monitor EAS usage. Make it easier to find troublesome devices etc.... - Work out / more fully test Task recurrence. Especially completion of a single instance using DEADRECUR etc... (most work is in Nag, but put here to keep sync todos together). BC BREAKING (i.e., Horde 6). ============================ - Move all non-specific constants to single class. - Clean up the various foldertype constant messes. I.e., probably store the Horde_ActiveSync::FOLDER_TYPE_* and Horde_ActiveSync::CLASS_* values in the saved state instead of having to switch between them in various places. (Some client commands are sent using the CLASS, some using the FOLDER_TYPE). - Clean up and refactor the folder creation/editing/deleting methods in the backend. They need to be normalized in accepting/returning the same objects now that we support multiple folders per non-email collection. - Refactor Horde_ActiveSync_State_*::listDevices() to return a Horde_ActiveSync_Device object instead of a hash keyed by backend field names and to take the object property names as filters instead of backend field names. - Clean up logger creation/setting/log_level setting. - Consolidate folderUid <-> backend id mapping methods into a single place. Currently they live in both the collection handler and the backend driver. - Refactor out the need for the various static ::_* properties in the main ActiveSync class. - Change the readable names of the WBXML tags to reflect what they are called in the MS-AS* documents, and not what Z-Push's developers decided to call them. - Look at extracting something like Request_Parser and Request_Handler classes to separate the reading of the request from the handling of it. The parser should return some standard request object that can be passed to the handler that contains the pertinent information. Use temporary streams to hold any incoming message data to preserve the low memory footprint from dealing with changes as we read them. - Maintain the serverid -> backendid map in a single place and stop storing backend ids (i.e., IMAP folder names) in collection/state/folder cache etc... - Possibly move the syncCache and state into the device object so we have a single object to pass around that has access to all. - Extract a basic Horde_ActiveSync_Storage class? At the very least we need to rename the current Horde_ActiveSync_State_* classes to Horde_ActiveSync_Storage since they now deal with more than device state. - Implmement Horde_ActiveSync_HttpRequest object and remove dependency on Horde_Controller. We only use the Horde_Controller_Request_Http object from that package. - Implmement A Horde_ActiveSync_Response object and move functionality currently living in the RPC layer (sending back certain headers, etc...) into this class. - Implement a Horde_ActiveSync_Change_Filter class/interface. Used to implement workarounds for broken clients. E.g., filter out the ADD commands sent in response to MOVEITEMS for Outlook clients. Use a similar pattern for other types of broken client behavior. - No longer ignore SMS synchronization in the library, but pass it up to the backend and the let the backend decide what to do with it. Since the SMS ability of EAS uses the device to actually send the SMS, I could foresee some added ability in IMP, or maybe a separate app, that allows managing this. - Break up Horde_ActiveSync_Driver::getMessage(). Add _getCalendarMessage() etc... and move base logic for calling the correct method into the base class. - Decouple the codepage definitions from the Encoder/Decoder class. Break them out into individual classes. - Pass the Horde_ActiveSync::FILTERTYPE_ constant directly to the driver, and let the driver calculate the needed time slice if needed. Needed to correctly deal with Horde_ActiveSync::FILTERTYPE_INCOMPLETETASKS. - Introduce some sort of filter or similar data in the definitions of the wbxml fields to allow for things like specifying the maximum size of a field allowed. - Move Horde_Core_ActiveSync_Mail into the Horde_ActiveSync library. Horde_ActiveSync_Mailer (?). This is functionality that should be provided out of the box from the library. Need to figure out the best way to inject the Horde specific things, like the actual mailer and identity data. - Implement something like Horde_ActiveSync_Sync_Options:: to encapsulate loading and transporting the various collection options/bodyprefs around. - Move non server-ish methods out of Horde_ActiveSync(_Server). E.g., getMimeTrucSize() etc... - Implment a "changes" object that uses either an array/spl array, or a temporary php stream (for the larger initial email syncs) to store the actual change data structure. Needed to avoid hitting PHP memory limit for very large mailboxes when synchronizing to clients that pull the entire mailbox down (like Outlook). Also, standardize the data format instead of having to (re)build a flat array from a multidimensional array of 'changes', 'add' etc.. - Use a configuration object (or maybe a Builder) for constructing the ActiveSync Driver and Server objects. At the very least, need to clean up the constructor parameters and various configuration options mess. (E.g., the "PING" configuration values are no longer really all strictly related to PING requests). Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Connector/Exporter.php0000664000076500000240000003516412654565405022066 0ustar * @package ActiveSync */ /** * Horde_ActiveSync_Connector_Exporter:: Outputs necessary wbxml to device. * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2009-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync */ class Horde_ActiveSync_Connector_Exporter { /** * The wbxml encoder * * @var Horde_ActiveSync_Wbxml_Encoder */ protected $_encoder; /** * Local cache of object ids we have already dealt with. * * @var array */ protected $_seenObjects = array(); /** * Array of folder objects that have changed. * Used when exporting folder structure changes since they are not streamed * from this object. * * @var array */ public $changed = array(); /** * Array of folder ids that have been deleted on the server. * * @var array */ public $deleted = array(); /** * Tracks the total number of folder changes * * @var integer */ public $count = 0; /** * Local cache of changes to send. * * @var array */ protected $_changes = array(); /** * Counter of changes sent. * * @var integer */ protected $_step = 0; /** * Currently syncing collection. * * @var array */ protected $_currentCollection; /** * The ActiveSync server object. * * @var Horde_ActiveSync */ protected $_as; /** * Process id for logging. * * @var integer */ protected $_procid; /** * Const'r * * @param Horde_ActiveSync $as The ActiveSync server. * @param Horde_ActiveSync_Wbxml_Encoder $encoder The encoder * * @return Horde_ActiveSync_Connector_Exporter */ public function __construct( Horde_ActiveSync $as, Horde_ActiveSync_Wbxml_Encoder $encoder = null) { $this->_as = $as; $this->_encoder = $encoder; $this->_logger = $as->logger; $this->_procid = getmypid(); } /** * Set the changes to send to the client. * * @param array $changes The changes array returned from the collection * handler. * @param array $collection The collection we are currently syncing. */ public function setChanges($changes, $collection) { $this->_changes = $changes; $this->_seenObjects = array(); $this->_step = 0; $this->_currentCollection = $collection; } /** * Sends the next change in the set to the client. * * @return boolean|Horde_Exception True if more changes can be sent false if * all changes were sent, Horde_Exception if * there was an error sending an item. */ public function sendNextChange() { if (empty($this->_currentCollection)) { return $this->_sendNextFolderSyncChange(); } else { return $this->_sendNextChange(); } } protected function _getNextChange() { $change = $this->_changes[$this->_step]; if (!is_array($change)) { // This is an initial sync, so we know it's a CHANGE_TYPE_CHANGE // and a new message with no flag changes etc... $change = array( 'id' => $change, 'type' => Horde_ActiveSync::CHANGE_TYPE_CHANGE, 'flags' => Horde_ActiveSync::FLAG_NEWMESSAGE ); } return $change; } /** * Sends the next message change to the client. * * @return @see self::sendNextChange() */ protected function _sendNextChange() { if ($this->_step >= count($this->_changes)) { return false; } $change = $this->_getNextChange(); // Ignore this change, no UID value, keep trying until we get a // good entry or we run out of entries. while (empty($change['id']) && $this->_step < count($this->_changes) - 1) { $this->_logger->notice(sprintf( 'Missing UID value for an entry in: %s. Details: %s.', $this->_currentCollection['id'], print_r($change, true) )); $this->_step++; $change = $this->_getNextChange(); } // Actually export the change by calling the appropriate // method to output the correct wbxml for this change. if (empty($change['ignore'])) { switch($change['type']) { case Horde_ActiveSync::CHANGE_TYPE_CHANGE: try { $message = $this->_as->driver->getMessage( $this->_currentCollection['serverid'], $change['id'], $this->_currentCollection); $message->flags = (isset($change['flags'])) ? $change['flags'] : false; $this->messageChange($change['id'], $message); } catch (Horde_Exception_NotFound $e) { $this->_logger->notice(sprintf( '[%s] Message gone or error reading message from server: %s', $this->_procid, $e->getMessage())); $this->_as->state->updateState($change['type'], $change); $this->_step++; return $e; } catch (Horde_ActiveSync_Exception $e) { $this->_logger->err(sprintf( '[%s] Unknown backend error skipping message: %s', $this->_procid, $e->getMessage())); $this->_as->state->updateState($change['type'], $change); $this->_step++; return $e; } break; case Horde_ActiveSync::CHANGE_TYPE_DELETE: $this->messageDeletion($change['id']); break; case Horde_ActiveSync::CHANGE_TYPE_SOFTDELETE: $this->messageDeletion($change['id'], true); break; case Horde_ActiveSync::CHANGE_TYPE_FLAGS: // Read flag. $message = Horde_ActiveSync::messageFactory('Mail'); $message->flags = Horde_ActiveSync::CHANGE_TYPE_CHANGE; $message->read = isset($change['flags']['read']) ? $change['flags']['read'] : false; // "Flagged" flag. if (isset($change['flags']['flagged']) && $this->_as->device->version >= Horde_ActiveSync::VERSION_TWELVE) { $flag = Horde_ActiveSync::messageFactory('Flag'); $flag->flagstatus = $change['flags']['flagged'] == 1 ? Horde_ActiveSync_Message_Flag::FLAG_STATUS_ACTIVE : Horde_ActiveSync_Message_Flag::FLAG_STATUS_CLEAR; $message->flag = $flag; } // Categories if (!empty($change['categories']) && $this->_as->device->version > Horde_ActiveSync::VERSION_TWELVEONE) { $message->categories = $change['categories']; } // Verbs if ($this->_as->device->version >= Horde_ActiveSync::VERSION_FOURTEEN) { if (isset($change['flags'][Horde_ActiveSync::CHANGE_REPLY_STATE])) { $message->lastverbexecuted = Horde_ActiveSync_Message_Mail::VERB_REPLY_SENDER; $message->lastverbexecutiontime = new Horde_Date($change['flags'][Horde_ActiveSync::CHANGE_REPLY_STATE]); } elseif (isset($change['flags'][Horde_ActiveSync::CHANGE_REPLYALL_STATE])) { $message->lastverbexecuted = Horde_ActiveSync_Message_Mail::VERB_REPLY_ALL; $message->lastverbexecutiontime = new Horde_Date($change['flags'][Horde_ActiveSync::CHANGE_REPLYALL_STATE]); } elseif (isset($change['flags'][Horde_ActiveSync::CHANGE_FORWARD_STATE])) { $message->lastverbexecuted = Horde_ActiveSync_Message_Mail::VERB_FORWARD; $message->lastverbexecutiontime = new Horde_Date($change['flags'][Horde_ActiveSync::CHANGE_FORWARD_STATE]); } } // Export it. $this->messageChange($change['id'], $message); break; case Horde_ActiveSync::CHANGE_TYPE_MOVE: $this->messageMove($change['id'], $change['parent']); break; } } // Update the state. $this->_as->state->updateState($change['type'], $change); $this->_step++; return true; } /** * Sends the next folder change to the client. * * @return @see self::sendNextChange() */ protected function _sendNextFolderSyncChange() { if ($this->_step < count($this->_changes)) { $change = $this->_changes[$this->_step]; switch($change['type']) { case Horde_ActiveSync::CHANGE_TYPE_CHANGE: // Folder add/change. if ($folder = $this->_as->driver->getFolder($change['serverid'])) { // @TODO BC HACK. Need to ensure we have a _serverid here. // REMOVE IN H6. if (empty($folder->_serverid)) { $folder->_serverid = $folder->serverid; } $stat = $this->_as->driver->statFolder( $change['id'], $folder->parentid, $folder->displayname, $folder->_serverid, $folder->type); $this->folderChange($folder); } else { $this->_logger->err(sprintf( '[%s] Error stating %s: ignoring.', $this->_procid, $change['id'])); $stat = array('id' => $change['id'], 'mod' => $change['id'], 0); } // Update the state. $this->_as->state->updateState( Horde_ActiveSync::CHANGE_TYPE_FOLDERSYNC, $stat); break; case Horde_ActiveSync::CHANGE_TYPE_DELETE: $this->folderDeletion($change['id']); $this->_as->state->updateState( Horde_ActiveSync::CHANGE_TYPE_DELETE, $change); break; } $this->_step++; return true; } else { return false; } } /** * Send a message change over the wbxml stream * * @param string $id The uid of the message * @param Horde_ActiveSync_Message_Base $message The message object */ public function messageChange($id, Horde_ActiveSync_Message_Base $message) { // Just ignore any messages that are not from this collection and // prevent sending the same object twice in one request. if ($message->getClass() != $this->_currentCollection['class'] || in_array($id, $this->_seenObjects)) { $this->_logger->notice(sprintf( '[%s] IGNORING message %s since it looks like it was already sent or does not belong to this collection. Class: %s, CurrentClass: %s', $this->_procid, $id, $message->getClass(), $this->_currentCollection['class'])); return; } // Remember this message $this->_seenObjects[] = $id; // Specify if this is an ADD or a MODIFY change? if ($message->flags === Horde_ActiveSync::FLAG_NEWMESSAGE) { $this->_encoder->startTag(Horde_ActiveSync::SYNC_ADD); } else { $this->_encoder->startTag(Horde_ActiveSync::SYNC_MODIFY); } // Send the message $this->_encoder->startTag(Horde_ActiveSync::SYNC_SERVERENTRYID); $this->_encoder->content($id); $this->_encoder->endTag(); $this->_encoder->startTag(Horde_ActiveSync::SYNC_DATA); $message->encodeStream($this->_encoder); $this->_encoder->endTag(); $this->_encoder->endTag(); } /** * Stream a message deletion to the client. * * @param string $id The uid of the message we are deleting. * @param boolean $soft If true, send a SOFTDELETE, otherwise a REMOVE. */ public function messageDeletion($id, $soft = false) { if ($soft) { $this->_encoder->startTag(Horde_ActiveSync::SYNC_SOFTDELETE); } else { $this->_encoder->startTag(Horde_ActiveSync::SYNC_REMOVE); } $this->_encoder->startTag(Horde_ActiveSync::SYNC_SERVERENTRYID); $this->_encoder->content($id); $this->_encoder->endTag(); $this->_encoder->endTag(); } /** * Move a message to a different folder. * * @param Horde_ActiveSync_Message_Base $message The message */ function messageMove($message) { } /** * Add a folder change to the cache (used during FolderSync requests). * * @param Horde_ActiveSync_Message_Folder $folder */ public function folderChange(Horde_ActiveSync_Message_Folder $folder) { $this->changed[] = $folder; $this->count++; } /** * Add a folder deletion to the cache (used during FolderSync Requests). * * @param string $id The folder id */ public function folderDeletion($id) { $this->deleted[] = $id; $this->count++; } }Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Connector/Importer.php0000664000076500000240000004232512654565405022054 0ustar * @package ActiveSync */ /** * Horde_ActiveSync_Connector_Imports:: Receives Wbxml from device. * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2011-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync */ class Horde_ActiveSync_Connector_Importer { /** * State machine * * @var Horde_ActiveSync_State_Base */ protected $_state; /** * The server object. * * @var Horde_ActiveSync */ protected $_as; /** * Conflict resolution flags * * @var integer */ protected $_flags; /** * The backend specific folder id * * @var string */ protected $_folderId; /** * The EAS folder uid * * @var string */ protected $_folderUid; /** * Logger * * @var Horde_Log_Logger */ protected $_logger; /** * Process id for logging. * * @var integer */ protected $_procid; /** * Const'r * * @param Horde_ActiveSync $as The server object. */ public function __construct(Horde_ActiveSync $as) { $this->_as = $as; $this->_procid = getmypid(); } /** * Initialize the importer for this collection * * @param Horde_ActiveSync_State_Base $state The state machine. * @param string $folderId The collection's uid. * @param integer $flags Conflict resolution flags. */ public function init(Horde_ActiveSync_State_Base $state, $folderId = null, $flags = 0) { $this->_state = $state; $this->_flags = $flags; if (!empty($folderId)) { $this->_folderId = $this->_as->getCollectionsObject()->getBackendIdForFolderUid($folderId); $this->_folderUid = $folderId; } } /** * Setter for a logger instance * * @param Horde_Log_Logger $logger The logger */ public function setLogger($logger) { $this->_logger = $logger; } /** * Import a message change from the wbxml stream * * @param string|boolean $id A server message id or * false if a new message. * @param Horde_ActiveSync_Message_Base $message A message object * @param Horde_ActiveSync_Device $device A device descriptor * @param integer $clientid Client id sent from client. * on message addition. * @param string $class The collection class - needed for SMS since the * actual serverid will be for an email folder. * @since 2.6.0 * @param string $synckey The synckey currently being processed when * processing a SYNC_MODIFY command. * @since 2.31.0 * * @todo Revisit passing $class for SMS. Probably pass class in the * const'r. * * @return string|array|boolean The server message id, an array containing * the serverid and failure code, or false */ public function importMessageChange( $id, Horde_ActiveSync_Message_Base $message, Horde_ActiveSync_Device $device, $clientid, $class = false, $synckey = false) { // Don't support SMS, but can't tell client that. Send back a phoney // UID for any imported SMS objects. if ($class == Horde_ActiveSync::CLASS_SMS || strpos($id, 'IGNORESMS_') === 0) { return 'IGNORESMS_' . $clientid; } // Changing an existing object if ($id && $this->_flags == Horde_ActiveSync::CONFLICT_OVERWRITE_PIM) { // This is complicated by the fact that in EAS 16.0, clients // will send a CHANGE for adding/editing an exception along with // a seperate change with the entire appointment - even if nothing // else has changed. This leads to conficts (since the appointment // is marked as changed after the first edit). Sniff that out here // and prevent the conflict check for those messages. if (!($message instanceof Horde_ActiveSync_Message_Appointment) && $this->_state->isDuplicatePIMChange($id, $synckey)) { $conflict = $this->_isConflict( Horde_ActiveSync::CHANGE_TYPE_CHANGE, $this->_folderId, $id ); if ($conflict) { $this->_logger->notice(sprintf( '[%s] Conflict when updating %s, will overwrite client version on next sync.', $this->_procid, $id) ); return array($id, Horde_ActiveSync_Request_Sync::STATUS_CONFLICT); } } } elseif (!$id && $uid = $this->_state->isDuplicatePIMAddition($clientid)) { // Already saw this addition, but client never received UID $this->_logger->notice(sprintf( '[%s] Duplicate addition for %s', $this->_procid, $uid) ); return $uid; } // Set the supported/ghosted data if this is a SYNC_MODIFY. if ($id && !empty($device->supported[$class])) { $message->setSupported($device->supported[$class]); } // Tell the backend about the change if (!$stat = $this->_as->driver->changeMessage($this->_folderId, $id, $message, $device)) { $this->_logger->err(sprintf( '[%s] Change message failed when updating %s', $this->_procid, $id) ); return $id ? array($id, Horde_ActiveSync_Request_Sync::STATUS_NOTFOUND) : array(false, Horde_ActiveSync_Request_Sync::STATUS_SERVERERROR); } $stat['serverid'] = $this->_folderId; // Record the state of the message, but only if we aren't updating // categories. @todo This should be fixed, but for now we can't // differentiate between different flag changes. Note that categories // only exists for email changes so for non email this will still // work as before. if (empty($stat['categories'])) { $this->_state->updateState( ($message instanceof Horde_ActiveSync_Message_Mail ? Horde_ActiveSync::CHANGE_TYPE_FLAGS : Horde_ActiveSync::CHANGE_TYPE_CHANGE), $stat, Horde_ActiveSync::CHANGE_ORIGIN_PIM, $this->_as->driver->getUser(), $clientid); } return $stat['id']; } /** * Import message deletions. This may conflict if the local object has been * modified. * * @param array $ids Server message uids to delete * @param string $class The server collection class. * @param boolean $instanceids If true, $ids is a hash of * instanceids => uids. @since 2.31.0 * * @return array An array containing ids of successfully deleted messages. */ public function importMessageDeletion(array $ids, $class, $instanceids = false) { // Don't support SMS, but can't tell client that. if ($class == Horde_ActiveSync::CLASS_SMS) { return array(); } if ($instanceids) { foreach ($ids as $uid => $iid) { $mod = $this->_as->driver->getSyncStamp($this->_folderId); $this->_as->driver->deleteMessage($this->_folderId, array($uid => $iid), true); $change = array( 'id' => $uid, 'mod' => $mod, 'serverid' => $this->_folderId ); // Log this as a change in the state, not a deletion because // these are actually instances of recurring series being // deleted, not the entire item being deleted. $this->_state->updateState( Horde_ActiveSync::CHANGE_TYPE_CHANGE, $change, Horde_ActiveSync::CHANGE_ORIGIN_PIM, $this->_as->driver->getUser() ); } return $ids; } // Ask the backend to delete the message. $mod = $this->_as->driver->getSyncStamp($this->_folderId); $ids = $this->_as->driver->deleteMessage($this->_folderId, $ids); foreach ($ids as $id) { // Ignore SMS changes. if (strpos($id, "IGNORESMS_") === 0) { continue; } $change = array(); $change['id'] = $id; $change['mod'] = $mod; $change['serverid'] = $this->_folderId; $this->_state->updateState( Horde_ActiveSync::CHANGE_TYPE_DELETE, $change, Horde_ActiveSync::CHANGE_ORIGIN_PIM, $this->_as->driver->getUser()); } return $ids; } /** * Import a change in 'read' flags. This can never conflict. * * @param integer $id Server message id (The IMAP UID). * @param string $flag The state of the /seen flag */ public function importMessageReadFlag($id, $flag) { $change = array(); $change['id'] = $id; $change['flags'] = array('read' => $flag); $change['parent'] = $this->_folderId; $this->_state->updateState( Horde_ActiveSync::CHANGE_TYPE_FLAGS, $change, Horde_ActiveSync::CHANGE_ORIGIN_PIM, $this->_as->driver->getUser()); $this->_as->driver->setReadFlag($this->_folderId, $id, $flag); } /** * Perform a message move initiated on the clinet. * * @param array $uids The source message ids. * @param string $dst The destination folder uid. * @param string $class The collection class (only needed for SMS). * @since 2.10.0 * * @return array An array containing the following keys: * - results: An array with old uids as keys and new uids as values. * - missing: An array containing source uids that were not found on the * IMAP server. */ public function importMessageMove(array $uids, $dst, $class = null) { // Don't support SMS, but can't tell client that. Send back a phoney // UID for any imported SMS objects. if ($class == Horde_ActiveSync::CLASS_SMS) { return $uids; } // Filter out SMS if $class is not CLASS_SMS $uids = array_filter( $uids, function($e) { return strpos($e, 'IGNORESMS_') === false; } ); $collections = $this->_as->getCollectionsObject(); $dst = $collections->getBackendIdForFolderUid($dst); $results = $this->_as->driver->moveMessage($this->_folderId, $uids, $dst); // Check for any missing (not found) source messages. $missing = count($results) != count($uids) ? array_diff($uids, array_keys($results)) : array(); // Update client state. For MOVEITEMS, we are supposed to send // a DELETE and ADD command to the appropriate folders on the next // sync, but some broken clients don't like this. Save the import // in the map table in case we need it later. $mod = $this->_as->driver->getSyncStamp($this->_folderId); foreach ($uids as $uid) { if (empty($results[$uid])) { continue; } $change = array(); $change['id'] = $results[$uid]; $change['mod'] = $mod; $change['serverid'] = $dst; $change['class'] = Horde_ActiveSync::CLASS_EMAIL; $change['folderuid'] = $this->_folderUid; $this->_state->updateState( Horde_ActiveSync::CHANGE_TYPE_CHANGE, $change, Horde_ActiveSync::CHANGE_ORIGIN_PIM, $this->_as->driver->getUser()); } return array('results' => $results, 'missing' => $missing); } /** * Import a folder change from the wbxml stream * * @param string $uid The folder uid * @param string $displayname The folder display name * @param string $parent The parent folder id. * @param integer $type The EAS Folder type. @since 2.9.0 * * @return Horde_ActiveSync_Message_Folder The new folder object. */ public function importFolderChange($uid, $displayname, $parent = Horde_ActiveSync::FOLDER_ROOT, $type = null) { $this->_logger->info(sprintf( '[%s] Horde_ActiveSync_Connector_Importer::importFolderChange(%s, %s, %s, %s)', $this->_procid, $uid, $displayname, $parent, $type)); // Convert the uids to serverids. $collections = $this->_as->getCollectionsObject(); $parent_sid = !empty($parent) ? $collections->getBackendIdForFolderUid($parent) : $parent; $folderid = !empty($uid) ? $collections->getBackendIdForFolderUid($uid) : false; // Perform the creation in the backend. try { $results = $this->_as->driver->changeFolder( $folderid, $displayname, $parent_sid, $uid, $type); } catch (Horde_ActiveSync_Exception $e) { $this->_logger->err($e->getMessage()); throw $e; } // @todo Horde 6 this should always return an object. if ($results instanceof Horde_ActiveSync_Message_Folder) { $folderid = $results->_serverid; $uid = $results->serverid; } else { // @TODO Remove for 3.0 Need to build a message folder object here // for BC reasons. $serverid = $results; $results = $this->_as->messageFactory('Folder'); $results->serverid = $serverid; $results->_serverid = $folderid; } $change = array(); $change['id'] = $uid; $change['folderid'] = $folderid; $change['mod'] = $displayname; $change['parent'] = $parent; $this->_state->updateState( Horde_ActiveSync::CHANGE_TYPE_CHANGE, $change, Horde_ActiveSync::CHANGE_ORIGIN_PIM); return $results; } /** * Imports a folder deletion from the client. * * @param string $uid The folder uid * @param string $parent The folder id of the parent folder. */ public function importFolderDeletion($uid, $parent = Horde_ActiveSync::FOLDER_ROOT) { $collections = $this->_as->getCollectionsObject(); $parent_sid = !empty($parent) ? $collections->getBackendIdForFolderUid($parent) : $parent; $folderid = $collections->getBackendIdForFolderUid($uid); $change = array(); $change['id'] = $uid; $this->_as->driver->deleteFolder($folderid, $parent_sid); $this->_state->updateState( Horde_ActiveSync::CHANGE_TYPE_DELETE, $change, Horde_ActiveSync::CHANGE_ORIGIN_NA); } /** * Check if this change conflicts with server changes * This is only true in the following situations: * * Changed here and changed there * Changed here and deleted there * Deleted here and changed there * * Any other combination of operations can be done * (e.g. change flags & move or move & delete) * * @param string $type The type of change('change', 'delete' etc...) * @param string $folderid The id of the folder this change is from. * @param string $id The uid for the changed message. * * @return boolean */ protected function _isConflict($type, $folderid, $id) { $stat = $this->_as->driver->statMessage($folderid, $id); if (!$stat) { /* Message is gone, if type is change, this is a conflict */ return $type == Horde_ActiveSync::CHANGE_TYPE_CHANGE; } return $this->_state->isConflict($stat, $type); } } Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Device/Ios.php0000664000076500000240000000472312654565405020252 0ustar * @package ActiveSync */ /** * Contains constants and maps related to iOS devices. * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2015-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync */ class Horde_ActiveSync_Device_Ios { /** * Mapping of regexps to match a ios agent string. * * @var array */ static public $VERSION_MAP = array( "801.293" => "4.0", "801.306" => "4.0.1", "801.400" => "4.0.2", "802.117" => "4.1", "802.118" => "4.1", "803.148" => "4.2.1", "803.14800001" => "4.2.1", "805.128" => "4.2.5", "805.200" => "4.2.6", "805.303" => "4.2.7", "805.401" => "4.2.8", "805.501" => "4.2.9", "805.600" => "4.2.10", "806.190" => "4.3", "806.191" => "4.3", "807.4" => "4.3.1", "808.7" => "4.3.2", "808.8" => "4.3.2", "810.2" => "4.3.3", "810.3" => "4.3.3", "811.2" => "4.3.4", "812.1" => "4.3.5", "901.334" => "5.0", "901.40\d+" => "5.0.1", "902.17\d+" => "5.1", "902.206" => "5.1.1", "1001.40\d+" => "6.0", "1001.52\d+" => "6.0.1", "1002.14\d+"=> "6.1", "1002.146" => "6.1.2", "1002.329" => "6.1.3", "1002.350" => "6.1.3", "1101.465" => "7.0", "1101.470"=>"7.0.1", "1101.47000001"=>"7.0.1", "1101.501"=>"7.0.2", "1102.511" => "7.0.3", "1102.55400001" => "7.0.4", "1102.601" => "7.0.5", "1102.651" => "7.0.6", "1104.167" => "7.1", "1104.169" => "7.1", "1104.201" => "7.1.1", "1104.257" => "7.1.2", ); }Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Driver/Base.php0000664000076500000240000007414412654565405020432 0ustar * @package ActiveSync */ /** * Base ActiveSync Driver backend. Provides communication with the actual * server backend that ActiveSync will be syncing devices with. This is an * abstract class, servers must implement their own backend to provide * the needed data. * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2010-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync */ abstract class Horde_ActiveSync_Driver_Base { /** * The username to sync with the backend as * * @var string */ protected $_user; /** * Authenticating user * * @var string */ protected $_authUser; /** * User password * * @var string */ protected $_authPass; /** * Logger instance * * @var Horde_Log_Logger */ protected $_logger; /** * Parameters * * @var array */ protected $_params; /** * Protocol version * * @var float */ protected $_version = Horde_ActiveSync::VERSION_FOURTEENONE; /** * The state driver for this request. Needs to be injected into this class. * * @var Horde_ActiveSync_State_Base */ protected $_state; /** * The device object * * @var Horde_ActiveSync_Device * @since 2.12.0 */ protected $_device; /** * Temporary serverid to uid map. Used when creating the hierarchy * for the first time * * @var array */ protected $_tempMap = array(); protected $_typeMap = array( 'F' => Horde_ActiveSync::CLASS_EMAIL, 'C' => Horde_ActiveSync::CLASS_CONTACTS, 'A' => Horde_ActiveSync::CLASS_CALENDAR, 'T' => Horde_ActiveSync::CLASS_TASKS, 'N' => Horde_ActiveSync::CLASS_NOTES ); /** * Const'r * * @param array $params Any configuration parameters or injected objects * the concrete driver may need. * - logger: (Horde_Log_Logger) The logger. * DEFAULT: none (No logging). * - state: (Horde_ActiveSync_State_Base) The state driver. * DEFAULT: none (REQUIRED). * * @return Horde_ActiveSync_Driver */ public function __construct($params = array()) { $this->_params = $params; if (empty($params['state']) || !($params['state'] instanceof Horde_ActiveSync_State_Base)) { throw new InvalidArgumentException('Missing required state object'); } /* Create a stub if we don't have a useable logger. */ if (isset($params['logger']) && is_callable(array($params['logger'], 'log'))) { $this->_logger = $params['logger']; unset($params['logger']); } else { $this->_logger = new Horde_Log_Logger(new Horde_Log_Handler_Null()); } $this->_state = $params['state']; $this->_state->setLogger($this->_logger); $this->_state->setBackend($this); } /** * Prevent circular dependency issues. */ public function __destruct() { unset($this->_state); unset($this->_logger); } /** * Setter for the logger instance * * @param Horde_Log_Logger $logger The logger */ public function setLogger($logger) { $this->_logger = $logger; } /** * Set the protocol version. Can't do it in constructer since we * don't know the version at the time this driver is instantiated. * * @param float $version The EAS protocol version to use. */ public function setProtocolVersion($version) { $this->_version = $version; } /** * Obtain the ping heartbeat settings * * @return array */ public function getHeartbeatConfig() { return $this->_params['ping']; } /** * Any code needed to authenticate to backend as the actual user. * * @param string $username The username to authenticate as * @param string $password The password * @param string $domain The user domain (unused in this driver). * * @return mixed Boolean true on success, boolean false on credential * failure or Horde_ActiveSync::AUTH_REASON_* * constant on policy failure. */ public function authenticate($username, $password, $domain = null) { $this->_authUser = $username; $this->_authPass = $password; return true; } /** * Get the username for this request. * * @return string The current username */ public function getUser() { return $this->_authUser; } /** * Clear authentication * * @return boolean */ public function clearAuthentication() { return true; } /** * Setup sync parameters. The user provided here is the user the backend * will sync with. This allows you to authenticate as one user, and sync as * another, if the backend supports this. * * @param string $user The username to sync as on the backend. * * @return boolean */ public function setup($user) { $this->_user = $user; return true; } /** * Obtain the FULL message from the backend, regardless of any truncation * options. * * @param string $folderid Folder id containing data to fetch. * @param string $id Server id of data to fetch. * @param array $collection The collection data. * * @return Horde_ActiveSync_Message_Base The message data */ public function fetch($folderid, $id, array $collection) { // Forces entire message since FETCH requests are part of normal SYNC // requests and, as such, will have the default OPTIONS loaded which // should not applied to FETCH requests. $collection['truncation'] = $collection['mimetruncation'] = false; if (!empty($collection['bodyprefs'])) { foreach ($collection['bodyprefs'] as &$bodypref) { if (isset($bodypref['truncationsize'])) { $bodypref['truncationsize'] = 0; } } } return $this->getMessage($folderid, $id, $collection); } /** * Add default truncation values for this driver. * * @param array $bodyprefs BODYPREFERENCE data. * * @return array The BODYPREFERENCE data, with default truncationsize values. * @deprecated Will be removed in 3.0. We don't need and indeed shouldn't * set default values for truncationsize. Not to mention we * were setting the incorrect constant value instead of the * byte value. * @todo */ public function addDefaultBodyPrefTruncation(array $bodyprefs) { return $bodyprefs; } /** * Set the currently connected device * * @param Horde_ActiveSync_Device $device The device object. */ public function setDevice(Horde_ActiveSync_Device $device) { $this->_device = $device; } /** * Build a EAS style FB string. Essentially, each digit represents 1/2 hour. * The values are as follows: * 0 - Free * 1 - Tentative * 2 - Busy * 3 - OOF * 4 - No data available. * * Though currently we only provide a Free/Busy/Unknown differentiation. * * @param stdClass $fb The fb information. An object containing: * - s: The start of the period covered. * - e: The end of the period covered. * - b: An array of busy periods. * * @param Horde_Date $start The start of the period requested by the client. * @param Horde_Date $end The end of the period requested by the client. * * @return string The EAS freebusy string. * @since 2.4.0 */ public static function buildFbString($fb, Horde_Date $start, Horde_Date $end) { if (empty($fb)) { return false; } // Calculate total time span. $end_ts = $end->timestamp(); $start_ts = $start->timestamp(); $sec = $end_ts - $start_ts; $fb_start = new Horde_Date($fb->s); $fb_end = new Horde_Date($fb->e); // Number of 30 minute periods. $period_cnt = ceil($sec / 1800); // Requested range is completely out of the available range. if ($start_ts >= $fb_end->timestamp() || $end_ts < $fb_start->timestamp()) { return str_repeat('4', $period_cnt); } // We already know we don't have any busy periods. if (empty($fb->b) && $fb_end->timestamp() <= $end_ts) { return str_repeat('0', $period_cnt); } $eas_fb = ''; // Move $start to the start of the available data. while ($start_ts < $fb_start->timestamp() && $start_ts <= $end_ts) { $eas_fb .= '4'; $start_ts += 1800; // 30 minutes } // The rest is assumed free up to $fb->e while ($start_ts <= $fb_end->timestamp() && $start_ts <= $end_ts) { $eas_fb .= '0'; $start_ts += 1800; } // The remainder is also unavailable while ($start_ts <= $end_ts) { $eas_fb .= '4'; $start_ts += 1800; } // Now put in the busy blocks. while (list($b_start, $b_end) = each($fb->b)) { $offset = $b_start - $start->timestamp(); $duration = ceil(($b_end - $b_start) / 1800); if ($offset > 0) { $eas_fb = substr_replace($eas_fb, str_repeat('2', $duration), floor($offset / 1800), $duration); } } return $eas_fb; } /** * Get an activesync uid for the given backend serverid. If we've seen this * serverid before, return the previously created uid, otherwise return * a new one. * * @param string $id The server's current folder name E.g., INBOX * @param string $type The folder type, a Horde_ActiveSync::FOLDER_TYPE_* * constant. If empty, assumes FOLDER_TYPE_USER_MAIL * @param string $old_id The previous folder name for this folder, if the * folder is being renamed. @since 2.15.0 * @todo This is tempoarary until 3.0 (H6) when we * will have the collection manager take care of ALL * of the folder name <-> UID mapping management. * * @return string A unique identifier for the specified backend folder id. * The first character indicates the foldertype as such: * 'F' - Email * 'C' - Contact * 'A' - Appointment * 'T' - Task * 'N' - Note * @since 2.4.0 */ protected function _getFolderUidForBackendId($id, $type = null, $old_id = null) { // Always use 'RI' for Recipient cache. if ($id == 'RI') { return 'RI'; } $map = $this->_state->getFolderUidToBackendIdMap(); // Rename? if (!empty($old_id) && !empty($map[$old_id])) { $this->_tempMap[$id] = $map[$old_id]; } if (!empty($map[$id])) { return $map[$id]; } elseif (!empty($this->_tempMap[$id])) { return $this->_tempMap[$id]; } // Convert TYPE to CLASS $type = $this->_getClassFromType($type); $rMap = array_flip($this->_typeMap); $prefix = $rMap[$type]; // None found, generate a new UID. $this->_tempMap[$id] = sprintf('%s%04x%04x', $prefix, mt_rand(0, 0xffff), mt_rand(0, 0xffff)); $this->_logger->info(sprintf( '[%s] Creating new folder uuid for %s: %s', getmypid(), $id, $this->_tempMap[$id])); return $this->_tempMap[$id]; } /** * Convert a TYPE constant into it's associated CLASS constant. * * @param integer $type The TYPE. * * @return string The CLASS */ protected function _getClassFromType($type) { // @todo This is for BC. Assume we are asking for an email collection // if we didn't pass a type. Remove in H6. if (empty($type)) { return Horde_ActiveSync::CLASS_EMAIL; } switch ($type) { case Horde_ActiveSync::FOLDER_TYPE_APPOINTMENT: case Horde_ActiveSync::FOLDER_TYPE_USER_APPOINTMENT: return Horde_ActiveSync::CLASS_CALENDAR; case Horde_ActiveSync::FOLDER_TYPE_CONTACT: case Horde_ActiveSync::FOLDER_TYPE_USER_CONTACT: return Horde_ActiveSync::CLASS_CONTACTS; case Horde_ActiveSync::FOLDER_TYPE_TASK: case Horde_ActiveSync::FOLDER_TYPE_USER_TASK: return Horde_ActiveSync::CLASS_TASKS; case Horde_ActiveSync::FOLDER_TYPE_NOTE: case Horde_ActiveSync::FOLDER_TYPE_USER_NOTE: return Horde_ActiveSync::CLASS_NOTES; case Horde_ActiveSync::FOLDER_TYPE_INBOX: case Horde_ActiveSync::FOLDER_TYPE_DRAFTS: case Horde_ActiveSync::FOLDER_TYPE_WASTEBASKET: case Horde_ActiveSync::FOLDER_TYPE_SENTMAIL: case Horde_ActiveSync::FOLDER_TYPE_OUTBOX: case Horde_ActiveSync::FOLDER_TYPE_USER_MAIL: return Horde_ActiveSync::CLASS_EMAIL; } } /** * Return the SyncStamp - the value used to determine the end of the current * sync range. Default implementation uses timestamps. Concrete drivers * can override this to provide another method to do this, such as * using modification sequences. * * @param $collection string The collection id we are currently requesting. * * @return integer The SyncStamp */ public function getSyncStamp($collection) { return time(); } /** * Delete a folder on the server. * * @param string $id The server's folder id. * @param string $parent The folder's parent, if needed. */ abstract public function deleteFolder($id, $parent = Horde_ActiveSync::FOLDER_ROOT); /** * Change a folder on the server. * * @param string $id The server's folder id * @param string $displayname The new display name. * @param string $parent The folder's parent, if needed. * @param string $uid The existing folder uid, if this is an edit. * @since 2.9.0 (@todo Look at this for H6. It's * here now to save an extra DB lookup for data * we already have.) * * @return Horde_ActiveSync_Message_Folder * @throws Horde_ActiveSync_Exception */ abstract public function changeFolder($id, $displayname, $parent, $uid = null); /** * Move message * * @param string $folderid Existing folder id * @param array $ids Message UIDs * @param string $newfolderid The new folder id * * @return array The new uids for the message. */ abstract public function moveMessage($folderid, array $ids, $newfolderid); /** * Returns array of items which contain contact information * * @param string $type The search type; ['gal'|'mailbox'] * @param array $query The search query. An array containing: * - query: (string) The search term. * DEFAULT: none, REQUIRED * - range: (string) A range limiter. * DEFAULT: none (No range used). * * @return array An array containing: * - rows: An array of search results * - status: The search store status code. */ abstract public function getSearchResults($type, array $query); /** * Stat folder. Note that since the only thing that can ever change for a * folder is the name, we use that as the 'mod' value. * * @param string $id The folder id * @param mixed $parent The parent folder (or 0 if none). * @param mixed $mod Modification indicator. For folders, this is the * name of the folder, since that's the only thing * that can change. * @return a stat hash */ abstract public function statFolder($id, $parent = 0, $mod = null); /** * Return the ActiveSync message object for the specified folder. * * @param string $id The folder's server id. * * @return Horde_ActiveSync_Message_Folder object. */ abstract public function getFolder($id); /** * Get the list of folder stat arrays @see self::statFolder() * * @return array An array of folder stat arrays. */ abstract public function getFolderList(); /** * Return an array of folder objects. * * @return array An array of Horde_ActiveSync_Message_Folder objects. */ abstract public function getFolders(); /** * Get a list of server changes that occured during the specified time * period. * * @param string $folderId The server id of the collection to check. * @param integer $from_ts The starting timestamp. * @param integer $to_ts The ending timestamp. * @param integer $cutoffdate The earliest date to retrieve back to. * @param boolean $ping If true, returned changeset may * not contain the full changeset, may only * contain a single change, designed only to * indicate *some* change has taken place. The * value should not be used to determine *what* * change has taken place. * * @return array A list of messge uids that have chnaged in the specified * time period. * * @todo Horde 6 * - Change return structure to (optionally) include the actual $to_ts value * that was used. This is needed because if using something like * Kolab/IMAP storage backend in Horde, then we must synchronize * Horde <-> Kolab to get any changes from external Kolab clients and this * may cause new/changed entries that would change the current MODSEQ. * Not critical, since without this it only results in having to wait * until the next SYNC cycle. By returning the actual $to_ts value we can * update the state with THIS value instead of the one we were originally * sent. */ abstract public function getServerChanges( $folderId, $from_ts, $to_ts, $cutoffdate, $ping); /** * Get a message stat. * * @param string $folderId The folder id * @param string $id The message id (??) * * @return hash with 'id', 'mod', and 'flags' members */ abstract public function statMessage($folderId, $id); /** * Obtain an ActiveSync message from the backend. * * @param string $folderid The server's folder id this message is from * @param string $id The server's message id * @param array $collection The colletion data. May contain things like: * - mimesupport: (integer) Indicates if the device has MIME support. * DEFAULT: 0 (No MIME support) * - truncation: (integer) Non-MIME truncation limit. Anything larger * than this amount of bytes will be truncated. * DEFAULT: 0 (No truncation) * - mimetruncation: (integer) MIME truncation limit. Anything larger * than this amount of bytes will be * truncated. * DEFAULT: 0 (No truncation) * - bodyprefs: (array) The bodypref array from the device. * * @return Horde_ActiveSync_Message_Base The message data * @throws Horde_ActiveSync_Exception, Horde_Exception_NotFound */ abstract public function getMessage($folderid, $id, array $collection); /** * Delete a message * * @param string $folderid The folder id containing the messages. * @param array $ids An array of message ids to delete. */ abstract public function deleteMessage($folderid, array $ids); /** * Get the wastebasket folder. * * @param string $class The collection class. * * @return string|boolean Returns name of the trash folder, or false * if not using a trash folder. */ abstract public function getWasteBasket($class); /** * Add/Edit a message * * @param string $folderid The server id for the folder the message belongs * to. * @param string $id The server's uid for the message if this is a * change to an existing message, null if new. * @param Horde_ActiveSync_Message_Base $message * The activesync message * @param Horde_ActiveSync_Device $device The device information * * @return array|boolean A stat array if successful, otherwise false. */ abstract public function changeMessage($folderid, $id, Horde_ActiveSync_Message_Base $message, $device); /** * Set the read (\seen) flag on the specified message. * * @param string $folderid The folder id containing the message. * @param integer $uid The message IMAP UID. * @param integer $flag The value to set the flag to. * @deprecated Will be removed in 3.0, use changeMessage() instead. */ abstract public function setReadFlag($folderid, $uid, $flag); /** * Sends the email represented by the rfc822 string received by the client. * * @param mixed $rfc822 The rfc822 mime message, a string or stream * resource. * @param integer $forward The UID of the message, if forwarding. * @param integer $reply The UID of the message if replying. * @param string $parent The collection id of parent message if * forwarding/replying. * @param boolean $save Save in sent messages. * * @return boolean */ abstract public function sendMail( $rfc822, $forward = null, $reply = null, $parent = null, $save = true); /** * Return the specified attachment. * * @param string $name The attachment identifier. For this driver, this * consists of 'mailbox:uid:mimepart' * * @param array $options Any options requested. Currently supported: * - stream: (boolean) Return a stream resource for the mime contents. * * @return array The attachment in the form of an array with the following * structure: * array('content-type' => {the content-type of the attachement}, * 'data' => {the raw attachment data}) */ abstract public function getAttachment($name, array $options = array()); /** * Return the specified attachement data for an ITEMOPERATIONS request. * * @param string $filereference The attachment identifier. * * @return */ abstract public function itemOperationsGetAttachmentData($filereference); /** * Returnmail object represented by the specified longid. Used to fetch * email objects from a search result, which only returns a 'longid'. * * @param string $longid The unique search result identifier. * @param array $bodyprefs The bodypreference array. * @param boolean $mimesupport Mimesupport flag. * * @return Horde_ActiveSync_Message_Base The message requested. */ abstract public function itemOperationsFetchMailbox($longid, array $bodyprefs, $mimesupport); /** * Return a documentlibrary item. * * @param string $linkid The linkid * @param array $cred A credential array: * - username: A hash with 'username' and 'domain' key/values. * - password: User password * * @return array An array containing the data and metadata: */ abstract public function itemOperationsGetDocumentLibraryLink($linkid, $cred); /** * Build a stat structure for an email message. * * @param string $folderid The mailbox name. * @param integer|array $id The message(s) to stat (IMAP UIDs). * * @return array */ abstract public function statMailMessage($folderid, $id); /** * Return the server id of the specified special folder type. * * @param string $type The self::SPECIAL_* constant. * * @return string The folder's server id. */ abstract public function getSpecialFolderNameByType($type); /** * Return the security policies. * * @param boolean|array $device The device information sent by EAS 14.1 * set to false otherwise. @since 3.0 * @return array An array of provisionable properties and values. */ abstract public function getCurrentPolicy(); /** * Return settings from the backend for a SETTINGS request. * * @param array $settings An array of settings to return. * @param Horde_ActiveSync_Device $device The device to obtain settings for. * * @return array The requested settings. */ abstract public function getSettings(array $settings, $device); /** * Set backend settings from a SETTINGS request. * * @param array $settings The settings to store. * @param Horde_ActiveSync_Device $device The device to store settings for. * * @return array An array of status responses for each set request. e.g.,: * array('oof' => Horde_ActiveSync_Request_Settings::STATUS_SUCCESS, * 'deviceinformation' => Horde_ActiveSync_Request_Settings::STATUS_SUCCESS); */ abstract public function setSettings(array $settings, $device); /** * Return properties for an AUTODISCOVER request. * * @return array An array of properties. */ abstract public function autoDiscover(); /** * Attempt to guess a username based on the email address passed from * EAS Autodiscover requests. * * @param string $email The email address * * @return string The username to use to authenticate to Horde with. */ abstract public function getUsernameFromEmail($email); /** * Handle ResolveRecipient requests * * @param string $type The type of recipient request. e.g., 'certificate' * @param string $search The email to resolve. * @param array $opts Any options required to perform the resolution. * - maxcerts: (integer) The maximum number of certificates to return * as provided by the client. * - maxambiguous: (integer) The maximum number of ambiguous results. If * set to zero, we MUST have an exact match. * - starttime: (Horde_Date) The start time for the availability window if * requesting AVAILABILITY. * - endtime: (Horde_Date) The end of the availability window if * requesting AVAILABILITY. * - maxsize: (integer) The maximum size of any pictures. * DEFAULT: 0 (No limit). * - maxpictures: (integer) The maximum count of images to return. * DEFAULT: - (No limit). * - pictures: (boolean) Return pictures. * * @return array An array of results containing any of the following: * - type: (string) The type of result a GAL entry or personal * address book entry. A * Horde_ActiveSync::RESOLVE_RESULT constant. * - displayname: (string) The display name of the contact. * - emailaddress: (string) The emailaddress. * - entries: (array) An array of certificates. * - availability: (string) A EAS style FB string. * - picture: (Horde_ActiveSync_Message_ResolveRecipientsPicture) */ abstract public function resolveRecipient($type, $search, array $options = array()); /** * Returns the provisioning support for the current request. * * @return mixed The value of the provisiong support flag. */ abstract public function getProvisioning(); /** * Hanlde meeting responses. * * @param array $response The response data. Contains: * - requestid: The identifier of the meeting request. Used by the server * to fetch the original meeting request details. * - response: The user's response to the request. One of the response * code constants. * - folderid: The collection id that contains the meeting request. * * * @return string The UID of any created calendar entries, otherwise false. * @throws Horde_ActiveSync_Exception, Horde_Exception_NotFound */ abstract public function meetingResponse(array $response); /** * Request freebusy information from the server * * @param string $user The user to request FB information for. * @param array $options Options. * * @return mixed boolean|array The FB information, if available. Otherwise * false. * @deprecated Will be removed in 3.0 - this is provided by resolveRecipients */ abstract public function getFreebusy($user, array $options = array()); } Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Driver/Mock.php0000664000076500000240000006707412654565405020455 0ustar * @package ActiveSync */ /** * Base ActiveSync Driver backend. Provides communication with the actual * server backend that ActiveSync will be syncing devices with. This is an * class, servers must implement their own backend to provide * the needed data. * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2010-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync */ class Horde_ActiveSync_Driver_Mock extends Horde_ActiveSync_Driver_Base { /** * Server folder ids for non-email folders. * We use the @ modifiers to avoid issues in the (fringe) case of * having email folders named like contacts etc... */ const APPOINTMENTS_FOLDER_UID = '@Calendar@'; const CONTACTS_FOLDER_UID = '@Contacts@'; const TASKS_FOLDER_UID = '@Tasks@'; const NOTES_FOLDER_UID = '@Notes@'; const SPECIAL_SENT = 'sent'; const SPECIAL_SPAM = 'spam'; const SPECIAL_TRASH = 'trash'; const SPECIAL_DRAFTS = 'drafts'; const SPECIAL_INBOX = 'inbox'; protected $_auth; protected $_connector; protected $_imap; protected $_displayMap = array( self::APPOINTMENTS_FOLDER_UID => 'Calendar', self::CONTACTS_FOLDER_UID => 'Contacts', self::TASKS_FOLDER_UID => 'Tasks', self::NOTES_FOLDER_UID => 'Notes', ); public function __construct($params = array()) { parent::__construct($params); $this->_connector = $params['connector']; //$this->_auth = $params['auth']; $this->_imap = $params['imap']; } /** * Delete a folder on the server. * * @param string $id The server's folder id. * @param string $parent The folder's parent, if needed. */ public function deleteFolder($id, $parent = Horde_ActiveSync::FOLDER_ROOT) { } /** * Change a folder on the server. * * @param string $id The server's folder id * @param string $displayname The new display name. * @param string $parent The folder's parent, if needed. * @param string $uid The existing folder uid, if this is an edit. * @since 2.5.0 (@todo Look at this for H6. It's * here now to save an extra DB lookup for data * we already have.) */ public function changeFolder($id, $displayname, $parent, $uid = null) { return $uid; } /** * Move message * * @param string $folderid Existing folder id * @param array $ids Message UIDs * @param string $newfolderid The new folder id * * @return array The new uids for the message. */ public function moveMessage($folderid, array $ids, $newfolderid) { return $ids; } /** * Returns array of items which contain contact information * * @param string $type The search type; ['gal'|'mailbox'] * @param array $query The search query. An array containing: * - query: (string) The search term. * DEFAULT: none, REQUIRED * - range: (string) A range limiter. * DEFAULT: none (No range used). * * @return array An array containing: * - rows: An array of search results * - status: The search store status code. */ public function getSearchResults($type, array $query) { return array(); } /** * Stat folder. Note that since the only thing that can ever change for a * folder is the name, we use that as the 'mod' value. * * @param string $id The folder id * @param mixed $parent The parent folder (or 0 if none). * @param mixed $mod Modification indicator. For folders, this is the * name of the folder, since that's the only thing * that can change. * @return a stat hash */ public function statFolder($id, $parent = 0, $mod = null) { $folder = array(); $folder['id'] = $id; $folder['mod'] = empty($mod) ? $id : $mod; $folder['parent'] = $parent; $folder['serverid'] = !empty($serverid) ? $serverid : $id; return $folder; } /** * Return the ActiveSync message object for the specified folder. * * @param string $id The folder's server id. * * @return Horde_ActiveSync_Message_Folder object. */ public function getFolder($id) { switch ($id) { case self::APPOINTMENTS_FOLDER_UID: $folder = $this->_buildNonMailFolder( $id, 0, Horde_ActiveSync::FOLDER_TYPE_APPOINTMENT, $this->_displayMap[self::APPOINTMENTS_FOLDER_UID]); break; case self::CONTACTS_FOLDER_UID: $folder = $this->_buildNonMailFolder( $id, 0, Horde_ActiveSync::FOLDER_TYPE_CONTACT, $this->_displayMap[self::CONTACTS_FOLDER_UID]); break; case self::TASKS_FOLDER_UID: $folder = $this->_buildNonMailFolder( $id, 0, Horde_ActiveSync::FOLDER_TYPE_TASK, $this->_displayMap[self::TASKS_FOLDER_UID]); break; case self::NOTES_FOLDER_UID: $folder = $this->_buildNonMailFolder( $id, 0, Horde_ActiveSync::FOLDER_TYPE_NOTE, $this->_displayMap[self::NOTES_FOLDER_UID]); break; default: // Must be a mail folder $folders = $this->_getMailFolders(); foreach ($folders as $folder) { if ($folder->_serverid == $id) { return $folder; } } throw new Horde_ActiveSync_Exception('Folder ' . $id . ' unknown'); } return $folder; } /** * Return the list of mail server folders. * * @return array An array of Horde_ActiveSync_Message_Folder objects. */ protected function _getMailFolders() { if (empty($this->_imap)) { $this->_mailFolders = array($this->_buildDummyFolder(self::SPECIAL_INBOX)); $this->_mailFolders[] = $this->_buildDummyFolder(self::SPECIAL_TRASH); $this->_mailFolders[] = $this->_buildDummyFolder(self::SPECIAL_SENT); } else { $folders = array(); $imap_folders = $this->_imap->getMailboxes(); // Build the folder tree, making sure the lower levels are // added first. $level = 0; $cnt = 0; while ($cnt < count($imap_folders)) { foreach ($imap_folders as $id => $folder) { if ($folder['level'] == $level) { try { $folders[] = $this->_getMailFolder($id, $imap_folders, $folder); ++$cnt; } catch (Horde_ActiveSync_Exception $e) { $this->_logger->err(sprintf( "[%s] Problem retrieving %s mail folder", $this->_pid, $id) ); } } } ++$level; } $this->_mailFolders = $folders; } } protected function _getFolderUidForBackendId($sid, $type = null, $old_id = null) { switch ($sid) { case 'INBOX': return '519422f1-4c5c-4547-946a-1701c0a8015f'; default: return $sid; } } protected function _getMailFolder($sid, array $fl, array $f) { $folder = new Horde_ActiveSync_Message_Folder(); $folder->_serverid = $sid; $folder->serverid = $this->_getFolderUidForBackendId($sid); $folder->parentid = '0'; $folder->displayname = $f['label']; // Check for nested folders. $fl will NEVER contain containers so we // can assume that any entry in $fl is an actual mailbox. EAS does // not support containers so we only do this if the parent is an // actual mailbox. if ($f['level'] != 0) { $parts = explode($f['d'], $sid); $displayname = array_pop($parts); if (!empty($fl[implode($f['d'], $parts)])) { $folder->parentid = $this->_getFolderUidForBackendId(implode($f['d'], $parts)); $folder->_parentid = implode($f['d'], $parts); $folder->displayname = $displayname; } } if (strcasecmp($sid, 'INBOX') === 0) { $folder->type = Horde_ActiveSync::FOLDER_TYPE_INBOX; return $folder; } try { $specialFolders = $this->_imap->getSpecialMailboxes(); } catch (Horde_ActiveSync_Exception $e) { $this->_logger->err(sprintf( "[%s] Problem retrieving special folders: %s", $this->_pid, $e->getMessage())); throw $e; } // Check for known, supported special folders. foreach ($specialFolders as $key => $value) { if (!is_array($value)) { $value = array($value); } foreach ($value as $mailbox) { if (!is_null($mailbox)) { switch ($key) { case self::SPECIAL_SENT: if ($sid == $mailbox->value) { $folder->type = Horde_ActiveSync::FOLDER_TYPE_SENTMAIL; return $folder; } break; case self::SPECIAL_TRASH: if ($sid == $mailbox->value) { $folder->type = Horde_ActiveSync::FOLDER_TYPE_WASTEBASKET; return $folder; } break; case self::SPECIAL_DRAFTS: if ($sid == $mailbox->value) { $folder->type = Horde_ActiveSync::FOLDER_TYPE_DRAFTS; return $folder; } break; } } } } // Not a known folder, set it to user mail. $folder->type = Horde_ActiveSync::FOLDER_TYPE_USER_MAIL; return $folder; } /** * Helper to build a folder object for non-email folders. * * @param string $id The folder's server id. * @param stirng $parent The folder's parent id. * @param integer $type The folder type. * @param string $name The folder description. * * @return Horde_ActiveSync_Message_Folder The folder object. */ protected function _buildNonMailFolder($id, $parent, $type, $name) { $folder = new Horde_ActiveSync_Message_Folder(); $folder->serverid = $id; $folder->parentid = $parent; $folder->type = $type; $folder->displayname = $name; return $folder; } /** * Get the list of folder stat arrays @see self::statFolder() * * @return array An array of folder stat arrays. */ public function getFolderList() { $folderlist = $this->getFolders(); $folders = array(); foreach ($folderlist as $f) { $folders[] = $this->statFolder($f->serverid, $f->parentid, $f->displayname, $f->_serverid); } return $folders; } /** * Return an array of the server's folder objects. * * @return array An array of Horde_ActiveSync_Message_Folder objects. */ public function getFolders() { if (empty($this->_folders)) { try { $supported = $this->_connector->listApis(); } catch (Exception $e) { return array(); } $folders = array(); if (array_search('calendar', $supported) !== false) { $folders[] = $this->getFolder(self::APPOINTMENTS_FOLDER_UID); } if (array_search('contacts', $supported) !== false) { $folders[] = $this->getFolder(self::CONTACTS_FOLDER_UID); } if (array_search('tasks', $supported) !== false) { $folders[] = $this->getFolder(self::TASKS_FOLDER_UID); } if (array_search('notes', $supported) !== false) { $folders[] = $this->getFolder(self::NOTES_FOLDER_UID); } if (array_search('mail', $supported) !== false) { try { $folders = array_merge($folders, $this->_getMailFolders()); } catch (Horde_ActiveSync_Exception $e) { return array(); } } $this->_folders = $folders; } return $this->_folders; } /** * Get a list of server changes that occured during the specified time * period. * * @param string $folderId The server id of the collection to check. * @param integer $from_ts The starting timestamp. * @param integer $to_ts The ending timestamp. * @param integer $cutoffdate The earliest date to retrieve back to. * @param boolean $ping If true, returned changeset may * not contain the full changeset, may only * contain a single change, designed only to * indicate *some* change has taken place. The * value should not be used to determine *what* * change has taken place. * * @return array A list of messge uids that have chnaged in the specified * time period. */ public function getServerChanges($folderId, $from_ts, $to_ts, $cutoffdate, $ping) { $changes = array( 'add' => array(), 'delete' => array(), 'modify' => array() ); if ($from_ts == 0 && !$ignoreFirstSync) { $startstamp = (int)$cutoffdate; $endstamp = time() + 32140800; //60 * 60 * 24 * 31 * 12 == one year $changes['add'] = $this->_connector->listUids($startstamp, $endstamp); } else { $changes = $this->_connector->getChanges($folderId, $from_ts, $to_ts); } $results = array(); foreach ($changes['add'] as $add) { $results[] = array( 'id' => $add, 'type' => Horde_ActiveSync::CHANGE_TYPE_CHANGE, 'flags' => Horde_ActiveSync::FLAG_NEWMESSAGE); } // For CLASS_EMAIL, all changes are a change in flags. if ($folder->collectionClass() == Horde_ActiveSync::CLASS_EMAIL) { $flags = $folder->flags(); foreach ($changes['modify'] as $uid) { $results[] = array( 'id' => $uid, 'type' => Horde_ActiveSync::CHANGE_TYPE_FLAGS, 'flags' => $flags[$uid] ); } } else { foreach ($changes['modify'] as $change) { $results[] = array( 'id' => $change, 'type' => Horde_ActiveSync::CHANGE_TYPE_CHANGE ); } } // Server Deletions foreach ($changes['delete'] as $deleted) { $results[] = array( 'id' => $deleted, 'type' => Horde_ActiveSync::CHANGE_TYPE_DELETE); } return $results; } /** * Get a message stat. * * @param string $folderId The folder id * @param string $id The message id (??) * * @return hash with 'id', 'mod', and 'flags' members */ public function statMessage($folderId, $id) { $mod = $this->_connector->getActionTimestamp($id, 'modify'); $message = array(); $message['id'] = $id; $message['mod'] = $mod; $message['flags'] = 1; return $message; } /** * Obtain an ActiveSync message from the backend. * * @param string $folderid The server's folder id this message is from * @param string $id The server's message id * @param array $collection The colletion data. May contain things like: * - mimesupport: (boolean) Indicates if the device has MIME support. * DEFAULT: false (No MIME support) * - truncation: (integer) The truncation constant, if sent by the device. * DEFAULT: 0 (No truncation) * - bodyprefs: (array) The bodypref array from the device. * * @return Horde_ActiveSync_Message_Base The message data * @throws Horde_ActiveSync_Exception */ public function getMessage($folderid, $id, array $collection) { return $this->_connector->export($id, array()); } /** * Delete a message * * @param string $folderid The folder id containing the messages. * @param array $ids An array of message ids to delete. */ public function deleteMessage($folderid, array $ids) { return $ids; } /** * Get the wastebasket folder. * * @param string $class The collection class. * * @return string|boolean Returns name of the trash folder, or false * if not using a trash folder. */ public function getWasteBasket($class) { return false; } /** * Add/Edit a message * * @param string $folderid The server id for the folder the message belongs * to. * @param string $id The server's uid for the message if this is a * change to an existing message, null if new. * @param Horde_ActiveSync_Message_Base $message * The activesync message * @param Horde_ActiveSync_Device $device The device information * * @return array|boolean A stat array if successful, otherwise false. */ public function changeMessage($folderid, $id, Horde_ActiveSync_Message_Base $message, $device) { } /** * Set the read (\seen) flag on the specified message. * * @param string $folderid The folder id containing the message. * @param integer $uid The message IMAP UID. * @param integer $flag The value to set the flag to. * @deprecated Will be removed in 3.0, use changeMessage() instead. */ public function setReadFlag($folderid, $uid, $flag) { } /** * Sends the email represented by the rfc822 string received by the client. * * @param mixed $rfc822 The rfc822 mime message, a string or stream * resource. * @param integer $forward The UID of the message, if forwarding. * @param integer $reply The UID of the message if replying. * @param string $parent The collection id of parent message if * forwarding/replying. * @param boolean $save Save in sent messages. * * @return boolean */ public function sendMail( $rfc822, $forward = null, $reply = null, $parent = null, $save = true) { return true; } /** * Return the specified attachment. * * @param string $name The attachment identifier. For this driver, this * consists of 'mailbox:uid:mimepart' * * @param array $options Any options requested. Currently supported: * - stream: (boolean) Return a stream resource for the mime contents. * * @return array The attachment in the form of an array with the following * structure: * array('content-type' => {the content-type of the attachement}, * 'data' => {the raw attachment data}) */ public function getAttachment($name, array $options = array()) { } /** * Return the specified attachement data for an ITEMOPERATIONS request. * * @param string $filereference The attachment identifier. * * @return */ public function itemOperationsGetAttachmentData($filereference) { } /** * Returnmail object represented by the specified longid. Used to fetch * email objects from a search result, which only returns a 'longid'. * * @param string $longid The unique search result identifier. * @param array $bodyprefs The bodypreference array. * @param boolean $mimesupport Mimesupport flag. * * @return Horde_ActiveSync_Message_Base The message requested. */ public function itemOperationsFetchMailbox($longid, array $bodyprefs, $mimesupport) { } /** * Return a documentlibrary item. * * @param string $linkid The linkid * @param array $cred A credential array: * - username: A hash with 'username' and 'domain' key/values. * - password: User password * * @return array An array containing the data and metadata: */ public function itemOperationsGetDocumentLibraryLink($linkid, $cred) { } /** * Build a stat structure for an email message. * * @param string $folderid The mailbox name. * @param integer|array $id The message(s) to stat (IMAP UIDs). * * @return array */ public function statMailMessage($folderid, $id) { return array( 'id' => $id, 'mod' => 0, 'flags' => false); } /** * Return the server id of the specified special folder type. * * @param string $type The self::SPECIAL_* constant. * * @return string The folder's server id. */ public function getSpecialFolderNameByType($type) { $folders = $this->_imap->getSpecialMailboxes(); $folder = $folders[$type]; if (!is_null($folder)) { return $folder->value; } } /** * Return the security policies. * * @param boolean|array $device The device information sent by EAS 14.1 * set to false otherwise. @since 3.0 * @return array An array of provisionable properties and values. */ public function getCurrentPolicy() { } /** * Return settings from the backend for a SETTINGS request. * * @param array $settings An array of settings to return. * @param Horde_ActiveSync_Device $device The device to obtain settings for. * * @return array The requested settings. */ public function getSettings(array $settings, $device) { } /** * Set backend settings from a SETTINGS request. * * @param array $settings The settings to store. * @param Horde_ActiveSync_Device $device The device to store settings for. * * @return array An array of status responses for each set request. e.g.,: * array('oof' => Horde_ActiveSync_Request_Settings::STATUS_SUCCESS, * 'deviceinformation' => Horde_ActiveSync_Request_Settings::STATUS_SUCCESS) { } */ public function setSettings(array $settings, $device) { } /** * Return properties for an AUTODISCOVER request. * * @return array An array of properties. */ public function autoDiscover() { } /** * Attempt to guess a username based on the email address passed from * EAS Autodiscover requests. * * @param string $email The email address * * @return string The username to use to authenticate to Horde with. */ public function getUsernameFromEmail($email) { } /** * Handle ResolveRecipient requests * * @param string $type The type of recipient request. e.g., 'certificate' * @param string $search The email to resolve. * @param array $opts Any options required to perform the resolution. * - maxcerts: (integer) The maximum number of certificates to return * as provided by the client. * - maxambiguous: (integer) The maximum number of ambiguous results. If * set to zero, we MUST have an exact match. * - starttime: (Horde_Date) The start time for the availability window if * requesting AVAILABILITY. * - endtime: (Horde_Date) The end of the availability window if * requesting AVAILABILITY. * - maxsize: (integer) The maximum size of any pictures. * DEFAULT: 0 (No limit). * - maxpictures: (integer) The maximum count of images to return. * DEFAULT: - (No limit). * - pictures: (boolean) Return pictures. * * @return array An array of results containing any of the following: * - type: (string) The type of result a GAL entry or personal * address book entry. A * Horde_ActiveSync::RESOLVE_RESULT constant. * - displayname: (string) The display name of the contact. * - emailaddress: (string) The emailaddress. * - entries: (array) An array of certificates. * - availability: (string) A EAS style FB string. * - picture: (Horde_ActiveSync_Message_ResolveRecipientsPicture) */ public function resolveRecipient($type, $search, array $options = array()) { } /** * Returns the provisioning support for the current request. * * @return mixed The value of the provisiong support flag. */ public function getProvisioning() { } /** * Hanlde meeting responses. * * @param array $response The response data. Contains: * - requestid: The identifier of the meeting request. Used by the server * to fetch the original meeting request details. * - response: The user's response to the request. One of the response * code constants. * - folderid: The collection id that contains the meeting request. * * * @return string The UID of any created calendar entries, otherwise false. * @throws Horde_ActiveSync_Exception, Horde_Exception_NotFound */ public function meetingResponse(array $response) { } /** * Request freebusy information from the server * * @param string $user The user to request FB information for. * @param array $options Options. * * @return mixed boolean|array The FB information, if available. Otherwise * false. */ public function getFreebusy($user, array $options = array()) { } public function getHeartbeatConfig() { return array( 'heartbeatmin' => 60, 'heartbeatmax' => 2700, 'heartbeatdefault' => 480, 'deviceping' => true, 'waitinterval' => 10); } } Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Driver/MockConnector.php0000664000076500000240000000413312654565405022313 0ustar * @package ActiveSync */ /** * Mock connector for testing using the Horde_ActiveSync_Driver_Mock driver. * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2010-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync */ class Horde_ActiveSync_Driver_MockConnector { /** * By default, support the main groupware apps, minus mail. * Mock this method to override. */ public function listApis() { return array('calendar', 'contacts', 'tasks', 'notes'); } /** * By default, return 2 UIDs as shown below. Mock this method to override. */ public function listUids() { return array('UID_001', 'UID_002'); } /** * By default, return no changes. Mock this method to override. */ public function getChanges($folderid, $from_ts, $to_ts) { return array( 'add' => array(), 'modify' => array(), 'delete' => array()); } /** * Always returns a MODSEQ of 2. Mock to override. */ public function getActionTimestamp($id, $action) { return 2; } /** * MUST mock this method if needed so we can return the expected object. * * @return Horde_ActiveSync_Message_Base */ public function export($id, $options) { } }Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Exception/EmailFatalFailure.php0000664000076500000240000000213212654565405023556 0ustar * @package ActiveSync * @since 2.19.0 */ /** * Exception * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2010-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync * @since 2.19.0 */ class Horde_ActiveSync_Exception_EmailFatalFailure extends Horde_ActiveSync_Exception { }Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Exception/FolderExists.php0000664000076500000240000000205612654565405022667 0ustar * @package ActiveSync */ /** * Exception * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2010-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync */ class Horde_ActiveSync_Exception_FolderExists extends Horde_ActiveSync_Exception { }Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Exception/FolderGone.php0000664000076500000240000000205212654565405022274 0ustar * @package ActiveSync */ /** * Exception * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2010-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync */ class Horde_ActiveSync_Exception_FolderGone extends Horde_ActiveSync_Exception { }Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Exception/InvalidRequest.php0000664000076500000240000000206212654565405023210 0ustar * @package ActiveSync */ /** * Exception * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2010-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync */ class Horde_ActiveSync_Exception_InvalidRequest extends Horde_ActiveSync_Exception { }Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Exception/StaleState.php0000664000076500000240000000205212654565405022321 0ustar * @package ActiveSync */ /** * Exception * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2010-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync */ class Horde_ActiveSync_Exception_StaleState extends Horde_ActiveSync_Exception { }Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Exception/StateGone.php0000664000076500000240000000205012654565405022137 0ustar * @package ActiveSync */ /** * Exception * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2010-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync */ class Horde_ActiveSync_Exception_StateGone extends Horde_ActiveSync_Exception { }Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Folder/Base.php0000664000076500000240000000770612654565405020412 0ustar * @package ActiveSync */ /** * The class contains functionality for maintaining state for a generic * collection folder. This would include Appointments, Contacts, and Tasks. * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2012-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync */ abstract class Horde_ActiveSync_Folder_Base { /** * The folder's current internal property state. * * @var array */ protected $_status = array(); /** * The backend server id for this folder. * * @var string */ protected $_serverid; /** * The collection class. * * @var string */ protected $_class; /** * Flag for indicating we have an initial sync for this collection. * * @var boolean */ public $haveInitialSync = false; /** * Timestamp for the last sincedate used for SOFTDELETE. * * @var integer */ protected $_lastSinceDate = 0; /** * Timestamp for the last time we performed a SOFTDELETE * * @var integer */ protected $_softDelete = 0; /** * Const'r * * @param string $serverid The backend serverid of this folder. * @param string $class The collection class. * @param array $status Internal folder state. */ public function __construct( $serverid, $class, array $status = array()) { $this->_serverid = $serverid; $this->_status = $status; $this->_class = $class; } /** * Return the serverid for this collection. * * @return string The serverid. */ public function serverid() { return $this->_serverid; } /** * Set a new value for the serverid. * * @param string $id The new id. * @since 2.4.0 * @todo For H6 make these all __get/__set calls. */ public function setServerId($id) { $this->_serverid = $id; } /** * Return the collection class for this collection. * * @return string The collection class. */ public function collectionClass() { return $this->_class; } /** * Set the status for this collection. * * @param array A status array. */ public function setStatus(array $status) { $this->_status = $status; } /** * Set the last softdelete timestamps used. * * @param long $sincedate The sincedate used in the last softdelete check. * @param long $ts Time the softdelete check was performed. */ public function setSoftDeleteTimes($sincedate, $ts) { $this->_lastSinceDate = $sincedate; $this->_softDelete = $ts; } /** * Return the softdelete timestamps. * * @return array An array with the last sincedate in the 0 element and * the last timestamp in the 1 element. */ public function getSoftDeleteTimes() { return array($this->_lastSinceDate, $this->_softDelete); } /** * Updates the internal UID cache, and clears the internal * update/deleted/changed cache. */ abstract public function updateState(); } Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Folder/Collection.php0000664000076500000240000000562212654565405021626 0ustar * @package ActiveSync */ /** * The class contains functionality for maintaining state for a generic * collection folder. This would include Appointments, Contacts, and Tasks. * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2012-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync */ class Horde_ActiveSync_Folder_Collection extends Horde_ActiveSync_Folder_Base implements Serializable { const VERSION = 1; /** * Updates the internal UID cache, and clears the internal * update/deleted/changed cache. */ public function updateState() { $this->haveInitialSync = true; } /** * Convert the instance into a string. * * @return string The string representation for this instance. */ public function __toString() { return sprintf( 'serverid: %s\nclass: %s\n', $this->serverid(), $this->collectionClass()); } /** * Serialize this object. * * @return string The serialized data. */ public function serialize() { return json_encode(array( 's' => $this->_status, 'f' => $this->_serverid, 'c' => $this->_class, 'lsd' => $this->_lastSinceDate, 'sd' => $this->_softDelete, 'i' => $this->haveInitialSync, 'v' => self::VERSION) ); } /** * Reconstruct the object from serialized data. * * @param string $data The serialized data. * @throws Horde_ActiveSync_Exception_StaleState */ public function unserialize($data) { $data = @json_decode($data, true); if (!is_array($data) || empty($data['v']) || $data['v'] != self::VERSION) { throw new Horde_ActiveSync_Exception_StaleState('Cache version change'); } $this->_status = $data['s']; $this->_serverid = $data['f']; $this->_class = $data['c']; $this->haveInitialSync = $data['i']; $this->_lastSinceDate = empty($data['lsd']) ? 0 : $data['lsd']; $this->_softDelete = empty($data['sd']) ? 0 : $data['sd']; } } Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Folder/Imap.php0000664000076500000240000004330112654565405020415 0ustar * @package ActiveSync */ /** * The class contains functionality for maintaining state for a single IMAP * folder, and generating server deltas. * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2012-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync */ class Horde_ActiveSync_Folder_Imap extends Horde_ActiveSync_Folder_Base implements Serializable { /* Key names for various IMAP server status values */ const UIDVALIDITY = 'uidvalidity'; const UIDNEXT = 'uidnext'; const HIGHESTMODSEQ = 'highestmodseq'; const MESSAGES = 'messages'; /* Serialize version */ const VERSION = 2; /* The UID count at which UID lists will be compressed before serialization */ const COMPRESSION_LIMIT = 500; /** * The folder's current message list. * Note: This represents the folder list on the client and is affected by * the FILTERTYPE on the collection. * * @var array */ protected $_messages = array(); /** * Internal cache of message UIDs that have been added since last sync. * Used for transporting changes back to activesync. * * @var array */ protected $_added = array(); /** * Internal cache of message UIDs that have been modified on the server * since the last sync. Used for transporting changes back to activesync. * * @var array */ protected $_changed = array(); /** * Internal cache of message UIDs that have been expunged from the IMAP * server since last sync. Used for transporting changes back to activesync. * * @var array */ protected $_removed = array(); /** * Array of messages to be SOFTDELETEd from client. Only used when we have * a CONDSTORE server available, otherwise we calculate the uid list based * on the cached data. * * @var array */ protected $_softDeleted = array(); /** * Internal cache of message flag changes. Should be one entry for each UID * also listed in the $_changed array. Used for transporting changes back to * activesync. An array keyed by message UID: * uid => array('read' => 1) * * @var array */ protected $_flags = array(); /** * Internal cache of custom message flags (i.e., categories). Should contain * one entry for each UID listed in the $_changed array. An array keyed on * message UID: * uid => array('TestOne', 'TestTwo') * * @var array */ protected $_categories = array(); /** * Internal flag to indicate initial first sync/prime. * * @var boolean */ protected $_primed = false; /** * Set message changes. * * @param array $messages An array of message UIDs. * @param array $flags A hash of message read flags, keyed by UID. * @param array $categories A hash of custom message flags, keyed by UID. * @since 2.17.0 * @param boolean $resetMinUid If true, reset the minimum UID. Should be * used when FilterType has widened and * messages older than originally selected are * being returned. @since 2.24.0 */ public function setChanges( array $messages, array $flags = array(), array $categories = array(), $resetMinUid = false) { $uidnext = $this->uidnext(); $minuid = $this->minuid(); $modseq = $this->modseq(); foreach ($messages as $uid) { if ($uid >= $uidnext) { // new (to us) message. $this->_added[] = $uid; } elseif ($uid >= $minuid || $resetMinUid) { if ($modseq > 0) { // MODSEQ capable server, either a changed message, or // one that was previously outside of FilterType but is now // within. if (array_search($uid, $this->_messages) !== false) { $this->_changed[] = $uid; } else { $this->_added[] = $uid; } } else { if (empty($this->_messages[$uid])) { // Do not know about this message continue; } if ((isset($flags[$uid]['read']) && $flags[$uid]['read'] != $this->_messages[$uid]['read']) || (isset($flags[$uid]['flagged']) && $flags[$uid]['flagged'] != $this->_messages[$uid]['flagged'])) { $this->_changed[] = $uid; } } } } foreach ($flags as $uid => $data) { if (!empty($this->_flags[$uid])) { $this->_flags[$uid] += $data; } else { $this->_flags[$uid] = $data; } } foreach ($categories as $uid => $data) { if (!empty($this->_categories[$uid])) { $this->_categories[$uid] += $data; } else { $this->_categories[$uid] = $data; } } } /** * Sets initial message collection for servers that support MODSEQ. Much * more effecient than using setChanges() when we know this is the initial * folder "priming". * * @param array $messages Array of message UIDs for the initial message * set for this folder. */ public function primeFolder($messages) { $this->_added = $messages; $this->_primed = true; } /** * Return a list of message uids that should be SOFTDELETEd from the client. * Must be called after setChanges and setRemoved, but before updateState. * * @return array */ public function getSoftDeleted() { if (empty($this->_status[self::HIGHESTMODSEQ])) { // non-CONDSTORE server, we actually already have this data without // having to search the server again. If we don't have an entry in // $this->_flags, the message was not returned in the latest query, // so it is either deleted or outside the range. $good_uids = array_keys($this->_flags); $messages = array_diff(array_keys($this->_messages), $this->_removed); $soft = array_diff($messages, $good_uids); // Now remove them so we don't return them again next time, and we // don't need to remember them since once they are SOFTDELETED, we // no longer care about any changes to the message. foreach ($soft as $id) { unset($this->_messages[$id]); } return $soft; } else { return $this->_softDeleted; } } /** * Set the list of uids to be SOFTDELETEd. Only needed for CONDSTORE * servers. * * @param array $softDeleted The message UID list. */ public function setSoftDeleted(array $softDeleted) { $this->_softDeleted = $softDeleted; $this->_messages = array_diff($this->_messages, $this->_softDeleted); } /** * Check the validity of various values. * * @param array $params A status array containing status to check. * * @throws Horde_ActiveSync_Exception_StaleState */ public function checkValidity(array $params = array()) { if (!$this->uidvalidity()) { throw new Horde_ActiveSync_Exception('State not initialized.'); } if (!empty($params[self::UIDVALIDITY]) && $this->uidvalidity() != $params[self::UIDVALIDITY]) { throw new Horde_ActiveSync_Exception_StaleState('UIDVALIDTY no longer valid'); } } /** * Set the list of expunged message UIDs. * * @param array $uids An array of message UIDs that have been expunged. * @throws Horde_ActiveSync_Exception_StaleState */ public function setRemoved(array $uids) { // Protect against HUGE numbers of UIDs from apparently broken(?) servers. if (count($uids)) { if ($uids[0] < $this->minuid()) { throw new Horde_ActiveSync_Exception_StaleState( 'BROKEN IMAP server has returned all VANISHED UIDs.'); } } $this->_removed = $uids; } /** * Updates the internal UID cache if needed and clears the internal * update/deleted/changed cache. To be called after all changes have * been dealt with by the activesync client. */ public function updateState() { if (empty($this->_status[self::HIGHESTMODSEQ])) { $this->_messages = array_diff(array_keys($this->_messages), $this->_removed); foreach ($this->_added as $add) { $this->_messages[] = $add; } $this->_messages = $this->_flags + array_flip($this->_messages); } else { if ($this->_primed) { $this->_messages = $this->_added; $this->_primed = false; } else { foreach ($this->_added as $add) { $this->_messages[] = $add; } $this->_messages = array_diff($this->_messages, $this->_removed); } } // Clean up $this->_removed = array(); $this->_added = array(); $this->_changed = array(); $this->_flags = array(); $this->_softDeleted = array(); $this->haveInitialSync = true; } /** * Return the folder's UID validity. * * @return string|boolean The folder UID validity marker, or false if not set. */ public function uidvalidity() { if (!array_key_exists(self::UIDVALIDITY, $this->_status)) { return false; } return $this->_status[self::UIDVALIDITY]; } /** * Return the folder's next UID number. * * @return string The next UID number. */ public function uidnext() { return empty($this->_status[self::UIDNEXT]) ? 0 : $this->_status[self::UIDNEXT]; } /** * Return the folder's MODSEQ value. * * @return string The MODSEQ number. */ public function modseq() { return empty($this->_status[self::HIGHESTMODSEQ]) ? 0 : $this->_status[self::HIGHESTMODSEQ]; } /** * Return the total, unfiltered number of messages in the folder. * * @return integer The total number of messages. */ public function total_messages() { return empty($this->_status[self::MESSAGES]) ? 0 : $this->_status[self::MESSAGES]; } /** * Return the list of UIDs currently on the device. * * @return array The list of backend messages. */ public function messages() { return empty($this->_status[self::HIGHESTMODSEQ]) ? array_keys($this->_messages) : $this->_messages; } /** * Return the internal message flags changes cache. * * @return array The array of message flag changes. */ public function flags() { return $this->_flags; } /** * Return the internal message category cache. * * @return array The array of message categories. @see self::$_categories */ public function categories() { return $this->_categories; } /** * Return the list of UIDs that need to be added to the device. * * @return array The list of UIDs. */ public function added() { return $this->_added; } /** * Return the list of UIDs that need to have flag changes sent to the device * * @return array The list of UIDs. */ public function changed() { return $this->_changed; } /** * Return the list of UIDs that need to be removed from the device. * * @return array The list of UIDs. */ public function removed() { return $this->_removed; } /** * Return the minimum IMAP UID contained in this folder. * * @return integer The IMAP UID. */ public function minuid() { if (empty($this->_messages)) { return 0; } if (empty($this->_status[self::HIGHESTMODSEQ])) { return min(array_keys($this->_messages)); } return min($this->_messages); } /** * Serialize this object. * * @return string The serialized data. */ public function serialize() { if (!empty($this->_status[self::HIGHESTMODSEQ])) { $msgs = (count($this->_messages) > self::COMPRESSION_LIMIT) ? $this->_toSequenceString($this->_messages) : implode(',', $this->_messages); } else { $msgs = $this->_messages; } return json_encode(array( 's' => $this->_status, 'm' => $msgs, 'f' => $this->_serverid, 'c' => $this->_class, 'lsd' => $this->_lastSinceDate, 'sd' => $this->_softDelete, 'v' => self::VERSION) ); } /** * Reconstruct the object from serialized data. * * @param string $data The serialized data. * @throws Horde_ActiveSync_Exception_StaleState */ public function unserialize($data) { $d_data = json_decode($data, true); if (!is_array($d_data) || empty($d_data['v']) || $d_data['v'] != self::VERSION) { // Try using the old serialization strategy, since this would save // an expensive resync of email collections. $d_data = @unserialize($data); if (!is_array($d_data) || empty($d_data['v']) || $d_data['v'] != 1) { throw new Horde_ActiveSync_Exception_StaleState('Cache version change'); } } $this->_status = $d_data['s']; $this->_messages = $d_data['m']; $this->_serverid = $d_data['f']; $this->_class = $d_data['c']; $this->_lastSinceDate = $d_data['lsd']; $this->_softDelete = $d_data['sd']; $this->haveInitialSync = !empty($this->_messages); if (!empty($this->_status[self::HIGHESTMODSEQ]) && is_string($this->_messages)) { $this->_messages = $this->_fromSequenceString($this->_messages); } } /** * Convert the instance into a string. * * @return string The string representation for this instance. */ public function __toString() { return sprintf( 'status: %s\nchanged: %s\nadded: %s\nremoved: %s', join(', ', $this->_status), join(', ', $this->_changed), join(', ', $this->_added), join(', ', $this->_removed) ); } /** * Create an IMAP message sequence string from a list of indices. * * Index Format: range_start:range_end,uid,uid2,... * * @param array $ids An array of UIDs. * * @return string The IMAP message sequence string. */ protected function _toSequenceString(array $ids) { if (empty($ids)) { return ''; } $in = $ids; sort($in, SORT_NUMERIC); $first = $last = array_shift($in); $i = count($in) - 1; $out = array(); reset($in); while (list($key, $val) = each($in)) { if (($last + 1) == $val) { $last = $val; } if (($i == $key) || ($last != $val)) { if ($last == $first) { $out[] = $first; if ($i == $key) { $out[] = $val; } } else { $out[] = $first . ':' . $last; if (($i == $key) && ($last != $val)) { $out[] = $val; } } $first = $last = $val; } } return empty($out) ? $first : implode(',', $out); } /** * Parse an IMAP message sequence string into a list of indices. * * @see _toSequenceString() * * @param string $str The IMAP message sequence string. * * @return array An array of indices. */ protected function _fromSequenceString($str) { $ids = array(); $str = trim($str); if (!strlen($str)) { return $ids; } $idarray = explode(',', $str); if (strpos($str, ':') === false) { return $idarray; } reset($idarray); while (list(,$val) = each($idarray)) { $range = explode(':', $val); if (isset($range[1])) { for ($i = min($range), $j = max($range); $i <= $j; ++$i) { $ids[] = $i; } } else { $ids[] = $val; } } return $ids; } } Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Folder/RI.php0000664000076500000240000001061212654565405020040 0ustar * @package ActiveSync */ /** * The class contains functionality for maintaining state for the * Recipient Information Cache. * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2014-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync */ class Horde_ActiveSync_Folder_RI extends Horde_ActiveSync_Folder_Base implements Serializable { const VERSION = 1; /** * The current list of recipient email addresses. * * @var array */ protected $_contacts = array(); protected $_serverid = 'RI'; protected $_removed = array(); protected $_added = array(); /** * Flag for indicating we have an initial sync for this collection. * * @var boolean */ public $haveInitialSync = false; /** * Set the current Recipient Cache * * @param array $contacts An array of email addresses. Ordered by weight. */ public function setChanges(array $contacts) { $contacts = array_reverse($contacts); // Calculate deletions. foreach ($this->_contacts as $weight => $email) { if (empty($contacts[$weight]) || $contacts[$weight] != $email) { $this->_removed[] = $email . ':' . $weight; } } // Additions foreach ($contacts as $weight => $email) { if (empty($this->_contacts[$weight]) || $this->_contacts[$weight] != $email) { $this->_added[] = $email . ':' . $weight; } } $this->_contacts = $contacts; } /** * Updates the internal UID cache, and clears the internal * update/deleted/changed cache. */ public function updateState() { $this->haveInitialSync = true; $this->_removed = array(); $this->_added = array(); } /** * Convert the instance into a string. * * @return string The string representation for this instance. */ public function __toString() { return sprintf( 'serverid: %s\nclass: %s\n', $this->serverid(), $this->collectionClass()); } /** * Return the recipients that are to be added. * * @return array An array of psuedo-uids consisting of the the email * address, a colon, and the weighed rank. E.g. * user@example.com:10 */ public function added() { return $this->_added; } /** * Return the recipients that are to be deleted. * * @return array An array of psuedo-uids consisting of the the email * address, a colon, and the weighed rank. E.g. * user@example.com:10 */ public function removed() { return $this->_removed; } /** * Serialize this object. * * @return string The serialized data. */ public function serialize() { return json_encode(array( 'd' => $this->_contacts, 'f' => $this->_serverid, 'c' => $this->_class, 'v' => self::VERSION) ); } /** * Reconstruct the object from serialized data. * * @param string $data The serialized data. * @throws Horde_ActiveSync_Exception_StaleState */ public function unserialize($data) { $data = @json_decode($data, true); if (!is_array($data) || empty($data['v']) || $data['v'] != self::VERSION) { throw new Horde_ActiveSync_Exception_StaleState('Cache version change'); } $this->_contacts = $data['d']; $this->_serverid = $data['f']; $this->_class = $data['c']; } } Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Imap/Adapter.php0000664000076500000240000021154612654565405020572 0ustar * @package ActiveSync */ /** * Horde_ActiveSync_Imap_Adapter contains methods for communicating with * Horde's Horde_Imap_Client library. * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2012-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync */ class Horde_ActiveSync_Imap_Adapter { /** * Maximum number of of messages to fetch from the IMAP server in one go. */ const MAX_FETCH = 2000; /** * @var Horde_ActiveSync_Interface_ImapFactory */ protected $_imap; /** * @var Horde_Log_Logger */ protected $_logger; /** * @var string */ protected $_defaultNamespace; /** * Process id used for logging. * * @var integer */ protected $_procid; /** * Cont'r * * @param array $params Parameters: * - factory: (Horde_ActiveSync_Interface_ImapFactory) Factory object * DEFAULT: none - REQUIRED */ public function __construct(array $params = array()) { $this->_imap = $params['factory']; Horde_Mime_Part::$defaultCharset = 'UTF-8'; Horde_Mime_Headers::$defaultCharset = 'UTF-8'; $this->_procid = getmypid(); $this->_logger = new Horde_Log_Logger(new Horde_Log_Handler_Null()); } /** * Append a message to the specified mailbox. Used for saving sent email. * * @param string $folderid The mailbox * @param string|stream $msg The message * @param array $flags Any flags to set on the newly appended message. * * @throws new Horde_ActiveSync_Exception, Horde_ActiveSync_Exception_FolderGone */ public function appendMessage($folderid, $msg, array $flags = array()) { $message = array(array('data' => $msg, 'flags' => $flags)); $mbox = new Horde_Imap_Client_Mailbox($folderid); try { $this->_getImapOb()->append($mbox, $message); } catch (Horde_Imap_Client_Exception $e) { if (!$this->_mailboxExists($folderid)) { throw new Horde_ActiveSync_Exception_FolderGone(); } throw new Horde_ActiveSync_Exception($e); } } /** * Create a new mailbox on the server, and subscribe to it. * * @param string $name The new mailbox name. * @param string $parent The parent mailbox, if any. * * @return string The new serverid for the mailbox. This is the UTF-8 name * of the mailbox. @since 2.9.0 * @throws Horde_ActiveSync_Exception, Horde_ActiveSync_Exception_FolderExists */ public function createMailbox($name, $parent = null) { if (!empty($parent)) { $ns = $this->_defaultNamespace(); $name = $parent . $ns['delimiter'] . $name; } $mbox = new Horde_Imap_Client_Mailbox($this->_prependNamespace($name)); $imap = $this->_getImapOb(); try { $imap->createMailbox($mbox); $imap->subscribeMailbox($mbox, true); } catch (Horde_Imap_Client_Exception $e) { if ($e->getCode() == Horde_Imap_Client_Exception::ALREADYEXISTS) { $this->_logger(sprintf( '[%s] Mailbox %s already exists, subscribing to it.', $this->_procid, $name )); try { $imap->subscribeMailbox($mbox, true); } catch (Horde_Imap_Client_Exception $e) { // Exists, but could not subscribe to it, something is // *really* wrong. throw new Horde_ActiveSync_Exception_FolderExists('Folder Exists!'); } } throw new Horde_ActiveSync_Exception($e); } return $mbox->utf8; } /** * Delete a mailbox * * @param string $name The mailbox name to delete. * * @throws Horde_ActiveSync_Exception, Horde_ActiveSync_Exception_FolderGone */ public function deleteMailbox($name) { $mbox = new Horde_Imap_Client_Mailbox($name); try { $this->_getImapOb()->deleteMailbox($mbox); } catch (Horde_Imap_Client_Exception $e) { if ($e->getCode() == Horde_Imap_Client_Exception::NONEXISTENT) { throw new Horde_ActiveSync_Exception_FolderGone(); } throw new Horde_ActiveSync_Exception($e); } } /** * Completely empty specified mailbox. * * @param string $mbox The mailbox to empty. * * @throws Horde_ActiveSync_Exception * @since 2.18.0 */ public function emptyMailbox($mbox) { $mbox = new Horde_Imap_Client_Mailbox($mbox); try { $this->_getImapOb()->expunge($mbox, array('delete' => true)); } catch (Horde_Imap_Client_Exception $e) { throw new Horde_ActiveSync_Exception($e); } } /** * Permanently delete a mail message. * * @param array $uids The message UIDs * @param string $folderid The folder id. * * @return array An array of uids that were successfully deleted. * @throws Horde_ActiveSync_Exception */ public function deleteMessages(array $uids, $folderid) { $imap = $this->_getImapOb(); $mbox = new Horde_Imap_Client_Mailbox($folderid); $ids_obj = new Horde_Imap_Client_Ids($uids); // Need to ensure the source message exists so we may properly notify // the client of the error. $search_q = new Horde_Imap_Client_Search_Query(); $search_q->ids($ids_obj); try { $fetch_res = $imap->search($mbox, $search_q); } catch (Horde_Imap_Client_Exception $e) { throw new Horde_ActiveSync_Exception($e); } if ($fetch_res['count'] != count($uids)) { $ids_obj = $fetch_res['match']; } try { $imap->store($mbox, array( 'ids' => $ids_obj, 'add' => array('\deleted')) ); $imap->expunge($mbox, array('ids' => $ids_obj)); } catch (Horde_Imap_Client_Exception $e) { throw new Horde_ActiveSync_Exception($e); } return $ids_obj->ids; } /** * Return the content of a specific MIME part of the specified message. * * @param string $mailbox The mailbox name. * @param string $uid The message UID. * @param string $part The MIME part identifier. * * @return Horde_Mime_Part The attachment data * * @throws Horde_ActiveSync_Exception */ public function getAttachment($mailbox, $uid, $part) { $imap = $this->_getImapOb(); $mbox = new Horde_Imap_Client_Mailbox($mailbox); $messages = $this->_getMailMessages($mbox, array($uid)); if (empty($messages[$uid]) || !$messages[$uid]->exists(Horde_Imap_Client::FETCH_STRUCTURE)) { throw new Horde_ActiveSync_Exception('Message Gone'); } $msg = new Horde_ActiveSync_Imap_Message($imap, $mbox, $messages[$uid]); $part = $msg->getMimePart($part); return $part; } /** * Return an array of available mailboxes. Uses's the mail/mailboxList API * method for obtaining the list. * * @return array */ public function getMailboxes() { return $this->_imap->getMailboxes(); } /** * Return the list of special mailboxes. * * @return array */ public function getSpecialMailboxes() { return $this->_imap->getSpecialMailboxes(); } /** * Return a complete Horde_ActiveSync_Imap_Message object for the requested * uid. * * @param string $mailbox The mailbox name. * @param array|integer $uid The message uid. * @param array $options Additional options: * - headers: (boolean) Also fetch the message headers if this is true. * DEFAULT: false (Do not fetch headers). * * @return array An array of Horde_ActiveSync_Imap_Message objects. * @todo This should be renamed to getImapMessages since we can now accept * an array of $uids. */ public function getImapMessage($mailbox, $uid, array $options = array()) { if (!is_array($uid)) { $uid = array($uid); } $mbox = new Horde_Imap_Client_Mailbox($mailbox); // @todo H6 - expand the $options array the same as _getMailMessages() // for now, always retrieve the envelope data as well. $options['envelope'] = true; $messages = $this->_getMailMessages($mbox, $uid, $options); $res = array(); foreach ($messages as $id => $message) { if ($message->exists(Horde_Imap_Client::FETCH_STRUCTURE)) { $res[$id] = new Horde_ActiveSync_Imap_Message($this->_getImapOb(), $mbox, $message); } } return $res; } /** * Return message changes from the specified mailbox. * * @param Horde_ActiveSync_Folder_Imap $folder The folder object. * @param array $options Additional options: * - sincedate: (integer) Timestamp of earliest message to retrieve. * DEFAULT: 0 (Don't filter). * - protocolversion: (float) EAS protocol version to support. * DEFAULT: none REQUIRED * - softdelete: (boolean) If true, calculate SOFTDELETE data. * @since 2.8.0 * - refreshfilter: (boolean) If true, force refresh the query to reflect * changes in FILTERTYPE (using the sincedate) * @since 2.28.0 * * @return Horde_ActiveSync_Folder_Imap The folder object, containing any * change instructions for the device. * * @throws Horde_ActiveSync_Exception_FolderGone, * Horde_ActiveSync_Exception, Horde_ActiveSync_Exception_StaleState */ public function getMessageChanges( Horde_ActiveSync_Folder_Imap $folder, array $options = array()) { $imap = $this->_getImapOb(); $mbox = new Horde_Imap_Client_Mailbox($folder->serverid()); $flags = array(); $search_uids = array(); // Note: non-CONDSTORE servers will return a highestmodseq of 0 $status_flags = Horde_Imap_Client::STATUS_HIGHESTMODSEQ | Horde_Imap_Client::STATUS_UIDVALIDITY | Horde_Imap_Client::STATUS_UIDNEXT_FORCE | Horde_Imap_Client::STATUS_MESSAGES | Horde_Imap_Client::STATUS_FORCE_REFRESH; try { $status = $imap->status($mbox, $status_flags); } catch (Horde_Imap_Client_Exception $e) { // If we can't status the mailbox, assume it's gone. throw new Horde_ActiveSync_Exception_FolderGone($e); } $this->_logger->info(sprintf( '[%s] IMAP status: %s', $this->_procid, serialize($status)) ); $current_modseq = $status[Horde_ActiveSync_Folder_Imap::HIGHESTMODSEQ]; if (($current_modseq && $folder->modseq() > 0) && (($folder->modseq() < $current_modseq) || !empty($options['softdelete']) || !empty($options['refreshfilter']) )) { $this->_logger->info(sprintf( '[%s] CONDSTORE and CHANGES', $this->_procid)); $folder->checkValidity($status); // Catch all *changes* since the provided MODSEQ value. $query = new Horde_Imap_Client_Search_Query(); // $imap->search uses a >= comparison for MODSEQ, so we must // increment by one so we don't continuously receive the same change // set. $query->modseq($folder->modseq() + 1); if (!empty($options['sincedate'])) { $query->dateSearch( new Horde_Date($options['sincedate']), Horde_Imap_Client_Search_Query::DATE_SINCE); } $search_ret = $imap->search( $mbox, $query, array('results' => array(Horde_Imap_Client::SEARCH_RESULTS_MATCH)) ); $search_uids = $search_ret['count'] ? $search_ret['match']->ids : array(); // Catch changes to FILTERTYPE. if (!empty($options['refreshfilter'])) { $this->_logger->info(sprintf( '[%s] Checking for additional messages within the new FilterType parameters.', $this->_procid)); $search_ret = $this->_buildSearchQuery($folder, $options, $mbox, false); if ($search_ret['count']) { $this->_logger->info(sprintf( '[%s] Found %d messages that are now outside FilterType.', $this->_procid, $search_ret['count']) ); $search_uids = array_merge($search_uids, $search_ret['match']->ids); } else { $this->_logger->info(sprintf( '[%s] Found NO additional messages.', $this->_procid) ); } } // Protect against very large change sets like might occur if // the FILTERTYPE is changed from some short interval like one week // to no filter at all. $cnt = (count($search_uids) / self::MAX_FETCH) + 1; $query = new Horde_Imap_Client_Fetch_Query(); $query->modseq(); $query->flags(); $changes = array(); $categories = array(); for ($i = 0; $i <= $cnt; $i++) { $ids = new Horde_Imap_Client_Ids( array_slice($search_uids, $i * self::MAX_FETCH, self::MAX_FETCH) ); try { $fetch_ret = $imap->fetch($mbox, $query, array('ids' => $ids)); } catch (Horde_Imap_Client_Exception $e) { $this->_logger->err($e->getMessage()); throw new Horde_ActiveSync_Exception($e); } $this->_buildModSeqChanges($changes, $flags, $categories, $fetch_ret, $options, $current_modseq); } // Set the changes in the folder object. $folder->setChanges( $changes, $flags, $categories, !empty($options['softdelete']) || !empty($options['refreshfilter']) ); // Check for deleted messages. try { $deleted = $imap->vanished( $mbox, $folder->modseq(), array('ids' => new Horde_Imap_Client_Ids($folder->messages()))); } catch (Horde_Imap_Client_Excetion $e) { $this->_logger->err($e->getMessage()); throw new Horde_ActiveSync_Exception($e); } $folder->setRemoved($deleted->ids); $this->_logger->info(sprintf( '[%s] Found %d deleted messages.', $this->_procid, $deleted->count()) ); // Check for SOFTDELETE messages. if ((!empty($options['softdelete']) || !empty($options['refreshfilter'])) && !empty($options['sincedate'])) { $this->_logger->info(sprintf( '[%s] Polling for SOFTDELETE in %s before %d', $this->_procid, $folder->serverid(), $options['sincedate'])); $search_ret = $this->_buildSearchQuery($folder, $options, $mbox, true); if ($search_ret['count']) { $this->_logger->info(sprintf( '[%s] Found %d messages to SOFTDELETE.', $this->_procid, count($search_ret['match']->ids))); $folder->setSoftDeleted($search_ret['match']->ids); } else { $this->_logger->info(sprintf( '[%s] Found NO messages to SOFTDELETE.', $this->_procid)); } $folder->setSoftDeleteTimes($options['sincedate'], time()); } } elseif ($folder->uidnext() == 0) { $this->_logger->info(sprintf( '[%s] INITIAL SYNC', $this->_procid)); $query = new Horde_Imap_Client_Search_Query(); if (!empty($options['sincedate'])) { $query->dateSearch( new Horde_Date($options['sincedate']), Horde_Imap_Client_Search_Query::DATE_SINCE); } $search_ret = $imap->search( $mbox, $query, array('results' => array(Horde_Imap_Client::SEARCH_RESULTS_MATCH))); if ($current_modseq && !$folder->haveInitialSync) { $this->_logger->info(sprintf( '[%s] Priming IMAP folder object.', $this->_procid)); $folder->primeFolder($search_ret['match']->ids); } elseif (count($search_ret['match']->ids)) { // No modseq. $query = new Horde_Imap_Client_Fetch_Query(); $query->flags(); $cnt = ($search_ret['count'] / self::MAX_FETCH) + 1; for ($i = 0; $i <= $cnt; $i++) { $ids = new Horde_Imap_Client_Ids( array_slice($search_ret['match']->ids, $i * self::MAX_FETCH, self::MAX_FETCH) ); $fetch_ret = $imap->fetch($mbox, $query, array('ids' => $ids)); foreach ($fetch_ret as $uid => $data) { $flags[$uid] = array( 'read' => (array_search(Horde_Imap_Client::FLAG_SEEN, $data->getFlags()) !== false) ? 1 : 0 ); if (($options['protocolversion']) > Horde_ActiveSync::VERSION_TWOFIVE) { $flags[$uid]['flagged'] = (array_search(Horde_Imap_Client::FLAG_FLAGGED, $data->getFlags()) !== false) ? 1 : 0; } } } $folder->setChanges($search_ret['match']->ids, $flags); } } elseif ($current_modseq == 0) { $this->_logger->info(sprintf( '[%s] NO CONDSTORE or per mailbox MODSEQ. minuid: %s, total_messages: %s', $this->_procid, $folder->minuid(), $status['messages'])); $folder->checkValidity($status); $query = new Horde_Imap_Client_Search_Query(); if (!empty($options['sincedate'])) { $query->dateSearch( new Horde_Date($options['sincedate']), Horde_Imap_Client_Search_Query::DATE_SINCE); } try { $search_ret = $imap->search( $mbox, $query, array('results' => array(Horde_Imap_Client::SEARCH_RESULTS_MATCH))); } catch (Horde_Imap_Client_Exception $e) { $this->_logger->err($e->getMessage()); throw new Horde_ActiveSync_Exception($e); } $cnt = ($search_ret['count'] / self::MAX_FETCH) + 1; $query = new Horde_Imap_Client_Fetch_Query(); $query->flags(); for ($i = 0; $i <= $cnt; $i++) { $ids = new Horde_Imap_Client_Ids( array_slice($search_ret['match']->ids, $i * self::MAX_FETCH, self::MAX_FETCH) ); try { $fetch_ret = $imap->fetch($mbox, $query, array('ids' => $ids)); } catch (Horde_Imap_Client_Exception $e) { $this->_logger->err($e->getMessage()); throw new Horde_ActiveSync_Exception($e); } foreach ($fetch_ret as $uid => $data) { $flags[$uid] = array( 'read' => (array_search(Horde_Imap_Client::FLAG_SEEN, $data->getFlags()) !== false) ? 1 : 0 ); if (($options['protocolversion']) > Horde_ActiveSync::VERSION_TWOFIVE) { $flags[$uid]['flagged'] = (array_search(Horde_Imap_Client::FLAG_FLAGGED, $data->getFlags()) !== false) ? 1 : 0; } } } if (!empty($flags)) { $folder->setChanges($search_ret['match']->ids, $flags); } $folder->setRemoved($imap->vanished($mbox, null, array('ids' => new Horde_Imap_Client_Ids($folder->messages())))->ids); } elseif ($current_modseq > 0 && $folder->modseq() == 0) { throw new Horde_ActiveSync_Exception_StaleState('Transition to MODSEQ enabled server'); } $folder->setStatus($status); return $folder; } /** * Return message UIDs that are now within the cureent FILTERTYPE value. * * @param Horde_ActiveSync_Folder_Imap $folder The IMAP folder object. * @param array $options Options array. * @param Horde_Imap_Client_Mailbox $mbox The current mailbox. * @param boolean $is_delete If true, return messages * to SOFTDELETE. * * @return Horde_Imap_Client_Search_Results */ protected function _buildSearchQuery($folder, $options, $mbox, $is_delete) { $query = new Horde_Imap_Client_Search_Query(); $query->dateSearch( new Horde_Date($options['sincedate']), $is_delete ? Horde_Imap_Client_Search_Query::DATE_BEFORE : Horde_Imap_Client_Search_Query::DATE_SINCE ); $query->ids(new Horde_Imap_Client_Ids($folder->messages()), !$is_delete); try { return $this->_getImapOb()->search( $mbox, $query, array('results' => array(Horde_Imap_Client::SEARCH_RESULTS_MATCH))); } catch (Horde_Imap_Client_Exception $e) { $this->_logger->err($e->getMessage()); throw new Horde_ActiveSync_Exception($e); } } /** * Populates the changes, flags, and categories arrays with data from * any messages added/changed on the IMAP server since the last poll. * * @param array &$changes Changes array. * @param array &$flags Flags array. * @param array &$categories Categories array. * @param Horde_Imap_Client_Fetch_Results $fetch_ret Fetch results. * @param array $options Options array. * @param integer $modseq Current MODSEQ. */ protected function _buildModSeqChanges( &$changes, &$flags, &$categories, $fetch_ret, $options, $modseq) { // Get custom flags to use as categories. $msgFlags = $this->_getMsgFlags(); // Filter out any changes that we already know about. $fetch_keys = $fetch_ret->ids(); $result_set = array_diff($fetch_keys, $changes); foreach ($result_set as $uid) { $data = $fetch_ret[$uid]; // Ensure no changes after the current modseq as reported by the // server status have been returned. if ($data->getModSeq() <= $modseq) { $changes[] = $uid; $flags[$uid] = array( 'read' => (array_search(Horde_Imap_Client::FLAG_SEEN, $data->getFlags()) !== false) ? 1 : 0 ); if (($options['protocolversion']) > Horde_ActiveSync::VERSION_TWOFIVE) { $flags[$uid]['flagged'] = (array_search(Horde_Imap_Client::FLAG_FLAGGED, $data->getFlags()) !== false) ? 1 : 0; } if ($options['protocolversion'] > Horde_ActiveSync::VERSION_TWELVEONE) { $categories[$uid] = array(); foreach ($data->getFlags() as $flag) { if (!empty($msgFlags[Horde_String::lower($flag)])) { $categories[$uid][] = $msgFlags[Horde_String::lower($flag)]; } } } } } } /** * Return AS mail messages, from the given IMAP UIDs. * * @param string $folderid The mailbox folder. * @param array $messages List of IMAP message UIDs * @param array $options Additional Options: * - truncation: (integer) The truncation constant, if sent from device. * DEFAULT: false (No truncation). * - bodyprefs: (array) The bodypref settings, if sent from device. * DEFAULT: none (No body prefs sent, or enforced). * - bodypartprefs: (array) The bodypartprefs settings, if present. * - mimesupport: (integer) Indicates if MIME is supported or not. * Possible values: 0 - Not supported 1 - Only S/MIME or * 2 - All MIME. * DEFAULT: 0 (No MIME support) * - protocolversion: (float) The EAS protocol version to support. * DEFAULT: 2.5 * * @return array An array of Horde_ActiveSync_Message_Mail objects. */ public function getMessages($folderid, array $messages, array $options = array()) { $mbox = new Horde_Imap_Client_Mailbox($folderid); $results = $this->_getMailMessages($mbox, $messages, array('headers' => true, 'envelope' => true)); $ret = array(); foreach ($results as $data) { if ($data->exists(Horde_Imap_Client::FETCH_STRUCTURE)) { try { $ret[] = $this->_buildMailMessage($mbox, $data, $options); } catch (Horde_Exception_NotFound $e) { } } } return $ret; } /** * Return an array of message UIDs from a list of Message-IDs. * * @param string $mid The Message-ID * @param Horde_ActiveSync_Folder_Imap $folder The folder object to search. * * @return integer The UID * @throws Horde_Exception_NotFound */ public function getUidFromMid($mid, Horde_ActiveSync_Folder_Imap $folder) { $iids = new Horde_Imap_Client_Ids(array_diff($folder->messages(), $folder->removed())); $search_q = new Horde_Imap_Client_Search_Query(); $search_q->ids($iids); $search_q->headerText('Message-ID', $mid); $mbox = new Horde_Imap_Client_Mailbox($folder->serverid()); $results = $this->_getImapOb()->search($mbox, $search_q); $uid = $results['match']->ids; if (!empty($uid)) { return current($uid); } throw new Horde_Exception_NotFound('Message not found.'); } /** * Attempt to find a Message-ID in a list of mail folders. * * @return array An array with the 0 element being the mbox * @throws Horde_Exception_NotFound, Horde_ActiveSync_Exception * * @deprecated This is unused and should be removed. */ public function getUidFromMidInFolders($id, array $folders) { $search_q = new Horde_Imap_Client_Search_Query(); $search_q->headerText('Message-ID', $id); foreach ($folders as $folder) { $mbox = new Horde_Imap_Client_Mailbox($folder->_serverid); try { $results = $this->_getImapOb()->search($mbox, $search_q); } catch (Horde_Imap_Client_Exception $e) { throw new Horde_ActiveSync_Exception($e->getMessage()); } $uid = $results['match']->ids; if (!empty($uid)) { return array($mbox, current($uid)); } } throw new Horde_Exception_NotFound('Message not found.'); } /** * Move a mail message * * @param string $folderid The existing folderid. * @param array $ids The message UIDs of the messages to move. * @param string $newfolderid The folder id to move $id to. * * @return array An hash of oldUID => newUID. * * @throws Horde_ActiveSync_Exception */ public function moveMessage($folderid, array $ids, $newfolderid) { $imap = $this->_getImapOb(); $from = new Horde_Imap_Client_Mailbox($folderid); $to = new Horde_Imap_Client_Mailbox($newfolderid); $ids_obj = new Horde_Imap_Client_Ids($ids); // Need to ensure the source message exists so we may properly notify // the client of the error. $search_q = new Horde_Imap_Client_Search_Query(); $search_q->ids($ids_obj); $fetch_res = $imap->search($from, $search_q); if ($fetch_res['count'] != count($ids)) { $ids_obj = $fetch_res['match']; } try { return $imap->copy($from, $to, array('ids' => $ids_obj, 'move' => true, 'force_map' => true)); } catch (Horde_Imap_Client_Exception $e) { // We already got rid of the missing ids, must be something else. $this->_logger->err($e->getMessage()); throw new Horde_ActiveSync_Exception($e); } } /** * Ping a mailbox. This detects only if any new messages have arrived in * the specified mailbox. * * @param Horde_ActiveSync_Folder_Imap $folder The folder object. * * @return boolean True if changes were detected, otherwise false. * @throws Horde_ActiveSync_Exception, Horde_ActiveSync_Exception_FolderGone */ public function ping(Horde_ActiveSync_Folder_Imap $folder) { $mbox = new Horde_Imap_Client_Mailbox($folder->serverid()); // Note: non-CONDSTORE servers will return a highestmodseq of 0 $status_flags = Horde_Imap_Client::STATUS_HIGHESTMODSEQ | Horde_Imap_Client::STATUS_UIDNEXT_FORCE | Horde_Imap_Client::STATUS_MESSAGES | Horde_Imap_Client::STATUS_FORCE_REFRESH; // Get IMAP status. try { $status = $this->_getImapOb()->status($mbox, $status_flags); } catch (Horde_Imap_Client_Exception $e) { // See if the folder disappeared. if (!$this->_mailboxExists($mbox->utf8)) { throw new Horde_ActiveSync_Exception_FolderGone(); } throw new Horde_ActiveSync_Exception($e); } // If we have per mailbox MODSEQ then we can pick up flag changes too. $modseq = $status[Horde_ActiveSync_Folder_Imap::HIGHESTMODSEQ]; if ($modseq && $folder->modseq() > 0 && $folder->modseq() < $modseq) { return true; } // Increase in UIDNEXT is always a positive PING. if ($folder->uidnext() < $status['uidnext']) { return true; } // If the message count changes, something certainly changed. if ($folder->total_messages() != $status['messages']) { return true; } // Otherwise, no PING detectable changes present. return false; } /** * Perform a search from a search mailbox request. * * @param array $query The query array. * * @return array An array of 'uniqueid', 'searchfolderid' hashes. */ public function queryMailbox($query) { return $this->_doQuery($query['query']); } /** * Rename a mailbox * * @param string $old The old mailbox name. * @param string $new The new mailbox name. * @param string $parent The parent mailbox, if any. * * @return string The new serverid for the mailbox. * @since 2.9.0 * @throws Horde_ActiveSync_Exception */ public function renameMailbox($old, $new, $parent = null) { if (!empty($parent)) { $ns = $this->_defaultNamespace(); $new = $parent . $ns['delimiter'] . $new; } $new_mbox = new Horde_Imap_Client_Mailbox($new); try { $this->_getImapOb()->renameMailbox( new Horde_Imap_Client_Mailbox($old), $new_mbox ); } catch (Horde_Imap_Client_Exception $e) { throw new Horde_ActiveSync_Exception($e); } return $new_mbox->utf8; } /** * Set a IMAP message flag. * * @param string $mailbox The mailbox name. * @param integer $uid The message UID. * @param string $flag The flag to set. A Horde_ActiveSync:: constant. * * @throws Horde_ActiveSync_Exception */ public function setImapFlag($mailbox, $uid, $flag) { $mbox = new Horde_Imap_Client_Mailbox($mailbox); $options = array( 'ids' => new Horde_Imap_Client_Ids(array($uid)) ); switch ($flag) { case Horde_ActiveSync::IMAP_FLAG_REPLY: $options['add'] = array(Horde_Imap_Client::FLAG_ANSWERED); break; case Horde_ActiveSync::IMAP_FLAG_FORWARD: $options['add'] = array(Horde_Imap_Client::FLAG_FORWARDED); } try { $this->_getImapOb()->store($mbox, $options); } catch (Horde_Imap_Client_Exception $e) { throw new Horde_ActiveSync_Exception($e); } } /** * Set this instance's logger. * * @param Horde_Log_Logger $logger The logger. */ public function setLogger(Horde_Log_Logger $logger) { $this->_logger = $logger; } /** * Set a POOMMAIL_FLAG on a mail message. This method differs from * setReadFlag() in that it is passed a Flag object, which contains * other data beside the seen status. Used for setting flagged for followup * and potentially creating tasks based on the email. * * @param string $mailbox The mailbox name. * @param integer $uid The message uid. * @param Horde_ActiveSync_Message_Flag $flag The flag * * @throws Horde_ActiveSync_Exception */ public function setMessageFlag($mailbox, $uid, $flag) { // There is no standard in EAS for the name of flags, so it is impossible // to map flagtype to an actual message flag. Until a better solution // is thought of, just always use \flagged. There is also no meaning // of a "completed" flag/task in IMAP email, so if it's not active, // clear the flag. $mbox = new Horde_Imap_Client_Mailbox($mailbox); $options = array( 'ids' => new Horde_Imap_Client_Ids(array($uid)), ); switch ($flag->flagstatus) { case Horde_ActiveSync_Message_Flag::FLAG_STATUS_ACTIVE: $options['add'] = array(Horde_Imap_Client::FLAG_FLAGGED); break; default: $options['remove'] = array(Horde_Imap_Client::FLAG_FLAGGED); } try { $this->_getImapOb()->store($mbox, $options); } catch (Horde_Imap_Client_Exception $e) { throw new Horde_ActiveSync_Exception($e); } } public function categoriesToFlags($mailbox, $categories, $uid) { $msgFlags = $this->_getMsgFlags(); $mbox = new Horde_Imap_Client_Mailbox($mailbox); $options = array( 'ids' => new Horde_Imap_Client_Ids(array($uid)), 'add' => array() ); foreach ($categories as $category) { // Do our best to make sure the imap flag is a RFC 3501 compliant. $atom = new Horde_Imap_Client_Data_Format_Atom(strtr(Horde_String_Transliterate::toAscii($category), ' ', '_')); $imapflag = Horde_String::lower($atom->stripNonAtomCharacters()); if (!empty($msgFlags[$imapflag])) { $options['add'][] = $imapflag; unset($msgFlags[$imapflag]); } } $options['remove'] = array_keys($msgFlags); try { $this->_getImapOb()->store($mbox, $options); } catch (Horde_Imap_Client_Exception $e) { throw new Horde_ActiveSync_Exception($e); } } /** * Set the message's read status. * * @param string $mailbox The mailbox name. * @param string $uid The message uid. * @param integer $flag Horde_ActiveSync_Message_Mail::FLAG_* constant * * @throws Horde_ActiveSync_Exception */ public function setReadFlag($mailbox, $uid, $flag) { $mbox = new Horde_Imap_Client_Mailbox($mailbox); $options = array( 'ids' => new Horde_Imap_Client_Ids(array($uid)), ); if ($flag == Horde_ActiveSync_Message_Mail::FLAG_READ_SEEN) { $options['add'] = array(Horde_Imap_Client::FLAG_SEEN); } else if ($flag == Horde_ActiveSync_Message_Mail::FLAG_READ_UNSEEN) { $options['remove'] = array(Horde_Imap_Client::FLAG_SEEN); } try { $this->_getImapOb()->store($mbox, $options); } catch (Horde_Imap_Client_Exception $e) { throw new Horde_ActiveSync_Exception($e); } } /** * Helper to build a subquery * * @param array $query A subquery array. * * @return Horde_Imap_Client_Search_Query The query object. */ protected function _buildSubQuery(array $query) { $imap_query = new Horde_Imap_Client_Search_Query(); foreach ($query as $q) { foreach ($q['value'] as $type => $value) { switch ($type) { case Horde_ActiveSync_Message_Mail::POOMMAIL_DATERECEIVED: if ($q['op'] == Horde_ActiveSync_Request_Search::SEARCH_GREATERTHAN) { $range = Horde_Imap_Client_Search_Query::DATE_SINCE; } elseif ($q['op'] == Horde_ActiveSync_Request_Search::SEARCH_LESSTHAN) { $range = Horde_Imap_Client_Search_Query::DATE_BEFORE; } else { $range = Horde_Imap_Client_Search_Query::DATE_ON; } $imap_query->dateSearch($value, $range); break; case Horde_ActiveSync_Request_Search::SEARCH_FREETEXT: $imap_query->text($value); break; } } } return $imap_query; } /** * Builds a proper AS mail message object. * * @param Horde_Imap_Client_Mailbox $mbox The IMAP mailbox. * @param Horde_Imap_Client_Data_Fetch $data The fetch results. * @param array $options Additional Options: * - truncation: (integer) Truncate the message body to this length. * DEFAULT: No truncation. * - bodyprefs: (array) Bodyprefs, if sent from device. * DEFAULT: none (No body prefs sent or enforced). * - bodypartprefs: (array) Bodypartprefs, if sent from device. * DEFAULT: none (No body part prefs sent or enforced). * - mimesupport: (integer) Indicates if MIME is supported or not. * Possible values: 0 - Not supported 1 - Only S/MIME or * 2 - All MIME. * DEFAULT: 0 (No MIME support) * - protocolversion: (float) The EAS protocol version to support. * DEFAULT: 2.5 * * @return Horde_ActiveSync_Message_Mail The message object suitable for * streaming to the device. */ protected function _buildMailMessage( Horde_Imap_Client_Mailbox $mbox, Horde_Imap_Client_Data_Fetch $data, $options = array()) { $version = empty($options['protocolversion']) ? Horde_ActiveSync::VERSION_TWOFIVE : $options['protocolversion']; $imap_message = new Horde_ActiveSync_Imap_Message($this->_getImapOb(), $mbox, $data); $eas_message = Horde_ActiveSync::messageFactory('Mail'); // Build To: data (POOMMAIL_TO has a max length of 32768). $to = $imap_message->getToAddresses(); $eas_message->to = array_pop($to['to']); foreach ($to['to'] as $to_atom) { if (strlen($eas_message->to) + strlen($to_atom) > 32768) { break; } $eas_message->to .= ',' . $to_atom; } $eas_message->displayto = implode(';', $to['displayto']); if (empty($eas_message->displayto)) { $eas_message->displayto = $eas_message->to; } // Ensure we don't send broken UTF8 data to the client. It makes clients // angry. And we don't like angry clients. $hdr_charset = $imap_message->getStructure()->getHeaderCharset(); // Fill in other header data try { $eas_message->from = $imap_message->getFromAddress(); } catch (Horde_ActiveSync_Exception $e) { $this->_logger->err($e->getMessage()); } try { $eas_message->cc = $imap_message->getCc(); } catch (Horde_ActiveSync_Exception $e) { $this->_logger->err($e->getMessage()); } try { $eas_message->reply_to = $imap_message->getReplyTo(); } catch (Horde_ActiveSync_Exception $e) { $this->_logger->err($e->getMessage()); } $eas_message->subject = Horde_ActiveSync_Utils::ensureUtf8($imap_message->getSubject(), $hdr_charset); $eas_message->threadtopic = $eas_message->subject; $eas_message->datereceived = $imap_message->getDate(); $eas_message->read = $imap_message->getFlag(Horde_Imap_Client::FLAG_SEEN); // Default to IPM.Note - may change below depending on message content. $eas_message->messageclass = 'IPM.Note'; // Codepage id. MS recommends to always set to UTF-8 when possible. // See http://msdn.microsoft.com/en-us/library/windows/desktop/dd317756%28v=vs.85%29.aspx $eas_message->cpid = Horde_ActiveSync_Message_Mail::INTERNET_CPID_UTF8; // Message importance. First try X-Priority, then Importance since // Outlook sends the later. if ($priority = $imap_message->getHeaders()->getValue('X-priority')) { $priority = preg_replace('/\D+/', '', $priority); } else { $priority = $imap_message->getHeaders()->getValue('Importance'); } $eas_message->importance = $this->_getEASImportance($priority); // Get the body data. $mbd = $imap_message->getMessageBodyDataObject($options); if ($version == Horde_ActiveSync::VERSION_TWOFIVE) { $eas_message->body = $mbd->plain['body']->stream; $eas_message->bodysize = $mbd->plain['body']->length(true); $eas_message->bodytruncated = $mbd->plain['truncated']; $eas_message->attachments = $imap_message->getAttachments($version); } else { // Get the message body and determine original type. if ($mbd->html) { $eas_message->airsyncbasenativebodytype = Horde_ActiveSync::BODYPREF_TYPE_HTML; } else { $eas_message->airsyncbasenativebodytype = Horde_ActiveSync::BODYPREF_TYPE_PLAIN; } $airsync_body = Horde_ActiveSync::messageFactory('AirSyncBaseBody'); $body_type_pref = $mbd->getBodyTypePreference(); if ($body_type_pref == Horde_ActiveSync::BODYPREF_TYPE_MIME) { $this->_logger->info(sprintf( '[%s] Sending MIME Message.', $this->_procid)); // ActiveSync *REQUIRES* all data sent to be in UTF-8, so we // must convert the body parts to UTF-8. Unfortunately if the // email is signed (or encrypted for that matter) we can't // alter the data in anyway or the signature will not be // verified, so we fetch the entire message and hope for the best. if (!$imap_message->isSigned() && !$imap_message->isEncrypted()) { $mime = new Horde_Mime_Part(); if ($mbd->plain) { $plain_mime = new Horde_Mime_Part(); $plain_mime->setType('text/plain'); $plain_mime->setContents($mbd->plain['body']->stream, array('usestream' => true)); $plain_mime->setCharset('UTF-8'); } if ($mbd->html) { $html_mime = new Horde_Mime_Part(); $html_mime->setType('text/html'); $html_mime->setContents($mbd->html['body']->stream, array('usestream' => true)); $html_mime->setCharset('UTF-8'); } // Sanity check the mime type if (!$mbd->html && !empty($plain_mime)) { $mime = $plain_mime; } elseif (!$mbd->plain && !empty($html_mime)) { $mime = $html_mime; } elseif (!empty($plain_mime) && !empty($html_mime)) { $mime->setType('multipart/alternative'); $mime->addPart($plain_mime); $mime->addPart($html_mime); } $html_mime = null; $plain_mime = null; // If we have attachments, create a multipart/mixed wrapper. if ($imap_message->hasAttachments()) { $base = new Horde_Mime_Part(); $base->setType('multipart/mixed'); $base->addPart($mime); $atc = $imap_message->getAttachmentsMimeParts(); foreach ($atc as $atc_part) { $base->addPart($atc_part); } $eas_message->airsyncbaseattachments = $imap_message->getAttachments($version); } else { $base = $mime; } $mime = null; // Populate the EAS body structure with the MIME data, but // remove the Content-Type and Content-Transfer-Encoding // headers since we are building this ourselves. $headers = $imap_message->getHeaders(); $headers->removeHeader('Content-Type'); $headers->removeHeader('Content-Transfer-Encoding'); $airsync_body->data = $base->toString(array( 'headers' => $headers, 'stream' => true) ); $airsync_body->estimateddatasize = $base->getBytes(); } else { // Signed/Encrypted message - can't mess with it at all. $raw = new Horde_ActiveSync_Rfc822($imap_message->getFullMsg(true), false); $airsync_body->estimateddatasize = $raw->getBytes(); $airsync_body->data = $raw->getString(); $eas_message->airsyncbaseattachments = $imap_message->getAttachments($version); } $airsync_body->type = Horde_ActiveSync::BODYPREF_TYPE_MIME; // MIME Truncation // @todo Remove this sanity-check hack in 3.0. This is needed // since truncationsize incorrectly defaulted to a // MIME_TRUNCATION constant and could be cached in the sync-cache. $ts = !empty($options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_MIME]['truncationsize']) ? $options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_MIME]['truncationsize'] : false; $mime_truncation = (!empty($ts) && $ts > 9) ? $ts : (!empty($options['truncation']) && $options['truncation'] > 9 ? $options['truncation'] : false); $this->_logger->info(sprintf( '[%s] Checking MIMETRUNCATION: %s, ServerData: %s', $this->_procid, $mime_truncation, $airsync_body->estimateddatasize)); if (!empty($mime_truncation) && $airsync_body->estimateddatasize > $mime_truncation) { ftruncate($airsync_body->data, $mime_truncation); $airsync_body->truncated = '1'; } else { $airsync_body->truncated = '0'; } $eas_message->airsyncbasebody = $airsync_body; } elseif ($body_type_pref == Horde_ActiveSync::BODYPREF_TYPE_HTML) { // Sending non MIME encoded HTML message text. $eas_message->airsyncbasebody = $this->_buildHtmlPart($mbd, $airsync_body); $eas_message->airsyncbaseattachments = $imap_message->getAttachments($version); } elseif ($body_type_pref == Horde_ActiveSync::BODYPREF_TYPE_PLAIN) { // Non MIME encoded plaintext $this->_logger->info(sprintf( '[%s] Sending PLAINTEXT Message.', $this->_procid)); if (!empty($mbd->plain['size'])) { $airsync_body->estimateddatasize = $mbd->plain['size']; $airsync_body->truncated = $mbd->plain['truncated']; $airsync_body->data = $mbd->plain['body']->stream; $airsync_body->type = Horde_ActiveSync::BODYPREF_TYPE_PLAIN; $eas_message->airsyncbasebody = $airsync_body; } $eas_message->airsyncbaseattachments = $imap_message->getAttachments($version); } // It's legal to have both a BODY and a BODYPART, so we must also // check for that. if ($version > Horde_ActiveSync::VERSION_FOURTEEN && !empty($options['bodypartprefs'])) { $body_part = Horde_ActiveSync::messageFactory('AirSyncBaseBodypart'); $eas_message->airsyncbasebodypart = $this->_buildBodyPart($mbd, $options, $body_part); } if ($version > Horde_ActiveSync::VERSION_TWELVEONE) { $flags = array(); $msgFlags = $this->_getMsgFlags(); foreach ($imap_message->getFlags() as $flag) { if (!empty($msgFlags[Horde_String::lower($flag)])) { $flags[] = $msgFlags[Horde_String::lower($flag)]; } } $eas_message->categories = $flags; } } // Body Preview? Note that this is different from BodyPart's preview if ($version >= Horde_ActiveSync::VERSION_FOURTEEN && !empty($options['bodyprefs']['preview'])) { $mbd->plain['body']->rewind(); $eas_message->airsyncbasebody->preview = $mbd->plain['body']->substring(0, $options['bodyprefs']['preview']); } $mbd = null; // Check for special message types. if ($imap_message->isEncrypted()) { $eas_message->messageclass = 'IPM.Note.SMIME'; } elseif ($imap_message->isSigned()) { $eas_message->messageclass = 'IPM.Note.SMIME.MultipartSigned'; } $part = $imap_message->getStructure(); if ($part->getType() == 'multipart/report') { $ids = array_keys($imap_message->contentTypeMap()); reset($ids); $part1_id = next($ids); $part2_id = Horde_Mime::mimeIdArithmetic($part1_id, 'next'); $lines = explode(chr(13), $imap_message->getBodyPart($part2_id, array('decode' => true))); switch ($part->getContentTypeParameter('report-type')) { case 'delivery-status': foreach ($lines as $line) { if (strpos(trim($line), 'Action:') === 0) { switch (trim(substr(trim($line), 7))) { case 'failed': $eas_message->messageclass = 'REPORT.IPM.NOTE.NDR'; break 2; case 'delayed': $eas_message->messageclass = 'REPORT.IPM.NOTE.DELAYED'; break 2; case 'delivered': $eas_message->messageclass = 'REPORT.IPM.NOTE.DR'; break 2; } } } break; case 'disposition-notification': foreach ($lines as $line) { if (strpos(trim($line), 'Disposition:') === 0) { if (strpos($line, 'displayed') !== false) { $eas_message->messageclass = 'REPORT.IPM.NOTE.IPNRN'; } elseif (strpos($line, 'deleted') !== false) { $eas_message->messageclass = 'REPORT.IPM.NOTE.IPNNRN'; } break; } } } } $part = null; // Check for meeting requests and POOMMAIL_FLAG data if ($version >= Horde_ActiveSync::VERSION_TWELVE) { $eas_message->contentclass = 'urn:content-classes:message'; if ($mime_part = $imap_message->hasiCalendar()) { $data = Horde_ActiveSync_Utils::ensureUtf8($mime_part->getContents(), $mime_part->getCharset()); $vCal = new Horde_Icalendar(); if ($vCal->parsevCalendar($data, 'VCALENDAR', $mime_part->getCharset())) { $classes = $vCal->getComponentClasses(); } else { $classes = array(); } if (!empty($classes['horde_icalendar_vevent'])) { try { $method = $vCal->getAttribute('METHOD'); $eas_message->contentclass = 'urn:content-classes:calendarmessage'; } catch (Horde_Icalendar_Exception $e) { } switch ($method) { case 'REQUEST': case 'PUBLISH': $eas_message->messageclass = 'IPM.Schedule.Meeting.Request'; $mtg = Horde_ActiveSync::messageFactory('MeetingRequest'); $mtg->fromvEvent($vCal); $eas_message->meetingrequest = $mtg; break; case 'REPLY': try { $reply_status = $this->_getiTipStatus($vCal); switch ($reply_status) { case 'ACCEPTED': $eas_message->messageclass = 'IPM.Schedule.Meeting.Resp.Pos'; break; case 'DECLINED': $eas_message->messageclass = 'IPM.Schedule.Meeting.Resp.Neg'; break; case 'TENTATIVE': $eas_message->messageclass = 'IPM.Schedule.Meeting.Resp.Tent'; } $mtg = Horde_ActiveSync::messageFactory('MeetingRequest'); $mtg->fromvEvent($vCal); $eas_message->meetingrequest = $mtg; } catch (Horde_ActiveSync_Exception $e) { $this->_logger->err($e->getMessage()); } } } } if ($imap_message->getFlag(Horde_Imap_Client::FLAG_FLAGGED)) { $poommail_flag = Horde_ActiveSync::messageFactory('Flag'); $poommail_flag->subject = $imap_message->getSubject(); $poommail_flag->flagstatus = Horde_ActiveSync_Message_Flag::FLAG_STATUS_ACTIVE; $poommail_flag->flagtype = Horde_Imap_Client::FLAG_FLAGGED; $eas_message->flag = $poommail_flag; } } if ($version >= Horde_ActiveSync::VERSION_FOURTEEN) { $eas_message->messageid = $imap_message->getHeaders()->getValue('Message-ID'); $eas_message->forwarded = $imap_message->getFlag(Horde_Imap_Client::FLAG_FORWARDED); $eas_message->answered = $imap_message->getFlag(Horde_Imap_Client::FLAG_ANSWERED); } $imap_message = null; return $eas_message; } /** * Build the HTML body and populate the appropriate message object. * * @param Horde_ActiveSync_Imap_MessageBodyData $mbd The body data array. * @param array $options The options array. * @param Horde_ActiveSync_Message_AirSyncBaseBody $message * The body or bodypart object. */ protected function _buildHtmlPart( Horde_ActiveSync_Imap_MessageBodyData $mbd, Horde_ActiveSync_Message_AirSyncBaseBody $message) { // Sending non MIME encoded HTML message text. $this->_logger->info(sprintf( '[%s] Sending HTML Message.', $this->_procid)); if (!$mbd->html) { $message->type = Horde_ActiveSync::BODYPREF_TYPE_PLAIN; $mbd->html = array( 'body' => $mbd->plain['body'], 'estimated_size' => $mbd->plain['size'], 'truncated' => $mbd->plain['truncated'] ); } else { $message->type = Horde_ActiveSync::BODYPREF_TYPE_HTML; } if (!empty($mbd->html['estimated_size'])) { $message->estimateddatasize = $mbd->html['estimated_size']; $message->truncated = $mbd->html['truncated']; $message->data = $mbd->html['body']->stream; } return $message; } protected function _buildBodyPart( Horde_ActiveSync_Imap_MessageBodyData $mbd, array $options, Horde_ActiveSync_Message_AirSyncBaseBodypart $message) { $this->_logger->info(sprintf( '[%s] Preparing BODYPART data.', $this->_procid) ); $message->status = Horde_ActiveSync_Message_AirSyncBaseBodypart::STATUS_SUCCESS; if (!empty($options['bodypartprefs']['preview']) && $mbd->plain) { $mbd->plain['body']->rewind(); $message->preview = $mbd->plain['body']->substring(0, $options['bodypartprefs']['preview']); } $message->data = $mbd->bodyPart['body']->stream; $message->truncated = $mbd->bodyPart['truncated']; return $message; } /** * Prefix the default namespace to mailbox name if needed. * * @param string $name The mailbox name. * * @return string The mailbox name with the default namespace added, if * needed. */ protected function _prependNamespace($name) { $def_ns = $this->_defaultNamespace(); if (!is_null($def_ns)) { $empty_ns = $this->_getNamespace(''); if (is_null($empty_ns) || $def_ns['name'] != $empty_ns['name']) { $name = $def_ns['name'] . $name; } } return $name; } /** * Return the default namespace. * * @return array The namespace data. */ protected function _defaultNamespace() { if (is_null($this->_defaultNamespace)) { foreach ($this->_getNamespacelist() as $ns) { if ($ns['type'] == Horde_Imap_Client::NS_PERSONAL) { $this->_defaultNamespace = $ns; break; } } } return $this->_defaultNamespace; } /** * Return the list of configured namespaces on the IMAP server. * * @return array */ protected function _getNamespacelist() { try { return $this->_getImapOb()->getNamespaces(); } catch (Horde_Imap_Client_Exception $e) { return array(); } } protected function _getNamespace($path) { $ns = $this->_getNamespacelist(); foreach ($ns as $key => $val) { $mbox = $path . $val['delimiter']; if (strlen($key) && (strpos($mbox, $key) === 0)) { return $val; } } return (isset($ns['']) && ($val['type'] == Horde_Imap_Client::NS_PERSONAL)) ? $ns[''] : null; } /** * Perform an IMAP search based on a SEARCH request. * * @param array $query The search query. * * @return array The results array containing an array of hashes: * 'uniqueid' => [The unique identifier of the result] * 'searchfolderid' => [The mailbox name that this result comes from] * * @throws Horde_ActiveSync_Exception */ protected function _doQuery(array $query) { $imap_query = new Horde_Imap_Client_Search_Query(); $imap_query->charset('UTF-8', false); $mboxes = array(); $results = array(); foreach ($query as $q) { switch ($q['op']) { case Horde_ActiveSync_Request_Search::SEARCH_AND: return $this->_doQuery(array($q['value']), $range); default: foreach ($q as $key => $value) { switch ($key) { case 'FolderType': if ($value != Horde_ActiveSync::CLASS_EMAIL) { throw new Horde_ActiveSync_Exception('Only Email folders are supported.'); } break; case 'serverid': $mboxes[] = new Horde_Imap_Client_Mailbox($value); break; case Horde_ActiveSync_Message_Mail::POOMMAIL_DATERECEIVED: if ($q['op'] == Horde_ActiveSync_Request_Search::SEARCH_GREATERTHAN) { $query_range = Horde_Imap_Client_Search_Query::DATE_SINCE; } elseif ($q['op'] == Horde_ActiveSync_Request_Search::SEARCH_LESSTHAN) { $query_range = Horde_Imap_Client_Search_Query::DATE_BEFORE; } else { $query_range = Horde_Imap_Client_Search_Query::DATE_ON; } $imap_query->dateSearch($value, $query_range); break; case Horde_ActiveSync_Request_Search::SEARCH_FREETEXT: $imap_query->text($value, false); break; case 'subquery': $imap_query->andSearch(array($this->_buildSubQuery($value))); } } } } if (empty($mboxes)) { foreach ($this->getMailboxes() as $mailbox) { $mboxes[] = $mailbox['ob']; } } foreach ($mboxes as $mbox) { try { $search_res = $this->_getImapOb()->search( $mbox, $imap_query, array( 'results' => array(Horde_Imap_Client::SEARCH_RESULTS_MATCH, Horde_Imap_Client::SEARCH_RESULTS_SAVE, Horde_Imap_Client::SEARCH_RESULTS_COUNT), 'sort' => array(Horde_Imap_Client::SORT_REVERSE, Horde_Imap_Client::SORT_ARRIVAL)) ); } catch (Horde_Imap_Client_Exception $e) { throw new Horde_ActiveSync_Exception($e); } if ($search_res['count'] == 0) { continue; } $ids = $search_res['match']->ids; foreach ($ids as $id) { $results[] = array('uniqueid' => $mbox->utf8 . ':' . $id, 'searchfolderid' => $mbox->utf8); } if (!empty($range)) { preg_match('/(.*)\-(.*)/', $range, $matches); $return_count = $matches[2] - $matches[1]; $results = array_slice($results, $matches[1], $return_count + 1, true); } } return $results; } /** * Map Importance header values to EAS importance values. * * @param string $importance The importance [high|normal|low]. * * @return integer The EAS importance value [0|1|2]. */ protected function _getEASImportance($importance) { switch (Horde_String::lower($importance)) { case '1': case 'high': return 2; case '5': case 'low': return 0; case 'normal': case '3': default: return 1; } } /** * Helper to obtain a valid IMAP client. Can't inject it since the user * is not yet authenticated at the time of object creation. * * @return Horde_Imap_Client_Base * @throws Horde_ActiveSync_Exception */ protected function _getImapOb() { try { return $this->_imap->getImapOb(); } catch (Horde_ActiveSync_Exception $e) { throw new Horde_Exception_AuthenticationFailure('EMERGENCY - Unable to obtain the IMAP Client'); } } /** * Return the attendee participation status. * * @param Horde_Icalendar $vCal The vCalendar component. * * @param Horde_Icalendar * @throws Horde_ActiveSync_Exception */ protected function _getiTipStatus($vCal) { foreach ($vCal->getComponents() as $component) { switch ($component->getType()) { case 'vEvent': try { $atparams = $component->getAttribute('ATTENDEE', true); } catch (Horde_Icalendar_Exception $e) { throw new Horde_ActiveSync_Exception($e); } if (!is_array($atparams)) { throw new Horde_Icalendar_Exception('Unexpected value'); } return $atparams[0]['PARTSTAT']; } } } /** * * @param Horde_Imap_Client_Mailbox $mbox The mailbox * @param array $uids An array of message uids * @param array $options An options array * - headers: (boolean) Fetch header text if true. * DEFAULT: false (Do not fetch header text). * - structure: (boolean) Fetch message structure. * DEFAULT: true (Fetch message structure). * - flags: (boolean) Fetch messagge flags. * DEFAULT: true (Fetch message flags). * - envelope: (boolen) Fetch the envelope data. * DEFAULT: false (Do not fetch envelope). @since 2.4.0 * * @return Horde_Imap_Fetch_Results The results. * @throws Horde_ActiveSync_Exception */ protected function _getMailMessages( Horde_Imap_Client_Mailbox $mbox, array $uids, array $options = array()) { $options = array_merge( array( 'headers' => false, 'structure' => true, 'flags' => true, 'envelope' => false), $options ); $query = new Horde_Imap_Client_Fetch_Query(); if ($options['structure']) { $query->structure(); } if ($options['flags']) { $query->flags(); } if ($options['envelope']) { $query->envelope(); } if (!empty($options['headers'])) { $query->headerText(array('peek' => true)); } try { return $this->_getImapOb()->fetch( $mbox, $query, array('ids' => new Horde_Imap_Client_Ids($uids), 'exists' => true) ); } catch (Horde_Imap_Client_Exception $e) { $this->_logger->err(sprintf( '[%s] Unable to fetch message: %s', $this->_procid, $e->getMessage())); throw new Horde_ActiveSync_Exception($e); } } /** * Check existence of a mailbox. * * @param string $mbox The mailbox name. * * @return boolean */ protected function _mailboxExists($mbox) { $mailboxes = $this->_imap->getMailboxes(true); foreach ($mailboxes as $mailbox) { if ($mailbox['ob']->utf8 == $mbox) { return true; } } return false; } protected function _getMsgFlags() { // @todo Horde_ActiveSync 3.0 remove method_exists check. if (method_exists($this->_imap, 'getMsgFlags')) { return $this->_imap->getMsgFlags(); } return array(); } } Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Imap/Message.php0000664000076500000240000006215712654565405020600 0ustar * @package ActiveSync */ /** * This class provides all functionality related to parsing and working with * a single IMAP email message when using Horde_Imap_Client. * * Some Mime parsing code taken from Imp_Contents. * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2012-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync * * @property-read Horde_Imap_Client_Data_Envelope $envelope * The message envelope. * @property-read array $flags The message flags. * @property-read integer $uid The message uid. * @property-read Horde_ActiveSync_Mime $basePart The base message part. */ class Horde_ActiveSync_Imap_Message { /** * Message data. * * @var Horde_Imap_Client_Fetch_Data */ protected $_data; /** * Message structure. * * @var Horde_ActiveSync_Mime */ protected $_basePart; /** * The imap client. * * @var Horde_Imap_Client_Base */ protected $_imap; /** * Cache if the last body part was encoded or not. * * @var boolean */ protected $_lastBodyPartDecode = null; /** * Flag to indicate if this message contains attachments. * * @var boolean */ protected $_hasAttachments = null; /** * Constructor * * @param Horde_Imap_Client_Base $imap The imap client object. * @param Horde_Imap_Client_Mailbox $mbox The mailbox object. * @param Horde_Imap_Client_Data_Fetch $data The data returned from a FETCH * must contain at least uid, * structure and flags. */ public function __construct( Horde_Imap_Client_Base $imap, Horde_Imap_Client_Mailbox $mbox, Horde_Imap_Client_Data_Fetch $data) { $this->_imap = $imap; $this->_mbox = $mbox; $this->_data = $data; } public function __destruct() { $this->_data = null; $this->_basePart = null; } /** * Accessor * * @param string $property The property. * * @return mixed */ public function &__get($property) { switch ($property) { case 'envelope': $e = $this->_data->getEnvelope(); return $e; case 'flags': $f = $this->_data->getFlags(); return $f; case 'uid': $u = $this->_data->getUid(); return $u; case 'basePart': if (empty($this->_basePart)) { $this->_basePart = new Horde_ActiveSync_Mime($this->_data->getStructure()); } return $this->_basePart; } throw new InvalidArgumentException(sprintf('The property %s of Horde_ActiveSync_Imap_Message does not exist', $property)); } /** * Return this message's base part headers. * * @return Horde_Mime_Header The message headers. */ public function getHeaders() { return $this->_data->getHeaderText(0, Horde_Imap_Client_Data_Fetch::HEADER_PARSE); } /** * Return nicely formatted text representing the headers to display with * in-line forwarded messages. * * @return string */ public function getForwardHeaders() { $tmp = array(); $h = $this->getHeaders(); if (($ob = $h->getValue('date'))) { $tmp[Horde_ActiveSync_Translation::t('Date')] = $ob; } if (($ob = strval($h->getOb('from')))) { $tmp[Horde_ActiveSync_Translation::t('From')] = $ob; } if (($ob = strval($h->getOb('reply-to')))) { $tmp[Horde_ActiveSync_Translation::t('Reply-To')] = $ob; } if (($ob = $h->getValue('subject'))) { $tmp[Horde_ActiveSync_Translation::t('Subject')] = $ob; } if (($ob = strval($h->getOb('to')))) { $tmp[Horde_ActiveSync_Translation::t('To')] = $ob; } if (($ob = strval($h->getOb('cc')))) { $tmp[Horde_ActiveSync_Translation::t('Cc')] = $ob; } $max = max(array_map(array('Horde_String', 'length'), array_keys($tmp))) + 2; $text = ''; foreach ($tmp as $key => $val) { $text .= Horde_String::pad($key . ': ', $max, ' ', STR_PAD_LEFT) . $val . "\n"; } return $text; } /** * Return the full message text. * * @param boolean $stream Return data as a stream? * * @return mixed A string or stream resource. * @throws Horde_ActiveSync_Exception */ public function getFullMsg($stream = false) { // First see if we already have it. if ($stream) { $full = new Horde_Stream_Existing(array('stream' => $this->_data->getFullMsg($stream))); $length = $full->length(); if (!$length) { $full->close(); } } else { $full = $this->_data->getFullMsg(false); $length = strlen($full); } if (!$length) { $query = new Horde_Imap_Client_Fetch_Query(); $query->fullText(array('peek' => true)); try { $fetch_ret = $this->_imap->fetch( $this->_mbox, $query, array('ids' => new Horde_Imap_Client_Ids(array($this->uid))) ); } catch (Horde_Imap_Client_Exception $e) { throw new Horde_ActiveSync_Exception($e); } $data = $fetch_ret[$this->uid]; $full = $data->getFullMsg($stream); } return $full; } /** * Return the message's base Mime part. * * @return Horde_Mime_Part */ public function getStructure() { return $this->basePart->base; } /** * Returns the main text body of the message suitable for sending over * EAS response. * * @param array $options An options array containgin: * - bodyprefs: (array) Bodypref settings * DEFAULT: none (No bodyprefs used). * - mimesupport: (integer) Indicates if MIME is supported or not. * Possible values: 0 - Not supported 1 - Only S/MIME or * 2 - All MIME. * DEFAULT: 0 (No MIME support) * - protocolversion: (float) The EAS protocol we are supporting. * DEFAULT 2.5 * * @return array An array of one or both of 'plain' and 'html' content. * * @throws Horde_ActiveSync_Exception, Horde_Exception_NotFound */ public function getMessageBodyData(array $options = array()) { return $this->getMessageBodyDataObject($options)->toArray(); } /** * Returns the main text body of the message suitable for sending over * EAS response. * * @param array $options An options array containgin: * - bodyprefs: (array) Bodypref settings * DEFAULT: none (No bodyprefs used). * - mimesupport: (integer) Indicates if MIME is supported or not. * Possible values: 0 - Not supported 1 - Only S/MIME or * 2 - All MIME. * DEFAULT: 0 (No MIME support) * - protocolversion: (float) The EAS protocol we are supporting. * DEFAULT 2.5 * * @return Horde_ActiveSync_Imap_MessageBodyData The result. * * @throws Horde_ActiveSync_Exception, Horde_Exception_NotFound */ public function getMessageBodyDataObject(array $options = array()) { return new Horde_ActiveSync_Imap_MessageBodyData( array( 'imap' => $this->_imap, 'mbox' => $this->_mbox, 'uid' => $this->uid, 'mime' => $this->basePart), $options ); } /** * Return an array of Horde_ActiveSync_Message_Attachment objects for * the current message. * * @param float $version The EAS protocol version this is for. * * @return array An array of Horde_ActiveSync_Message_Attachment objects. */ public function getAttachments($version) { $ret = array(); $iterator = new Horde_ActiveSync_Mime_Iterator($this->_basePart->base); foreach ($iterator as $part) { $type = $part->getType(); $id = $part->getMimeId(); if ($this->isAttachment($id, $type)) { if ($type != 'application/ms-tnef' || (!$mime_part = $this->_decodeTnefData($id))) { $mime_part = $this->getMimePart($id, array('nocontents' => true)); } $ret[] = $this->_buildEasAttachmentFromMime($id, $mime_part, $version); $mime_part = null; } } return $ret; } /** * Build an appropriate attachment object from the given mime part. * * @param integer $id The mime id for the part * @param Horde_Mime_Part $mime_part The mime part. * @param float $version The EAS version. * * @return Horde_ActiveSync_Message_AirSyncBaseAttachment | * Horde_ActiveSync_Message_Attachment */ protected function _buildEasAttachmentFromMime($id, Horde_Mime_Part $mime_part, $version) { if ($version > Horde_ActiveSync::VERSION_TWOFIVE) { $atc = Horde_ActiveSync::messageFactory('AirSyncBaseAttachment'); $atc->contentid = $mime_part->getContentId(); $atc->isinline = $mime_part->getDisposition() == 'inline'; } else { $atc = Horde_ActiveSync::messageFactory('Attachment'); $atc->attoid = $mime_part->getContentId(); } $atc->attsize = intval($mime_part->getBytes(true)); $atc->attname = $this->_mbox . ':' . $this->uid . ':' . $id; $atc->displayname = Horde_String::convertCharset( $this->getPartName($mime_part, true), $this->basePart->getHeaderCharset(), 'UTF-8', true); $atc->attmethod = in_array($mime_part->getType(), array('message/rfc822', 'message/disposition-notification')) ? Horde_ActiveSync_Message_AirSyncBaseAttachment::ATT_TYPE_EMBEDDED : Horde_ActiveSync_Message_AirSyncBaseAttachment::ATT_TYPE_NORMAL; return $atc; } /** * Convert a TNEF attachment into a multipart/mixed part. * * @param integer|Horde_Mime_part $data Either a mime part id or a * Horde_Mime_Part object containing * the TNEF attachment. * * @return Horde_Mime_Part The multipart/mixed MIME part containing any * attachment data we can decode. */ protected function _decodeTnefData($data) { $wrapper = new Horde_Mime_Part(); $wrapper->setType('multipart/mixed'); if (!($data instanceof Horde_Mime_Part)) { $mime_part = $this->getMimePart($data); } else { $mime_part = $data; } $tnef_parser = Horde_Compress::factory('Tnef'); try { $tnef_data = $tnef_parser->decompress($mime_part->getContents()); } catch (Horde_Compress_Exception $e) { return false; } if (!count($tnef_data)) { return false; } reset($tnef_data); while (list(,$data) = each($tnef_data)) { $tmp_part = new Horde_Mime_Part(); $tmp_part->setName($data['name']); $tmp_part->setDescription($data['name']); $tmp_part->setContents($data['stream']); $type = $data['type'] . '/' . $data['subtype']; if (in_array($type, array('application/octet-stream', 'application/base64'))) { $type = Horde_Mime_Magic::filenameToMIME($data['name']); } $tmp_part->setType($type); $wrapper->addPart($tmp_part); } return $wrapper; } /** * Return an array of mime parts for each message attachment. * * @return array An array of Horde_Mime_Part objects. */ public function getAttachmentsMimeParts() { $mime_parts = array(); $map = $this->basePart->contentTypeMap(); foreach ($map as $id => $type) { if ($this->isAttachment($id, $type)) { $mpart = $this->getMimePart($id); if ($mpart->getType() == 'text/calendar') { $mpart->setDisposition('inline'); } if ($mpart->getType() != 'application/ms-tnef' || ($mpart->getType() == 'application/ms-tnef' && !$part = $this->_decodeTnefData($mpart))) { $part = $mpart; } $mime_parts[] = $part; $mpart = null; $part = null; } } return $mime_parts; } /** * Fetch a part of a MIME message. * * @param integer $id The MIME index of the part requested. * @param array $options Additional options: * - length: (integer) If set, only download this many bytes of the * bodypart from the server. * DEFAULT: All data is retrieved. * - nocontents: (boolean) If true, don't add the contents to the part * DEFAULT: Contents are added to the part * * @return Horde_Mime_Part The raw MIME part asked for. */ public function getMimePart($id, array $options = array()) { $part = $this->basePart->getPart($id); if ($part && (strcasecmp($part->getCharset(), 'ISO-8859-1') === 0)) { $part->setCharset('windows-1252'); } if (!empty($id) && !is_null($part) && substr($id, -2) != '.0' && empty($options['nocontents']) && !$part->getContents(array('stream' => true))) { $body = $this->getBodyPart( $id, array( 'decode' => true, 'length' => empty($options['length']) ? null : $options['length'], 'stream' => true) ); $part->setContents($body, array('encoding' => $this->_lastBodyPartDecode, 'usestream' => true)); } return $part; } /** * Return the descriptive part label, making sure it is not empty. * * @param Horde_Mime_Part $part The MIME Part object. * @param boolean $use_descrip Use description? If false, uses name. * * @return string The part label (non-empty). */ public function getPartName(Horde_Mime_Part $part, $use_descrip = false) { $name = $use_descrip ? $part->getDescription(true) : $part->getName(true); if ($name) { return $name; } switch ($ptype = $part->getPrimaryType()) { case 'multipart': if (($part->getSubType() == 'related') && ($view_id = $part->getMetaData('viewable_part')) && ($viewable = $this->getMimePart($view_id, array('nocontents' => true)))) { return $this->getPartName($viewable, $use_descrip); } /* Fall-through. */ case 'application': case 'model': $ptype = $part->getSubType(); break; } switch ($ptype) { case 'audio': return Horde_ActiveSync_Translation::t('Audio part'); case 'image': return Horde_ActiveSync_Translation::t('Image part'); case 'message': case Horde_Mime_Part::UNKNOWN: return Horde_ActiveSync_Translation::t('Message part'); case 'multipart': return Horde_ActiveSync_Translation::t('Multipart part'); case 'text': return Horde_ActiveSync_Translation::t('Text part'); case 'video': return Horde_ActiveSync_Translation::t('Video part'); default: // Attempt to translate this type, if possible. Odds are that // it won't appear in the dictionary though. return sprintf(Horde_ActiveSync_Translation::t('%s part'), _(Horde_String::ucfirst($ptype))); } } /** * Gets the raw text for one section of the message. * * @param integer $id The ID of the MIME part. * @param array $options Additional options: * - decode: (boolean) Attempt to decode the bodypart on the remote * server. If successful, sets self::$_lastBodyPartDecode to * the content-type of the decoded data. * DEFAULT: No * - length: (integer) If set, only download this many bytes of the * bodypart from the server. * DEFAULT: All data is retrieved. * - mimeheaders: (boolean) Include the MIME headers also? * DEFAULT: No * - stream: (boolean) If true, return a stream. * DEFAULT: No * * @return mixed The text of the part or a stream resource if 'stream' * is true. * @todo Simplify by removing 'mimeheaders' parameter (not used). */ public function getBodyPart($id, $options) { $options = array_merge( array( 'decode' => false, 'mimeheaders' => false, 'stream' => false), $options); $this->_lastBodyPartDecode = null; $query = new Horde_Imap_Client_Fetch_Query(); if (!isset($options['length']) || !empty($options['length'])) { $bodypart_params = array( 'decode' => true, 'peek' => true ); if (isset($options['length'])) { $bodypart_params['start'] = 0; $bodypart_params['length'] = $options['length']; } $query->bodyPart($id, $bodypart_params); } if (!empty($options['mimeheaders'])) { $query->mimeHeader($id, array( 'peek' => true )); } $fetch_res = $this->_imap->fetch( $this->_mbox, $query, array('ids' => new Horde_Imap_Client_Ids(array($this->uid))) ); if (empty($options['mimeheaders'])) { $this->_lastBodyPartDecode = $fetch_res[$this->uid]->getBodyPartDecode($id); return $fetch_res[$this->uid]->getBodyPart($id, $options['stream']); } elseif (empty($options['stream'])) { return $fetch_res[$this->uid]->getMimeHeader($id) . $fetch_res[$this->uid]->getBodyPart($id); } else { $swrapper = new Horde_Support_CombineStream( array( $fetch_res[$this->uid]->getMimeHeader($id, Horde_Imap_Client_Data_Fetch::HEADER_STREAM), $fetch_res[$this->uid]->getBodyPart($id, true)) ); return $swrapper->fopen(); } } /** * Return the To addresses from this message. * * @return array An array containing arrays of 'to' and 'displayto' * addresses. */ public function getToAddresses() { $to = $this->envelope->to; $dtos = $tos = array(); foreach ($to->raw_addresses as $e) { $tos[] = $e->bare_address; $dtos[] = $e->label; } return array('to' => $tos, 'displayto' => $dtos); } /** * Return the CC addresses for this message. * * @return string The Cc address string. * @throws Horde_ActiveSync_Exception @since 2.27.0 */ public function getCc() { try { $cc = new Horde_Mail_Rfc822_List($this->envelope->cc->addresses); } catch (Horde_Mail_Exception $e) { throw new Horde_ActiveSync_Exception($e); } return $cc->writeAddress(); } /** * Return the ReplyTo Address * * @return string * @throws Horde_ActiveSync_Exception @since 2.27.0 */ public function getReplyTo() { $r = $this->envelope->reply_to->addresses; try { $a = new Horde_Mail_Rfc822_Address(current($r)); } catch (Horde_Mail_Exception $e) { throw new Horde_ActiveSync_Exception($e); } return $a->writeAddress(false); } /** * Return the message's From: address. * * @return string The From address of this message. * @throws Horde_ActiveSync_Exception @since 2.27.0 */ public function getFromAddress() { $from = $this->envelope->from->addresses; try { $a = new Horde_Mail_Rfc822_Address(current($from)); } catch (Horde_ActiveSync_Exception $e) { throw new Horde_ActiveSync_Exception($e); } return $a->writeAddress(false); } /** * Return the message subject. * * @return string The subject. */ public function getSubject() { return $this->envelope->subject; } /** * Return the message date. * * @return Horde_Date The messages's envelope date. */ public function getDate() { return new Horde_Date((string)$this->envelope->date); } /** * Get a message flag * * @param string $flag The flag to search for. * * @return boolean */ public function getFlag($flag) { return (array_search($flag, $this->flags) !== false) ? 1 : 0; } /** * Return all message flags. * * @return array An array of message flags. * @since 2.17.0 */ public function getFlags() { return $this->flags; } /** * Return this message's content map * * @return array The content map, with mime ids as keys and content type * as values. */ public function contentTypeMap() { return $this->basePart->contentTypeMap(); } /** * Determines if a MIME type is an attachment. * For our purposes, an attachment is any MIME part that can be * downloaded by itself (i.e. all the data needed to view the part is * contained within the download data). * * @param string $id The MIME Id for the part we are checking. * @param string $mime_type The MIME type. * * @return boolean True if an attachment. * @deprecated Will be removed in 3.0 (Only used in self::hasAttachments call). */ public function isAttachment($id, $mime_type) { return $this->basePart->isAttachment($id, $mime_type); } /** * Return the MIME part of the iCalendar attachment, if available. * * @return mixed The mime part, if present, false otherwise. */ public function hasiCalendar() { if ($id = $this->basePart->hasiCalendar()) { // May already have downloaded the part. $part = $this->basePart->base->getPart($id); if (!$part->getContents(array('stream' => true))) { return $this->getMimePart($id); } return $part; } return false; } /** * Return the hasAttachments flag * * @return boolean */ public function hasAttachments() { return $this->basePart->hasAttachments(); } /** * Return the S/MIME signature status of this message (RFC2633) * * @param Horde_Mime_Part $message A mime part to check. If omitted, use * self::$_message. * * @return boolean True if message is S/MIME signed, false otherwise. */ public function isSigned(Horde_Mime_Part $message = null) { if (!empty($message)) { $message = new Horde_ActiveSync_Mime($message); return $message->isSigned(); } return $this->basePart->isSigned(); } /** * Return the S/MIME encryption status of this message (RFC2633) * * @param Horde_Mime_Part $message A mime part to check. If omitted, use * self::$_message. * * @return boolean True if message is S/MIME signed or encrypted, * false otherwise. */ public function isEncrypted(Horde_Mime_Part $message = null) { if (!empty($message)) { $message = new Horde_ActiveSync_Mime($message); return $message->isEncrypted(); } return $this->basePart->isEncrypted(); } } Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Imap/MessageBodyData.php0000664000076500000240000005730012654565405022202 0ustar * @package ActiveSync */ /** * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2012-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync * * @property array html An array describing the text/html part with: * - charset: (string) The charset of the text. * - body: (Horde_Stream) The body text in a stream. * - truncated: (boolean) True if text was truncated. * - size: (integer) The original part size, in bytes. * * @property array plain An array describing the text/plain part with: * - charset: (string) The charset of the text. * - body: (Horde_Stream) The body text in a stream. * - truncated: (boolean) True if text was truncated. * - size: (integer) The original part size, in bytes. * * @property array bodyPart An array describing the BODYPART requested. BODYPART * is typically a truncated text/html representation of * part of the message. Very few clients request this. * - charset: (string) The charset of the text. * - body: (Horde_Stream) The body text in a stream. * - truncated: (boolean) True if text was truncated. * - size: (integer) The original part size, in bytes. */ class Horde_ActiveSync_Imap_MessageBodyData { /** * * @var Horde_ActiveSync_Imap_Adapter */ protected $_imap; /** * @var Horde_ActiveSync_Mime */ protected $_basePart; /** * * @var array */ protected $_options; /** * * @var float */ protected $_version; /** * * @var Horde_Imap_Client_Mailbox */ protected $_mbox; /** * * @var integer */ protected $_uid; /** * * @var arary */ protected $_plain; /** * * @var array */ protected $_html; /** * * @var array */ protected $_bodyPart; /** * Flag to indicate self::$_Plain is validated. * * @var boolean */ protected $_validatedPlain; /** * Flag to indicate self::$_html is validated. * * @var boolean */ protected $_validatedHtml; /** * Const'r * * @param array $params Parameters: * - imap: (Horde_Imap_Client_Base) The IMAP client. * - mbox: (Horde_Imap_Client_Mailbox) The mailbox. * - uid: (integer) The message UID. * - mime: (Horde_ActiveSync_Mime) The MIME object. * * @param array $options The options array. */ public function __construct(array $params, array $options) { stream_filter_register('horde_eol', 'Horde_Stream_Filter_Eol'); $this->_imap = $params['imap']; $this->_basePart = $params['mime']; $this->_mbox = $params['mbox']; $this->_uid = $params['uid']; $this->_options = $options; $this->_version = empty($options['protocolversion']) ? Horde_ActiveSync::VERSION_TWOFIVE : $options['protocolversion']; $this->_getParts(); } public function __destruct() { $this->_basePart = null; $this->_imap = null; if (!empty($this->_plain) && ($this->_plain['body'] instanceof Horde_Stream)) { $this->_plain['body'] = null; } if (!empty($this->_html) && ($this->_html['body'] instanceof Horde_Stream)) { $this->_html['body'] = null; } } public function &__get($property) { switch ($property) { case 'plain': $body = $this->plainBody(); return $body; case 'html': $body = $this->htmlBody(); return $body; case 'bodyPart': $body = $this->bodyPartBody(); return $body; default: throw new InvalidArgumentException("Unknown property: $property"); } } public function __set($property, $value) { switch ($property) { case 'html': $this->_html = $value; break; default: throw new InvalidArgumentException("$property can not be set."); } } /** * Return the BODYTYPE to return to the client. Takes BODYPREF and available * parts into account. * * @param boolean $save_bandwith If true, saves bandwidth usage by * favoring HTML over MIME BODYTYPE if able. * * @return integer A Horde_ActiveSync::BODYPREF_TYPE_* constant. */ public function getBodyTypePreference($save_bandwidth = false) { // Apparently some clients don't send the MIME_SUPPORT field (thus // defaulting it to MIME_SUPPORT_NONE), but still request // BODYPREF_TYPE_MIME. Failure to do this results in NO data being // sent to the client, so we ignore the MIME_SUPPORT requirement and // assume it is implied if it is requested in a BODYPREF element. $bodyprefs = $this->_options['bodyprefs']; if ($save_bandwidth) { return !empty($bodyprefs[Horde_ActiveSync::BODYPREF_TYPE_HTML]) && !empty($this->_html) ? Horde_ActiveSync::BODYPREF_TYPE_HTML : (!empty($bodyprefs[Horde_ActiveSync::BODYPREF_TYPE_MIME]) ? Horde_ActiveSync::BODYPREF_TYPE_MIME : Horde_ActiveSync::BODYPREF_TYPE_PLAIN); } // Prefer high bandwidth, full MIME. return !empty($bodyprefs[Horde_ActiveSync::BODYPREF_TYPE_MIME]) ? Horde_ActiveSync::BODYPREF_TYPE_MIME : (!empty($bodyprefs[Horde_ActiveSync::BODYPREF_TYPE_HTML]) && !empty($this->_html) ? Horde_ActiveSync::BODYPREF_TYPE_HTML : Horde_ActiveSync::BODYPREF_TYPE_PLAIN); } /** * Determine which parts we need, and fetches them from the IMAP client. * Takes into account the available parts and the BODYPREF/BODYPARTPREF * options. */ protected function _getParts() { // Look for the parts we need. We try to detect and fetch only the parts // we need, while ensuring we have something to return. So, e.g., if we // don't have BODYPREF_TYPE_HTML, we only request plain text, but if we // can't find plain text but we have a html body, fetch that anyway. // // If this is any type of Report (like a NDR) we can't use findBody // since some MTAs generate MDRs with no explicit mime type in the // human readable portion (the first part). We assume the MDR contains // three parts as specified in the RFC: (1) A human readable part, (2) // A machine parsable body Machine parsable body part // [message/disposition-notification] and (3) The (optional) original // message [message/rfc822] switch ($this->_basePart->getType()) { case 'message/disposition-notification': // OL may send this without an appropriate multipart/report wrapper. // Not sure what to do about this yet. Probably parse the machine // part and write out some basic text? break; case 'multipart/report': $iterator = $this->_basePart->partIterator(false); $iterator->rewind(); if (!$curr = $iterator->current()) { break; } $text_id = $curr->getMimeId(); $html_id = null; break; default: $text_id = $this->_basePart->findBody('plain'); $html_id = $this->_basePart->findBody('html'); } // Deduce which part(s) we need to request. $want_html_text = $this->_wantHtml(); $want_plain_text = $this->_wantPlainText($html_id, $want_html_text); $want_html_as_plain = false; if (!empty($text_id) && $want_plain_text) { $text_body_part = $this->_basePart->getPart($text_id); } elseif ($want_plain_text && !empty($html_id) && empty($this->_options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_MIME])) { $want_html_text = true; $want_html_as_plain = true; } if (!empty($html_id) && $want_html_text) { $html_body_part = $this->_basePart->getPart($html_id); } // Make sure we have truncation if needed. if (empty($this->_options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_PLAIN]) && !empty($this->_options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_HTML]) && $want_plain_text && $want_html_text) { // We only have HTML truncation data, requested HTML body but only // have plaintext. $this->_options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_PLAIN] = $this->_options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_HTML]; } // Fetch the data from the IMAP client. $data = $this->_fetchData(array('html_id' => $html_id, 'text_id' => $text_id)); if (!empty($text_id) && $want_plain_text) { $this->_plain = $this->_getPlainPart($data, $text_body_part); } if (!empty($html_id) && $want_html_text) { $results = $this->_getHtmlPart($data, $html_body_part, $want_html_as_plain); $this->_html = !empty($results['html']) ? $results['html'] : null; $this->_plain = !empty($results['plain']) ? $results['plain'] : null; } if (!empty($this->_options['bodypartprefs'])) { $this->_bodyPart = $this->_getBodyPart( $data, !empty($html_id) ? $html_body_part : $text_body_part, empty($html_id) ); } $text_body_part = null; $html_body_part = null; } /** * Return if we want HTML data. * * @return boolean True if HTML data is needed. */ protected function _wantHtml() { return $this->_version >= Horde_ActiveSync::VERSION_TWELVE && (!empty($this->_options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_HTML]) || !empty($this->_options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_MIME]) || !empty($this->_options['bodypartprefs'])); } /** * Return if we want plain text data. * * @param string $html_id The MIME id of any HTML part, if available. * Used to detect if we need to fetch the plain * part if we are requesting HTML, but only have * plain. * @param boolean $want_html True if the client wants HTML. * * @return boolean True if plain data is needed. */ protected function _wantPlainText($html_id, $want_html) { return $this->_version == Horde_ActiveSync::VERSION_TWOFIVE || empty($this->_options['bodyprefs']) || !empty($this->_options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_PLAIN]) || !empty($this->_options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_RTF]) || !empty($this->_options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_MIME]) || ($want_html && empty($html_id)); } /** * Fetch data from the IMAP client. * * @param array $params Parameter array. * - html_id (string) The MIME id of the HTML part, if any. * - text_id (string) The MIME id of the plain part, if any. * * @return Horde_Imap_Client_Data_Fetch The results. */ protected function _fetchData(array $params) { $query = new Horde_Imap_Client_Fetch_Query(); $query_opts = array( 'decode' => true, 'peek' => true ); // Get body information if ($this->_version >= Horde_ActiveSync::VERSION_TWELVE) { if (!empty($params['html_id'])) { $query->bodyPartSize($params['html_id']); $query->bodyPart($params['html_id'], $query_opts); } if (!empty($params['text_id'])) { $query->bodyPart($params['text_id'], $query_opts); $query->bodyPartSize($params['text_id']); } } else { // EAS 2.5 Plaintext body $query->bodyPart($params['text_id'], $query_opts); $query->bodyPartSize($params['text_id']); } try { $fetch_ret = $this->_imap->fetch( $this->_mbox, $query, array('ids' => new Horde_Imap_Client_Ids(array($this->_uid))) ); } catch (Horde_Imap_Client_Exception $e) { throw new Horde_ActiveSync_Exception($e); } if (!$data = $fetch_ret->first()) { throw new Horde_Exception_NotFound( sprintf('Could not load message %s from server.', $this->_uid)); } return $data; } /** * Build the data needed for the plain part. * * @param Horde_Imap_Client_Data_Fetch $data The FETCH results. * @param Horde_Mime_Part $text_mime The plaintext MIME part. * * @return array The plain part data. * - charset: (string) The charset of the text. * - body: (string) The body text. * - truncated: (boolean) True if text was truncated. * - size: (integer) The original part size, in bytes. */ protected function _getPlainPart( Horde_Imap_Client_Data_Fetch $data, Horde_Mime_Part $text_mime) { $text_id = $text_mime->getMimeId(); $text = $data->getBodyPart($text_id); if (!$data->getBodyPartDecode($text_id)) { $text_mime->setContents($text); $text = $text_mime->getContents(); } $text_size = !is_null($data->getBodyPartSize($text_id)) ? $data->getBodyPartSize($text_id) : strlen($text); if (!empty($this->_options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_PLAIN]['truncationsize'])) { // EAS >= 12.0 truncation $text = Horde_String::substr( $text, 0, $this->_options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_PLAIN]['truncationsize'], $text_mime->getCharset() ); } $truncated = $text_size > strlen($text); if ($this->_version >= Horde_ActiveSync::VERSION_TWELVE && $truncated && !empty($this->_options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_PLAIN]['allornone'])) { $text = ''; } return array( 'charset' => $text_mime->getCharset(), 'body' => $text, 'truncated' => $truncated, 'size' => $text_size); } /** * Build the data needed for the html part. * * @param Horde_Imap_Client_Data_Fetch $data FETCH results. * @param Horde_Mime_Part $html_mime The text/html MIME part. * @param boolean $convert_to_plain Convert text to plain text * also? If true, will also return a 'plain' array. * * @return array An array containing 'html' and if $convert_to_true is set, * a 'plain' part as well. @see self::_getPlainPart for * structure of each entry. */ protected function _getHtmlPart( Horde_Imap_Client_Data_Fetch $data, Horde_Mime_Part $html_mime, $convert_to_plain) { // @todo The length stuff in this method should really be done after // we validate the text since it might change if there was an incorrect // charset etc... For BC reasons, however, we need to keep the // unvalidated data available. Keep this as-is for now and refactor // for Horde 6. The worse-case here is that an incorrectly announced // charset MAY cause an email to be reported as truncated when it's not, // causing an additional reload on the client when viewing. $results = array(); $html_id = $html_mime->getMimeId(); $html = $data->getBodyPart($html_id); if (!$data->getBodyPartDecode($html_id)) { $html_mime->setContents($html); $html = $html_mime->getContents(); } $charset = $html_mime->getCharset(); // Size of the original HTML part. $html_size = !is_null($data->getBodyPartSize($html_id)) ? $data->getBodyPartSize($html_id) : strlen($html); if (!empty($this->_options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_HTML]['truncationsize'])) { $html = Horde_String::substr( $html, 0, $this->_options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_HTML]['truncationsize'], $charset); } if ($convert_to_plain) { $html_plain = Horde_Text_Filter::filter( $html, 'Html2text', array('charset' => $charset, 'nestingLimit' => 1000)); // Get the new size, since it probably changed. $html_plain_size = strlen($html_plain); if (!empty($this->_options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_PLAIN]['truncationsize'])) { // EAS >= 12.0 truncation $html_plain = Horde_String::substr( $html_plain, 0, $this->_options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_PLAIN]['truncationsize'], $charset); } $results['plain'] = array( 'charset' => $charset, 'body' => $html_plain, 'truncated' => $html_plain_size > strlen($html_plain), 'size' => $html_plain_size ); } $truncated = $html_size > strlen($html); if ($this->_version >= Horde_ActiveSync::VERSION_TWELVE && !($truncated && !empty($this->_options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_HTML]['allornone']))) { $results['html'] = array( 'charset' => $charset, 'body' => $html, 'estimated_size' => $html_size, 'truncated' => $truncated); } return $results; } /** * Build the data needed for the BodyPart part. * * @param Horde_Imap_Client_Data_Fetch $data The FETCH results. * @param Horde_Mime_Part $mime The plaintext MIME part. * @param boolean $to_html If true, $id is assumed to be a text/plain * part and is converted to html. * * @return array The BodyPart data. * - charset: (string) The charset of the text. * - body: (string) The body text. * - truncated: (boolean) True if text was truncated. * - size: (integer) The original part size, in bytes. */ protected function _getBodyPart( Horde_Imap_Client_Data_Fetch $data, Horde_Mime_Part $mime, $to_html) { $id = $mime->getMimeId(); $text = $data->getBodyPart($id); if (!$data->getBodyPartDecode($id)) { $mime->setContents($text); $text = $mime->getContents(); } if ($to_html) { $text = Horde_Text_Filter::filter( $text, 'Text2html', array('parselevel' => Horde_Text_Filter_Text2html::MICRO, 'charset' => $mime->getCharset())); $size = strlen($text); } else { $size = !is_null($data->getBodyPartSize($id)) ? $data->getBodyPartSize($id) : strlen($text); } if (!empty($this->_options['bodypartprefs']['truncationsize'])) { $text = Horde_String::substr( $text, 0, $this->_options['bodypartprefs']['truncationsize'], $mime->getCharset()); } return array( 'charset' => $mime->getCharset(), 'body' => $text, 'truncated' => $size > strlen($text), 'size' => $size ); } /** * Return the validated text/plain body data. * * @return array The validated body data array: * - charset: (string) The charset of the text. * - body: (Horde_Stream) The body text in a stream. * - truncated: (boolean) True if text was truncated. * - size: (integer) The original part size, in bytes. */ public function plainBody() { if (!empty($this->_plain) && empty($this->_validatedPlain)) { $this->_validateBodyData($this->_plain); $this->_validatedPlain = true; } if ($this->_plain['body'] instanceof Horde_Stream) { return $this->_plain; } return false; } /** * Return the validated text/html body data. * * @return array The validated body data array: * - charset: (string) The charset of the text. * - body: (Horde_Stream) The body text in a stream. * - truncated: (boolean) True if text was truncated. * - size: (integer) The original part size, in bytes. */ public function htmlBody() { if (!empty($this->_html) && empty($this->_validatedHtml)) { $this->_validateBodyData($this->_html); $this->_validatedHtml = true; } if ($this->_html['body'] instanceof Horde_Stream) { return $this->_html; } return false; } /** * Return the validated BODYPART data. * * @return array The validated body data array: * - charset: (string) The charset of the text. * - body: (Horde_Stream) The body text in a stream. * - truncated: (boolean) True if text was truncated. * - size: (integer) The original part size, in bytes. */ public function bodyPartBody() { if (!empty($this->_bodyPart)) { $this->_validateBodyData($this->_bodyPart); return $this->_bodyPart; } return false; } /** * Validate the body data to ensure consistent EOL and UTF8 data. Returns * body data in a stream object. * * @param array $data The body data. @see self::_bodyPartText() for * structure. * * @return array The validated body data array. @see self::_bodyPartText() */ protected function _validateBodyData(&$data) { $stream = new Horde_Stream_Temp(array('max_memory' => 1048576)); $filter_h = stream_filter_append($stream->stream, 'horde_eol', STREAM_FILTER_WRITE); $stream->add(Horde_ActiveSync_Utils::ensureUtf8($data['body'], $data['charset']), true); stream_filter_remove($filter_h); $data['body'] = $stream; } /** * Return the body data in array format. Needed for BC. * * @return array * @todo remove in H6. */ public function toArray() { $result = array(); if ($this->plain) { $result['plain'] = $this->_plain; } if ($this->html) { $result['html'] = $this->_html; } return $result; } }Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Interface/ImapFactory.php0000664000076500000240000000345012654565405022433 0ustar * @package ActiveSync */ /** * Horde_ActiveSync_Interface_ImapFactory:: Defines an interface for a factory * object that knows how to provide an appropriate Horde_ActiveSync_Imap_Adapter * object and mailbox lists. * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2012-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync */ interface Horde_ActiveSync_Interface_ImapFactory { /** * Return an imap client object. * * @return mixed An object capable of communicating with an IMAP server. */ public function getImapOb(); /** * Return the list of IMAP mailboxes * * @param boolean $force If true, force a refresh of the list. * * @return array An array of mailbox names. */ public function getMailboxes($force = false); /** * Return the list of "special" mailboxes such as Trash/Sent * * @return array An array of mailbox names, keyed by the special type. */ public function getSpecialMailboxes(); }Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Interface/LoggerFactory.php0000664000076500000240000000274612654565405022773 0ustar * @package ActiveSync */ /** * Horde_ActiveSync_Interface_LoggerFactory:: Defines an interface for a factory * object that knows how to provide an appropriate Horde_Log_Logger object. * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2012-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync */ interface Horde_ActiveSync_Interface_LoggerFactory { /** * Factory for a log object. Attempts to create a device specific file if * custom logging is requested. * * @param array $properties The property array. * * @return Horde_Log_Logger The logger object, correctly configured. */ public function create($properties = array()); }Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Message/AirSyncBaseAttachment.php0000664000076500000240000000701712654565405024060 0ustar * @package ActiveSync */ /** * Horde_ActiveSync_Message_AirSyncBaseAttachment:: * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2011-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync * * @property string $attmethod The attachment method. * @property integer $attsize The attachment size. * @property string $displayname The attachment's display name. * @property string $attname The attachment's name. * @property boolean $attremoved @todo * @property string $contentid The Content-Id of the mime part. * @property string $contentlocation @todo * @property boolean $isinline Indicates that this part is to be displayed inline. */ class Horde_ActiveSync_Message_AirSyncBaseAttachment extends Horde_ActiveSync_Message_Base { /* Attachement types */ const ATT_TYPE_NORMAL = 1; const ATT_TYPE_EMBEDDED = 5; const ATT_TYPE_OLE = 6; /** * Property mappings * * @var array */ protected $_mapping = array( Horde_ActiveSync::AIRSYNCBASE_DISPLAYNAME => array (self::KEY_ATTRIBUTE => 'displayname'), Horde_ActiveSync::AIRSYNCBASE_FILEREFERENCE => array (self::KEY_ATTRIBUTE => 'attname'), Horde_ActiveSync::AIRSYNCBASE_METHOD => array (self::KEY_ATTRIBUTE => 'attmethod'), Horde_ActiveSync::AIRSYNCBASE_ESTIMATEDDATASIZE => array (self::KEY_ATTRIBUTE => 'attsize'), Horde_ActiveSync::AIRSYNCBASE_CONTENTID => array (self::KEY_ATTRIBUTE => 'contentid'), Horde_ActiveSync::AIRSYNCBASE_CONTENTLOCATION => array (self::KEY_ATTRIBUTE => 'contentlocation'), Horde_ActiveSync::AIRSYNCBASE_ISINLINE => array (self::KEY_ATTRIBUTE => 'isinline'), Horde_ActiveSync::AIRSYNCBASE_DATA => array (self::KEY_ATTRIBUTE => '_data'), ); /** * Property mapping. * * @var array */ protected $_properties = array( 'attmethod' => false, 'attsize' => false, 'displayname' => false, 'attname' => false, 'attremoved' => false, 'contentid' => false, 'contentlocation' => false, 'isinline' => false, '_data' => false ); /** * Return the type of message. * * @return string */ public function getClass() { return 'AirSyncBaseAttachment'; } } Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Message/AirSyncBaseBody.php0000664000076500000240000000640412654565405022664 0ustar * @package ActiveSync */ /** * Horde_ActiveSync_Message_AirSyncBaseBody:: * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2011-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync * * @property integer $type The content type of the body. * A Horde_ActiveSync::BODYPREF_TYPE_* constant. * @property integer $estimateddatasize The estimated size of the untruncated body. * @property integer $truncated The truncated flag. 0 == not truncated, 1 == truncated * @property mixed $string|stream $data The body data. */ class Horde_ActiveSync_Message_AirSyncBaseBody extends Horde_ActiveSync_Message_Base { /** * Property mapping * * @var array */ protected $_mapping = array( Horde_ActiveSync::AIRSYNCBASE_TYPE => array(self::KEY_ATTRIBUTE => 'type'), Horde_ActiveSync::AIRSYNCBASE_ESTIMATEDDATASIZE => array(self::KEY_ATTRIBUTE => 'estimateddatasize'), Horde_ActiveSync::AIRSYNCBASE_TRUNCATED => array(self::KEY_ATTRIBUTE => 'truncated'), Horde_ActiveSync::AIRSYNCBASE_DATA => array(self::KEY_ATTRIBUTE => 'data'), ); /** * Property values * * @var array */ protected $_properties = array( 'type' => false, 'estimateddatasize' => false, 'truncated' => false, 'data' => false ); /** * Const'r * * @see Horde_ActiveSync_Message_Base::__construct() */ public function __construct(array $options = array()) { parent::__construct($options); if ($this->_version >= Horde_ActiveSync::VERSION_FOURTEEN) { $this->_mapping += array( Horde_ActiveSync::AIRSYNCBASE_PREVIEW => array(self::KEY_ATTRIBUTE => 'preview') ); $this->_properties += array( 'preview' => false ); } } public function __destruct() { $this->_properties['data'] = null; } /** * Return the message type. * * @return string */ public function getClass() { return 'AirSyncBaseBody'; } }Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Message/AirSyncBaseBodypart.php0000664000076500000240000000562012654565405023552 0ustar * @package ActiveSync */ /** * Horde_ActiveSync_Message_AirSyncBaseBodypart:: * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2011-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync * * @property integer $status The status property. * Either Horde_ActiveSync_Status::BODYPART_CONVERSATION_TOO_LARGE or * self::STATUS_SUCESS * @property integer $type The content type of the body. * A Horde_ActiveSync::BODYPREF_TYPE_* constant. * @property integer $estimateddatasize The estimated size of the untruncated body. * @property integer $truncated The truncated flag. 0 == not truncated, 1 == truncated * @property string|stream $data The body data. * @property string $preview Body preview. */ class Horde_ActiveSync_Message_AirSyncBaseBodypart extends Horde_ActiveSync_Message_Base { const STATUS_SUCCESS = 1; /** * Property mapping * * @var array */ protected $_mapping = array( Horde_ActiveSync::AIRSYNCBASE_STATUS => array(self::KEY_ATTRIBUTE => 'status'), Horde_ActiveSync::AIRSYNCBASE_TYPE => array(self::KEY_ATTRIBUTE => 'type'), Horde_ActiveSync::AIRSYNCBASE_ESTIMATEDDATASIZE => array(self::KEY_ATTRIBUTE => 'estimateddatasize'), Horde_ActiveSync::AIRSYNCBASE_TRUNCATED => array(self::KEY_ATTRIBUTE => 'truncated'), Horde_ActiveSync::AIRSYNCBASE_DATA => array(self::KEY_ATTRIBUTE => 'data'), Horde_ActiveSync::AIRSYNCBASE_PREVIEW => array(self::KEY_ATTRIBUTE => 'preview') ); /** * Property values * * @var array */ protected $_properties = array( 'status' => false, 'type' => Horde_ActiveSync::BODYPREF_TYPE_HTML, 'estimateddatasize' => false, 'truncated' => false, 'data' => false, 'preview' => false, ); /** * Return the message type. * * @return string */ public function getClass() { return 'AirSyncBaseBodypart'; } }Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Message/AirSyncBaseFileAttachment.php0000664000076500000240000000774612654565405024671 0ustar * @package ActiveSync */ /** * Horde_ActiveSync_Message_AirSyncFileAttachment:: * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2011-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync * * @property string $contenttype The content type of the attachment. * @property mixed $string|stream $data The attachment data. * @property integer $total The total size of the attachment. * @property string $range The range string being returned. */ class Horde_ActiveSync_Message_AirSyncBaseFileAttachment extends Horde_ActiveSync_Message_Base { /** * Property map * * @var array */ protected $_mapping = array( Horde_ActiveSync_Request_ItemOperations::ITEMOPERATIONS_RANGE => array(self::KEY_ATTRIBUTE => 'range'), Horde_ActiveSync_Request_ItemOperations::ITEMOPERATIONS_TOTAL => array(self::KEY_ATTRIBUTE => 'total'), Horde_ActiveSync::AIRSYNCBASE_CONTENTTYPE => array(self::KEY_ATTRIBUTE => 'contenttype'), Horde_ActiveSync_Request_ItemOperations::ITEMOPERATIONS_DATA => array(self::KEY_ATTRIBUTE => 'data') ); /** * Property values * * @var array */ protected $_properties = array( 'range' => false, 'total' => false, 'contenttype' => false, 'data' => false, ); /** * Return the message type. * * @return string */ public function getClass() { return 'AirSyncBaseFileAttachment'; } /** * Checks if the data needs to be encoded like e.g., when outputing binary * data in-line during ITEMOPERATIONS requests. * * @param mixed $data The data to check. A string or stream resource. * @param string $tag The tag we are outputing. * * @return mixed The encoded data. A string or stream resource with * a filter attached. */ protected function _checkEncoding($data, $tag) { if ($tag == Horde_ActiveSync_Request_ItemOperations::ITEMOPERATIONS_DATA) { // See Bug: 14086. Use a STREAM_FILTER_WRITE and perform the // filtering here instead of using the currently broken behavior of // PHP when using base64-encode as STREAM_FILTER_READ. feof() is // apparently not safe to use when using STREAM_FILTER_READ. if (is_resource($data)) { $temp = fopen('php://temp/', 'r+'); $filter = stream_filter_prepend($temp, 'convert.base64-encode', STREAM_FILTER_WRITE); rewind($data); while (!feof($data)) { fwrite($temp, fread($data, 8192)); } stream_filter_remove($filter); rewind($temp); return $temp; } else { return base64_encode($data); } } return $data; } } Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Message/AirSyncBaseLocation.php0000664000076500000240000001064412654565405023540 0ustar * @package ActiveSync */ /** * Horde_ActiveSync_Message_AirSyncBaseLocation:: * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync */ class Horde_ActiveSync_Message_AirSyncBaseLocation extends Horde_ActiveSync_Message_Base { /** * Property map * * @var array */ protected $_mapping = array( Horde_ActiveSync::AIRSYNCBASE_ACCURACY => array(self::KEY_ATTRIBUTE => 'accuracy'), Horde_ActiveSync::AIRSYNCBASE_ALTITUDE => array(self::KEY_ATTRIBUTE => 'altitude'), Horde_ActiveSync::AIRSYNCBASE_ALTITUDEACCURACY => array(self::KEY_ATTRIBUTE => 'altitudeaccuracy'), Horde_ActiveSync::AIRSYNCBASE_ANNOTATION => array(self::KEY_ATTRIBUTE => 'annotation'), Horde_ActiveSync::AIRSYNCBASE_CITY => array(self::KEY_ATTRIBUTE => 'annotation'), Horde_ActiveSync::AIRSYNCBASE_COUNTRY => array(self::KEY_ATTRIBUTE => 'annotation'), Horde_ActiveSync::AIRSYNCBASE_DISPLAYNAME => array(self::KEY_ATTRIBUTE => 'displayname'), Horde_ActiveSync::AIRSYNCBASE_LATITUDE => array(self::KEY_ATTRIBUTE => 'latitude'), Horde_ActiveSync::AIRSYNCBASE_LOCATIONURI => array(self::KEY_ATTRIBUTE => 'locationuri'), Horde_ActiveSync::AIRSYNCBASE_LONGITUDE => array(self::KEY_ATTRIBUTE => 'longitude'), Horde_ActiveSync::AIRSYNCBASE_POSTALCODE => array(self::KEY_ATTRIBUTE => 'postalcode'), Horde_ActiveSync::AIRSYNCBASE_STATE => array(self::KEY_ATTRIBUTE => 'state'), Horde_ActiveSync::AIRSYNCBASE_STREET => array(self::KEY_ATTRIBUTE => 'street'), ); /** * Property values * * @var array */ protected $_properties = array( 'accuracy' => false, 'altitude' => false, 'altitudeaccuracy' => false, 'annotation' => false, 'city' => false, 'country' => false, 'displayname' => false, 'latitude' => false, 'locationuri' => false, 'longitude' => false, 'postalcode' => false, 'state' => false, 'street' => false, ); /** * Return the message type. * * @return string */ public function getClass() { return 'AirSyncBaseFileAttachment'; } /** * Checks if the data needs to be encoded like e.g., when outputing binary * data in-line during ITEMOPERATIONS requests. * * @param mixed $data The data to check. A string or stream resource. * @param string $tag The tag we are outputing. * * @return mixed The encoded data. A string or stream resource with * a filter attached. */ protected function _checkEncoding($data, $tag) { if ($tag == Horde_ActiveSync_Request_ItemOperations::ITEMOPERATIONS_DATA) { // See Bug: 14086. Use a STREAM_FILTER_WRITE and perform the // filtering here instead of using the currently broken behavior of // PHP when using base64-encode as STREAM_FILTER_READ. feof() is // apparently not safe to use when using STREAM_FILTER_READ. if (is_resource($data)) { $temp = fopen('php://temp/', 'r+'); $filter = stream_filter_prepend($temp, 'convert.base64-encode', STREAM_FILTER_WRITE); rewind($data); while (!feof($data)) { fwrite($temp, fread($data, 8192)); } stream_filter_remove($filter); rewind($temp); return $temp; } else { return base64_encode($data); } } return $data; } } Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Message/Appointment.php0000664000076500000240000010455312654565405022205 0ustar * @package ActiveSync */ /** * Horde_ActiveSync_Message_Appointment * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2009-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync */ class Horde_ActiveSync_Message_Appointment extends Horde_ActiveSync_Message_Base { /* POOMCAL Tag Constants */ const POOMCAL_TIMEZONE = 'POOMCAL:Timezone'; const POOMCAL_ALLDAYEVENT = 'POOMCAL:AllDayEvent'; const POOMCAL_ATTENDEES = 'POOMCAL:Attendees'; const POOMCAL_ATTENDEE = 'POOMCAL:Attendee'; const POOMCAL_ATTENDEESTATUS = 'POOMCAL:AttendeeStatus'; const POOMCAL_ATTENDEETYPE = 'POOMCAL:AttendeeType'; const POOMCAL_EMAIL = 'POOMCAL:Email'; const POOMCAL_NAME = 'POOMCAL:Name'; const POOMCAL_BODY = 'POOMCAL:Body'; const POOMCAL_BODYTRUNCATED = 'POOMCAL:BodyTruncated'; const POOMCAL_BUSYSTATUS = 'POOMCAL:BusyStatus'; const POOMCAL_CATEGORIES = 'POOMCAL:Categories'; const POOMCAL_CATEGORY = 'POOMCAL:Category'; const POOMCAL_RTF = 'POOMCAL:Rtf'; const POOMCAL_DTSTAMP = 'POOMCAL:DtStamp'; const POOMCAL_ENDTIME = 'POOMCAL:EndTime'; const POOMCAL_EXCEPTION = 'POOMCAL:Exception'; const POOMCAL_EXCEPTIONS = 'POOMCAL:Exceptions'; const POOMCAL_DELETED = 'POOMCAL:Deleted'; const POOMCAL_EXCEPTIONSTARTTIME = 'POOMCAL:ExceptionStartTime'; const POOMCAL_LOCATION = 'POOMCAL:Location'; const POOMCAL_MEETINGSTATUS = 'POOMCAL:MeetingStatus'; const POOMCAL_ORGANIZEREMAIL = 'POOMCAL:OrganizerEmail'; const POOMCAL_ORGANIZERNAME = 'POOMCAL:OrganizerName'; const POOMCAL_RECURRENCE = 'POOMCAL:Recurrence'; const POOMCAL_TYPE = 'POOMCAL:Type'; const POOMCAL_UNTIL = 'POOMCAL:Until'; const POOMCAL_OCCURRENCES = 'POOMCAL:Occurrences'; const POOMCAL_INTERVAL = 'POOMCAL:Interval'; const POOMCAL_DAYOFWEEK = 'POOMCAL:DayOfWeek'; const POOMCAL_DAYOFMONTH = 'POOMCAL:DayOfMonth'; const POOMCAL_WEEKOFMONTH = 'POOMCAL:WeekOfMonth'; const POOMCAL_MONTHOFYEAR = 'POOMCAL:MonthOfYear'; const POOMCAL_REMINDER = 'POOMCAL:Reminder'; const POOMCAL_SENSITIVITY = 'POOMCAL:Sensitivity'; const POOMCAL_SUBJECT = 'POOMCAL:Subject'; const POOMCAL_STARTTIME = 'POOMCAL:StartTime'; const POOMCAL_UID = 'POOMCAL:UID'; // 14.0 const POOMCAL_DISALLOWNEWTIMEPROPOSAL = 'POOMCAL:DisallowNewTimeProposal'; const POOMCAL_RESPONSEREQUESTED = 'POOMCAL:ResponseRequested'; const POOMCAL_APPOINTMENTREPLYTIME = 'POOMCAL:AppointmentReplyTime'; const POOMCAL_CALENDARTYPE = 'POOMCAL:CalendarType'; const POOMCAL_ISLEAPMONTH = 'POOMCAL:IsLeapMonth'; const POOMCAL_RESPONSETYPE = 'POOMCAL:ResponseType'; // 14.1 const POOMCAL_FIRSTDAYOFWEEK = 'POOMCAL:FirstDayOfWeek'; const POOMCAL_ONLINECONFLINK = 'POOMCAL:OnlineMeetingConfLink'; const POOMCAL_ONLINEEXTLINK = 'POOMCAL:OnlineMeetingExternalLink'; // 16.0 const POOMCAL_CLIENTUID = 'POOMCAL:ClientUid'; /* Sensitivity */ const SENSITIVITY_NORMAL = 0; const SENSITIVITY_PERSONAL = 1; const SENSITIVITY_PRIVATE = 2; const SENSITIVITY_CONFIDENTIAL = 3; /* Busy status */ const BUSYSTATUS_FREE = 0; const BUSYSTATUS_TENTATIVE = 1; const BUSYSTATUS_BUSY = 2; const BUSYSTATUS_OUT = 3; // 16.0 only. const BUSYSTATUS_ELSEWHERE = 4; /* All day meeting */ const IS_ALL_DAY = 1; /* Meeting status */ const MEETING_NOT_MEETING = 0; const MEETING_IS_MEETING = 1; const MEETING_RECEIVED = 3; const MEETING_CANCELLED = 5; const MEETING_CANCELLED_RECEIVED = 7; /* Response status */ const RESPONSE_NONE = 0; const RESPONSE_ORGANIZER = 1; const RESPONSE_TENTATIVE = 2; const RESPONSE_ACCEPTED = 3; const RESPONSE_DECLINED = 4; const RESPONSE_NORESPONSE = 5; /** * DOW mapping for DATE to MASK * * @var array */ protected $_dayOfWeekMap = array( Horde_Date::DATE_SUNDAY => Horde_Date::MASK_SUNDAY, Horde_Date::DATE_MONDAY => Horde_Date::MASK_MONDAY, Horde_Date::DATE_TUESDAY => Horde_Date::MASK_TUESDAY, Horde_Date::DATE_WEDNESDAY => Horde_Date::MASK_WEDNESDAY, Horde_Date::DATE_THURSDAY => Horde_Date::MASK_THURSDAY, Horde_Date::DATE_FRIDAY => Horde_Date::MASK_FRIDAY, Horde_Date::DATE_SATURDAY => Horde_Date::MASK_SATURDAY, ); /** * Property mapping. * * @var array */ protected $_mapping = array( self::POOMCAL_TIMEZONE => array (self::KEY_ATTRIBUTE => 'timezone'), self::POOMCAL_ALLDAYEVENT => array (self::KEY_ATTRIBUTE => 'alldayevent'), self::POOMCAL_BUSYSTATUS => array (self::KEY_ATTRIBUTE => 'busystatus'), self::POOMCAL_ORGANIZERNAME => array (self::KEY_ATTRIBUTE => 'organizername'), self::POOMCAL_ORGANIZEREMAIL => array (self::KEY_ATTRIBUTE => 'organizeremail'), self::POOMCAL_DTSTAMP => array (self::KEY_ATTRIBUTE => 'dtstamp', self::KEY_TYPE => self::TYPE_DATE), self::POOMCAL_ENDTIME => array (self::KEY_ATTRIBUTE => 'endtime', self::KEY_TYPE => self::TYPE_DATE), self::POOMCAL_REMINDER => array (self::KEY_ATTRIBUTE => 'reminder'), self::POOMCAL_SENSITIVITY => array (self::KEY_ATTRIBUTE => 'sensitivity'), self::POOMCAL_SUBJECT => array (self::KEY_ATTRIBUTE => 'subject'), self::POOMCAL_STARTTIME => array (self::KEY_ATTRIBUTE => 'starttime', self::KEY_TYPE => self::TYPE_DATE), self::POOMCAL_UID => array (self::KEY_ATTRIBUTE => 'uid'), self::POOMCAL_MEETINGSTATUS => array (self::KEY_ATTRIBUTE => 'meetingstatus'), self::POOMCAL_ATTENDEES => array (self::KEY_ATTRIBUTE => 'attendees', self::KEY_TYPE => 'Horde_ActiveSync_Message_Attendee', self::KEY_VALUES => self::POOMCAL_ATTENDEE), self::POOMCAL_CATEGORIES => array (self::KEY_ATTRIBUTE => 'categories', self::KEY_VALUES => self::POOMCAL_CATEGORY), self::POOMCAL_RECURRENCE => array (self::KEY_ATTRIBUTE => 'recurrence', self::KEY_TYPE => 'Horde_ActiveSync_Message_Recurrence'), self::POOMCAL_EXCEPTIONS => array (self::KEY_ATTRIBUTE => 'exceptions', self::KEY_TYPE => 'Horde_ActiveSync_Message_Exception', self::KEY_VALUES => self::POOMCAL_EXCEPTION), ); /** * Property values. * * @var array */ protected $_properties = array( 'alldayevent' => false, 'attendees' => array(), 'busystatus' => false, 'categories' => array(), 'dtstamp' => false, 'endtime' => false, 'exceptions' => array(), 'organizeremail' => false, 'organizername' => false, 'meetingstatus' => self::MEETING_NOT_MEETING, 'recurrence' => false, 'reminder' => false, 'sensitivity' => false, 'starttime' => false, 'subject' => false, 'timezone' => false, 'uid' => false, // Not part of the protocol. Used internally. 'serveruid' => false, ); /** * Const'r * * @see Horde_ActiveSync_Message_Base::__construct() */ public function __construct(array $options = array()) { parent::__construct($options); // Removed in 16.0 if ($this->_version <= Horde_ActiveSync::VERSION_FOURTEENONE) { $this->_mapping += array( self::POOMCAL_LOCATION => array(self::KEY_ATTRIBUTE => 'location'), ); $this->_properties += array( 'location' => false, ); } if ($this->_version < Horde_ActiveSync::VERSION_TWELVE) { $this->_mapping += array( self::POOMCAL_BODY => array(self::KEY_ATTRIBUTE => 'body'), self::POOMCAL_BODYTRUNCATED => array(self::KEY_ATTRIBUTE => 'bodytruncated'), self::POOMCAL_RTF => array(self::KEY_ATTRIBUTE => 'rtf'), ); $this->_properties += array( 'body' => false, 'bodytruncated' => 0, 'rtf' => false ); } else { $this->_mapping += array( Horde_ActiveSync::AIRSYNCBASE_BODY => array(self::KEY_ATTRIBUTE => 'airsyncbasebody', self::KEY_TYPE => 'Horde_ActiveSync_Message_AirSyncBaseBody') ); $this->_properties += array( 'airsyncbasebody' => false ); if ($this->_version >= Horde_ActiveSync::VERSION_FOURTEEN) { $this->_mapping += array( self::POOMCAL_RESPONSEREQUESTED => array(self::KEY_ATTRIBUTE => 'responserequested'), self::POOMCAL_APPOINTMENTREPLYTIME => array(self::KEY_ATTRIBUTE => 'appointmentreplytime', self::KEY_TYPE => self::TYPE_DATE_DASHES), self::POOMCAL_RESPONSETYPE => array(self::KEY_ATTRIBUTE => 'responsetype'), self::POOMCAL_DISALLOWNEWTIMEPROPOSAL => array(self::KEY_ATTRIBUTE => 'disallownewtimeproposal'), ); $this->_properties += array( 'disallownewtimeproposal' => false, 'responserequested' => false, 'appointmentreplytime' => false, 'responsetype' => false, ); } if ($this->_version >= Horde_ActiveSync::VERSION_FOURTEENONE) { $this->_mapping += array( self::POOMCAL_ONLINECONFLINK => array(self::KEY_ATTRIBUTE => 'onlinemeetingconflink'), self::POOMCAL_ONLINEEXTLINK => array(self::KEY_ATTRIBUTE => 'onlinemeetingexternallink') ); $this->_properties += array( 'onlinemeetingconflink' => false, 'onlinemeetingexternallink' => false ); } if ($this->_version >= Horde_ActiveSync::VERSION_SIXTEEN) { $this->_mapping += array( Horde_ActiveSync::AIRSYNCBASE_LOCATION => array(self::KEY_ATTRIBUTE => 'location', self::KEY_TYPE => 'Horde_ActiveSync_Message_AirSyncBaseLocation'), self::POOMCAL_CLIENTUID => array(self::KEY_ATTRIBUTE => 'clientuid'), Horde_ActiveSync::AIRSYNCBASE_INSTANCEID => array(self::KEY_ATTRIBUTE => 'instanceid', self::KEY_TYPE => self::TYPE_DATE), ); $this->_properties += array( 'location' => false, 'clientuid' => false, 'instanceid' => false, ); } } } /** * Give concrete classes the chance to enforce rules on property values. * * @return boolean True on success, otherwise false. */ protected function _validateDecodedValues() { if ($this->commandType == Horde_ActiveSync::SYNC_MODIFY && $this->_version == Horde_ActiveSync::VERSION_SIXTEEN) { if ($this->_properties['alldayevent'] == true) { // Timezone element is forbidden here. if (!empty($this->_properties['timezone'])) { return false; } // No time components allowed here. The server is to interpret // the starttime as occuring on the date listed here regardless // of the timezone. if ($this->_properties['starttime'] && ($this->_properties['starttime']->hour != 0 || $this->_properties['starttime']->min != 0 || $this->_properties['starttime']->sec != 0)) { return false; } if ($this->_properties['endtime'] && ($this->_properties['endtime']->hour != 0 || $this->_properties['endtime']->min != 0 || $this->_properties['endtime']->sec != 0)) { return false; } if ($this->_properties['recurrence'] && $this->_properties['recurrence']->until && ($this->_properties['recurrence']->until != 0 || $this->_properties['recurrence']->until != 0 || $this->_properties['recurrence']->until != 0)) { return false; } } // Exception events must match the alldayevent property of the // master event. foreach ($this->_properties['exceptions'] as $ex) { if ($ex->alldayevent != $this->_properties['alldayevent']) { return false; } } } // These values are not allowed in a EAS 16.0 command request. // @todo - should we just wipe the values instead of failing the test? if ($this->_version == Horde_ActiveSync::VERSION_SIXTEEN) { if (!empty($this->_properties['uid']) || !empty($this->_properties['dtstamp']) || !empty($this->_properties['organizername']) || !empty($this->_properties['organizeremail'])) { return false; } } return true; } /** * Give concrete classes the chance to enforce rules before encoding * messages to send to the client. * * @return boolean True if values were valid (or could be made valid). * False if values are unable to be validated. * @since 2.31.0 */ protected function _preEncodeValidation() { if ($this->_properties['alldayevent']) { if ($this->_properties['starttime']->hour != 0 || $this->_properties['starttime']->min != 0 || $this->_properties['starttime']->sec != 0) { return false; } if ($this->_properties['endtime'] && ($this->_properties['endtime']->hour != 0 || $this->_properties['endtime']->min != 0 || $this->_properties['endtime']->sec != 0)) { return false; } // For EAS 16, timezone cannot be sent for allday events. The // event is interpreted to be on the given date regardless of // timezone...as such, we need to manually convert to UTC here // and (re)set the date to be sure it matches the desired date. if ($this->_version == Horde_ActiveSync::VERSION_SIXTEEN) { $this->_properties['timezone'] = false; $mday = $this->_properties['starttime']->mday; $this->_properties['starttime']->setTimezone('UTC'); $this->_properties['starttime']->mday = $mday; $this->_properties['starttime']->hour = 0; $this->_properties['starttime']->min = 0; $this->_properties['starttime']->sec = 0; } if ($this->_properties['endtime']) { $mday = $this->_properties['endtime']->mday; $this->_properties['endtime']->setTimezone('UTC'); $this->_properties['endtime']->mday = $mday; $this->_properties['endtime']->hour = 0; $this->_properties['endtime']->min = 0; $this->_properties['endtime']->sec = 0; } } return true; } /** * Set the timezone * * @param mixed $date Either a Horde_Date or timezone descriptor such as * America/New_York etc... * * @throws InvalidArgumentException */ public function setTimezone($date) { if (!($date instanceof Horde_Date)) { if (!is_string($date)) { throw new InvalidArgumentException( '$date must be an instance of Horde_Date or a valid timezone descriptor'); } $date = new Horde_Date(time(), $date); } $offsets = Horde_Mapi_Timezone::getOffsetsFromDate($date); $tz = Horde_Mapi_Timezone::getSyncTZFromOffsets($offsets); $this->_properties['timezone'] = $tz; } /** * Get the event's timezone * * @return string The timezone identifier */ public function getTimezone() { $parser = new Horde_Mapi_Timezone(); return $parser->getTimezone($this->timezone, date_default_timezone_get()); } /** * Set the appointment's modify timestamp * * @param mixed Horde_Date|integer $date The date to set. * @deprecated */ public function setDTStamp($date) { if (!($date instanceof Horde_Date)) { $date = new Horde_Date($date); } $this->_properties['dtstamp'] = $date; } /** * Get the appointment's dtimestamp * * @return Horde_Date The timestamp. * @deprecated */ public function getDTStamp() { return $this->_getAttribute('dtstamp'); } /** * Set the appointment time/duration. * * @param array $datetime An array containing: * - start: (Horde_Date) The start time. * - end: (Horde_Date) The end time. If omitted, must include duration or * allday. * - duration: (integer) The event duration in seconds. * - allday: (boolean) If true, this is an allday event. * * @throws InvalidArgumentException * @deprecated Set individual properties manually from calling code. */ public function setDatetime(array $datetime = array()) { // Start date is always required if (empty($datetime['start'])) { throw new InvalidArgumentException('Missing the required start parameter'); } /* Get or calculate start and end time in local tz */ $start = clone($datetime['start']); if (!empty($datetime['end'])) { $end = clone($datetime['end']); } elseif (!empty($datetime['duration'])) { $end = clone($start); $end->sec += $datetime['duration']; } else { $end = clone($start); } // Is this an all day event? if ($start->hour == 0 && $start->min == 0 && $start->sec == 0 && $end->hour == 23 && $end->min == 59) { $end = new Horde_Date( array('year' => (int)$end->year, 'month' => (int)$end->month, 'mday' => (int)$end->mday + 1)); $this->_properties['alldayevent'] = self::IS_ALL_DAY; } elseif (!empty($datetime['allday'])) { $this->_properties['alldayevent'] = self::IS_ALL_DAY; $end = new Horde_Date( array('year' => (int)$end->year, 'month' => (int)$end->month, 'mday' => (int)$end->mday)); } $this->_properties['starttime'] = $start; $this->_properties['endtime'] = $end; } /** * Get the appointment's time data * * @return array An array containing: * - start: (Horde_Date) The start time. * - end: (Horde_Date) The end time. * - allday: (boolean) If true, this is an allday event. * @deprecated */ public function getDatetime() { return array( 'start' => $this->_properties['starttime'], 'end' => $this->_properties['endtime'], 'allday' => !empty($this->_properties['alldayevent']) ? true : false ); } /** * Set the appointment subject field. * * @param string $subject A UTF-8 string * @deprecated Set the property directly. I.e. $message->subject = 'Test' */ public function setSubject($subject) { $this->_properties['subject'] = $subject; } /** * Get the subject * * @return string The UTF-8 subject string * @deprecated Retrieve the value directly. I.e., $message->subject */ public function getSubject() { return $this->_getAttribute('subject'); } /** * Set the appointment uid. Note that this is the client's UID value, and * not the value that the server normally uses for the UID. ActiveSync * messages do not normally include any server uid value as part of the * message directly. This causes issues with meeting requests since most * clients will use the CLIENT_ENTRY_ID for this value, and will send the * invitation email out using this value as the UID so we sort-of HAVE to * use this value as the server's UID. * * @param string $uid The server's uid for this appointment */ public function setUid($uid) { $this->_properties['uid'] = $uid; } /** * Get the client's UID. See not above regarding server UIDs. * * @return string */ public function getUid() { return $this->_getAttribute('uid'); } /** * Because the client doesn't pass the server uid as part of the message, * we need to add it manually so the backend can have access to it * when changing this object. * * @param string $uid The server UID */ public function setServerUID($uid) { $this->_properties['serveruid'] = $uid; } /** * Obtain the server UID. See note above. * * @return string */ public function getServerUID() { return $this->_getAttribute('serveruid'); } /** * Set the organizer name and/or email * * @param array 'name' and 'email' for this appointment organizer. */ public function setOrganizer(array $organizer) { $this->_properties['organizername'] = !empty($organizer['name']) ? $organizer['name'] : ''; $this->_properties['organizeremail'] = !empty($organizer['email']) ? $organizer['email'] : ''; } /** * Get the details for the appointment organizer * * @return array with 'name' and 'email' values */ public function getOrganizer() { return array( 'name' => $this->_getAttribute('organizername'), 'email' => $this->_getAttribute('organizeremail')); } /** * Set appointment location field. * * @param string $location * @deprecated */ public function setLocation($location) { $this->_properties['location'] = $location; } /** * Get the location field * * @return string * @deprecated */ public function getLocation() { return $this->_getAttribute('location'); } /** * Set recurrence information for this appointment * * @param Horde_Date_Recurrence $recurrence The recurrence data. * @param integer $fdow The first day of the week. * (A Horde_ActiveSync_Message_Recurrence:: constant). @since 2.4.0 */ public function setRecurrence(Horde_Date_Recurrence $recurrence, $fdow = null) { $r = Horde_ActiveSync::messageFactory('Recurrence'); if ($this->_version >= Horde_ActiveSync::VERSION_FOURTEENONE) { $r->firstdayofweek = $fdow; } /* Map the type fields */ switch ($recurrence->recurType) { case Horde_Date_Recurrence::RECUR_DAILY: $r->type = Horde_ActiveSync_Message_Recurrence::TYPE_DAILY; break; case Horde_Date_Recurrence::RECUR_WEEKLY: $r->type = Horde_ActiveSync_Message_Recurrence::TYPE_WEEKLY; $r->dayofweek = $recurrence->getRecurOnDays(); break; case Horde_Date_Recurrence::RECUR_MONTHLY_DATE: $r->type = Horde_ActiveSync_Message_Recurrence::TYPE_MONTHLY; break; case Horde_Date_Recurrence::RECUR_MONTHLY_WEEKDAY; $r->type = Horde_ActiveSync_Message_Recurrence::TYPE_MONTHLY_NTH; $r->weekofmonth = ceil($recurrence->start->mday / 7); $r->dayofweek = $this->_dayOfWeekMap[$recurrence->start->dayOfWeek()]; break; case Horde_Date_Recurrence::RECUR_YEARLY_DATE: $r->type = Horde_ActiveSync_Message_Recurrence::TYPE_YEARLY; $r->monthofyear = $recurrence->start->month; $r->dayofmonth = $recurrence->start->mday; break; case Horde_Date_Recurrence::RECUR_YEARLY_DAY: $r->type = Horde_ActiveSync_Message_Recurrence::TYPE_YEARLYNTH; $r->weekofmonth = ceil($recurrence->start->mday / 7); $r->monthofyear = $recurrence->start->month; break; case Horde_Date_Recurrence::RECUR_YEARLY_WEEKDAY: $r->type = Horde_ActiveSync_Message_Recurrence::TYPE_YEARLYNTH; $r->dayofweek = $this->_dayOfWeekMap[$recurrence->start->dayOfWeek()]; $r->weekofmonth = ceil($recurrence->start->mday / 7); $r->monthofyear = $recurrence->start->month; break; } if (!empty($recurrence->recurInterval)) { $r->interval = $recurrence->recurInterval; } /* AS messages can only have one or the other (or none), not both */ if ($recurrence->hasRecurCount()) { $r->occurrences = $recurrence->getRecurCount(); } elseif ($recurrence->hasRecurEnd()) { $r->until = $recurrence->getRecurEnd(); } // We don't support non-gregorian calendars. if ($this->_version >= Horde_ActiveSync::VERSION_FOURTEEN) { $r->calendartype = Horde_ActiveSync_Message_Recurrence::CALENDAR_TYPE_GREGORIAN; } $this->_properties['recurrence'] = $r; } /** * Obtain a recurrence object. Note this returns a Horde_Date_Recurrence * object, not Horde_ActiveSync_Message_Recurrence. * * @return Horde_Date_Recurrence */ public function getRecurrence() { if (!$recurrence = $this->_getAttribute('recurrence')) { return false; } $d = clone($this->_getAttribute('starttime')); $d->setTimezone($this->getTimezone()); $rrule = new Horde_Date_Recurrence($d); /* Map MS AS type field to Horde_Date_Recurrence types */ switch ($recurrence->type) { case Horde_ActiveSync_Message_Recurrence::TYPE_DAILY: $rrule->setRecurType(Horde_Date_Recurrence::RECUR_DAILY); break; case Horde_ActiveSync_Message_Recurrence::TYPE_WEEKLY: $rrule->setRecurType(Horde_Date_Recurrence::RECUR_WEEKLY); $rrule->setRecurOnDay($recurrence->dayofweek); break; case Horde_ActiveSync_Message_Recurrence::TYPE_MONTHLY: $rrule->setRecurType(Horde_Date_Recurrence::RECUR_MONTHLY_DATE); break; case Horde_ActiveSync_Message_Recurrence::TYPE_MONTHLY_NTH: $rrule->setRecurType(Horde_Date_Recurrence::RECUR_MONTHLY_WEEKDAY); $rrule->setRecurOnDay($recurrence->dayofweek); break; case Horde_ActiveSync_Message_Recurrence::TYPE_YEARLY: $rrule->setRecurType(Horde_Date_Recurrence::RECUR_YEARLY_DATE); break; case Horde_ActiveSync_Message_Recurrence::TYPE_YEARLYNTH: $rrule->setRecurType(Horde_Date_Recurrence::RECUR_YEARLY_WEEKDAY); $rrule->setRecurOnDay($recurrence->dayofweek); break; } if ($rcnt = $recurrence->occurrences) { $rrule->setRecurCount($rcnt); } if ($runtil = $recurrence->until) { $rrule->setRecurEnd(new Horde_Date($runtil)); } if ($interval = $recurrence->interval) { $rrule->setRecurInterval($interval); } return $rrule; } /** * Add a recurrence exception * * @param Horde_ActiveSync_Message_Exception $exception */ public function addException(Horde_ActiveSync_Message_Exception $exception) { $this->_properties['exceptions'][] = $exception; } /** * Return the exceptions for this appointment. * * @return array An array of Horde_ActiveSync_Message_Exception objects */ public function getExceptions() { return $this->_properties['exceptions']; } /** * Set the sensitivity level for this appointment. * * Should be one of: * normal, personal, private, confidential * * @param integer $sensitivity The SENSITIVITY constant * @deprecated */ public function setSensitivity($sensitivity) { $this->_properties['sensitivity'] = $sensitivity; } /** * Return the sensitivity setting for this appointment * * @return integer The SENSITIVITY constant * @deprecated */ public function getSensitivity() { return $this->_getAttribute('sensitivity'); } /** * Sets the busy status for this appointment * * @param integer $busy The BUSYSTATUS constant * @deprecated */ public function setBusyStatus($busy) { $this->_properties['busystatus'] = $busy; } /** * Return the busy status for this appointment. * * @return integer The BUSYSTATUS constant * @deprecated */ public function getBusyStatus() { return $this->_getAttribute('busystatus'); } /** * Set user response type. Should be one of: * none, organizer, tentative, accepted, declined * * @param integer $response The response type constant * @deprecated */ public function setResponseType($response) { $this->_properties['responsetype'] = $response; } /** * Get response type * * @return integer The responsetype constant * @deprecated */ public function getResponseType() { return $this->_getAttribute('responsetype'); } /** * Set reminder for this appointment. * * @param integer $minutes The number of minutes before appintment to * trigger a reminder. * @deprecated */ public function setReminder($minutes) { $this->_properties['reminder'] = (int)$minutes; } /** * Get the reminder time. * * @return integer|boolean Number of minutes before appointment for * notifications or false if not set. * @deprecated */ public function getReminder() { $reminder = $this->_getAttribute('reminder'); if ($reminder < 0) { return false; } return $reminder; } /** * Set the status for this appointment. Should be one of: * none, meeting, received, canceled, canceledreceived. * * @param integer $status A MEETING_* constant */ public function setMeetingStatus($status) { $this->_properties['meetingstatus'] = $status; } /** * Return the meeting status for this meeting. * * @return integer A MEETING_* constant */ public function getMeetingStatus() { return $this->_getAttribute('meetingstatus', self::MEETING_NOT_MEETING); } /** * Add an attendee to this appointment * * @param array $attendee 'name', 'email' for each attendee */ public function addAttendee($attendee) { /* Both email and name are REQUIRED if setting an attendee */ $this->_properties['attendees'][] = $attendee; } /** * Get a list of this event's attendees * * @return array An array of 'name' and 'email' hashes */ public function getAttendees() { return $this->_properties['attendees']; } /** * Set the appointment's body * * @param string $body UTF-8 encoded string * @deprecated */ public function setBody($body) { $this->_properties['body'] = $body; } /** * Get the appointment's body * * @return string UTF-8 encoded string * @deprecated */ public function getBody() { return $this->_getAttribute('body'); } /** * Add a category to the appointment * * @param string $category */ public function addCategory($category) { $this->_properties['categories'][] = $category; } /** * Return this appointments tags/categories. * * @return array */ public function getCategories() { return $this->_properties['categories']; } /** * Override parent class' method. In EAS 16.0, top level appointment * properties are ALWAYS ghosted if they are not explicitly sent. * * @param string $property The property to check * * @return boolean */ public function isGhosted($property) { if ($this->_version >= Horde_ActiveSync::VERSION_SIXTEEN && empty($this->_exists[$property])) { return true; } return parent::isGhosted($property); } /** * Return the collection class name the object is for. * * @return string */ public function getClass() { return 'Calendar'; } } Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Message/Attachment.php0000664000076500000240000000641212654565405021772 0ustar * @package ActiveSync */ /** * Horde_ActiveSync_Message_Attachment * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2010-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync * * @property string $attmethod The attachment method. * @property integer $attsize The attachment size. * @property string $displayname The attachment's display name. * @property string $attname The attachment's name. * @property string $attoid The ObjectID of the attachment. * @property integer $attremoved @todo */ class Horde_ActiveSync_Message_Attachment extends Horde_ActiveSync_Message_Base { /* Wbxml constants */ const POOMMAIL_ATTNAME = 'POOMMAIL:AttName'; const POOMMAIL_ATTSIZE = 'POOMMAIL:AttSize'; const POOMMAIL_ATTOID = 'POOMMAIL:AttOid'; const POOMMAIL_ATTMETHOD = 'POOMMAIL:AttMethod'; const POOMMAIL_ATTREMOVED = 'POOMMAIL:AttRemoved'; const POOMMAIL_DISPLAYNAME = 'POOMMAIL:DisplayName'; /* Attachement types */ const ATT_TYPE_NORMAL = 1; const ATT_TYPE_EMBEDDED = 5; const ATT_TYPE_OLE = 6; /** * Property mappings * * @var array */ protected $_mapping = array( self::POOMMAIL_ATTMETHOD => array (self::KEY_ATTRIBUTE => 'attmethod'), self::POOMMAIL_ATTSIZE => array (self::KEY_ATTRIBUTE => 'attsize'), self::POOMMAIL_DISPLAYNAME => array (self::KEY_ATTRIBUTE => 'displayname'), self::POOMMAIL_ATTNAME => array (self::KEY_ATTRIBUTE => 'attname'), self::POOMMAIL_ATTOID => array (self::KEY_ATTRIBUTE => 'attoid'), self::POOMMAIL_ATTREMOVED => array (self::KEY_ATTRIBUTE => 'attremoved'), ); /** * Property values * * @var array */ protected $_properties = array( 'attmethod' => false, 'attsize' => false, 'displayname' => false, 'attname' => false, 'attoid' => false, 'attremoved' => false ); /** * Return the message type. * * @return string */ public function getClass() { return 'Attachment'; } } Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Message/Attendee.php0000664000076500000240000000610612654565405021433 0ustar * @package ActiveSync */ /** * Horde_ActiveSync_Message_Attendee * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2010-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync * * @property string $email The attendee's email address. * @property string $name The attendee's name. * @property integer $status The attendee's status (a STATUS_* constant). * @property integer $type The attendee type (a TYPE_* constant) */ class Horde_ActiveSync_Message_Attendee extends Horde_ActiveSync_Message_Base { /* Attendee Type Constants */ const TYPE_REQUIRED = 1; const TYPE_OPTIONAL = 2; const TYPE_RESOURCE = 3; /* Attendee Status */ const STATUS_UNKNOWN = 0; const STATUS_TENTATIVE = 2; const STATUS_ACCEPT = 3; const STATUS_DECLINE = 4; const STATUS_NORESPONSE = 5; /** * Property mapping. * * @var array */ protected $_mapping = array( Horde_ActiveSync_Message_Appointment::POOMCAL_EMAIL => array (self::KEY_ATTRIBUTE => 'email'), Horde_ActiveSync_Message_Appointment::POOMCAL_NAME => array (self::KEY_ATTRIBUTE => 'name'), Horde_ActiveSync_Message_Appointment::POOMCAL_ATTENDEESTATUS => array(self::KEY_ATTRIBUTE => 'status'), Horde_ActiveSync_Message_Appointment::POOMCAL_ATTENDEETYPE => array(self::KEY_ATTRIBUTE => 'type') ); /** * Property values. * * @var array */ protected $_properties = array( 'email' => false, 'name' => false, 'status' => false, 'type' => false ); /** * Give concrete classes the chance to enforce rules on property values. * * @return boolean True on success, otherwise false. */ protected function _validateDecodedValues() { if ($this->_version == Horde_ActiveSync::VERSION_SIXTEEN && !empty($this->_properties['status'])) { return false; } return true; } }Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Message/Base.php0000664000076500000240000006321412654565405020557 0ustar * @package ActiveSync */ /** * Horde_ActiveSync_Message_Base:: Base class for all ActiveSync message * objects. * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2009-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync */ class Horde_ActiveSync_Message_Base { /* Attribute Keys */ const KEY_ATTRIBUTE = 1; const KEY_VALUES = 2; const KEY_TYPE = 3; const KEY_PROPERTY = 4; /* Types */ const TYPE_DATE = 1; const TYPE_HEX = 2; const TYPE_DATE_DASHES = 3; const TYPE_MAPI_STREAM = 4; const TYPE_MAPI_GOID = 5; const PROPERTY_NO_CONTAINER = 7; /** * Holds the mapping for object properties * * @var array */ protected $_mapping; /** * Holds property values * * @var array */ protected $_properties = array(); /** * Message flags * * @var Horde_ActiveSync::FLAG_* constant */ public $flags = false; /** * Request type. One of: Horde_ActiveSync::SYNC_ADD, SYNC_MODIFY, * SYNC_REMOVE, or SYNC_FETCH. Used internally for enforcing various * protocol rules depending on request. @since 2.31.0 * * @var string */ public $commandType; /** * Logger * * @var Horde_Log_Logger */ protected $_logger; /** * An array describing the non-ghosted elements this message supports. * * @var array */ protected $_supported = array(); /** * Existence cache, used for working with ghosted properties. * * @var array */ protected $_exists = array(); /** * The version of EAS we are to support. * * @var float */ protected $_version = Horde_ActiveSync::VERSION_TWOFIVE; /** * The device object * * @var Horde_ActiveSync_Device * @since 2.9.2 */ protected $_device; /** * Cache of current stream filters. * * @var array */ protected $_streamFilters = array(); /** * Const'r * * @param array $options Configuration options for the message: * - logger: (Horde_Log_Logger) A logger instance * DEFAULT: none (No logging). * - protocolversion: (float) The version of EAS to support. * DEFAULT: Horde_ActiveSync::VERSION_TWOFIVE (2.5) * - device: (Horde_ActiveSync_Device) The device object. @since 2.9.2 * * @return Horde_ActiveSync_Message_Base */ public function __construct(array $options = array()) { if (!empty($options['logger'])) { $this->_logger = $options['logger']; } else { $this->_logger = new Horde_Log_Logger(new Horde_Log_Handler_Null()); } if (!empty($options['protocolversion'])) { $this->_version = $options['protocolversion']; } if (!empty($options['device'])) { $this->_device = $options['device']; } } public function __destruct() { foreach ($this->_streamFilters as $filter) { stream_filter_remove($filter); } } /** * Return the EAS version this object supports. * * @return float A Horde_ActiveSync::VERSION_* constant. */ public function getProtocolVersion() { return $this->_version; } /** * Check the existence of a property in this message. * * @param string $property The property name * * @return boolean */ public function propertyExists($property) { return array_key_exists($property, $this->_properties); } /** * Accessor * * @param string $property Property to get. * * @return mixed The value of the requested property. * @todo: Return boolean false if not set. Not BC to change it. */ public function &__get($property) { if ($this->_properties[$property] !== false) { return $this->_properties[$property]; } else { $string = ''; return $string; } } /** * Setter * * @param string $property The property to set. * @param mixed $value The value to set it to. * * @throws InvalidArgumentException */ public function __set($property, $value) { if (!array_key_exists($property, $this->_properties)) { $this->_logger->err('Unknown property: ' . $property); throw new InvalidArgumentException(get_class($this) . ' Unknown property: ' . $property); } $this->_properties[$property] = $value; $this->_exists[$property] = true; } /** * Give concrete classes the chance to enforce rules. * * @return boolean True on success, otherwise false. * @since 2.31.0 */ protected function _validateDecodedValues() { return true; } /** * Give concrete classes the chance to enforce rules before encoding * messages to send to the client. * * @return boolean True if values were valid (or could be made valid). * False if values are unable to be validated. * @since 2.31.0 */ protected function _preEncodeValidation() { return true; } /** * Magic caller method. * * @param mixed $method The method to call. * @param array $arg Method arguments. * * @return mixed */ public function __call($method, $arg) { /* Support calling set{Property}() */ if (strpos($method, 'set') === 0) { $property = Horde_String::lower(substr($method, 3)); $this->_properties[$property] = $arg; } elseif (strpos($method, 'get') === 0) { return $this->_getAttribute(Horde_String::lower(substr($method, 3))); } throw new BadMethodCallException('Unknown method: ' . $method . ' in class: ' . __CLASS__); } /** * Magic method. * * @param string $property The property name to check. * * @return boolean. */ public function __isset($property) { return isset($this->_properties[$property]); } /** * Set the list of non-ghosted fields for this message. * * @param array $fields The array of fields, keyed by the fully qualified * property name i.e., POOMCONTACTS:Anniversary. To * signify an empty SUPPORTED container $fields should * contain a single element equal to * Horde_ActiveSync::ALL_GHOSTED. */ public function setSupported(array $fields) { $this->_supported = array(); if (current($fields) == Horde_ActiveSync::ALL_GHOSTED) { $this->_supported = $fields; return; } foreach ($fields as $field) { $this->_supported[] = $this->_mapping[$field][self::KEY_ATTRIBUTE]; } } /** * Get the list of non-ghosted properties for this message. * * @return array The array of non-ghosted properties */ public function getSupported() { return $this->_supported; } /** * Determines if the property specified has been ghosted by the client. * A property is ghosted if it is NOT listed in the SUPPORTED list sent * by the client AND is NOT present in the request data. * * @param string $property The property to check * * @return boolean */ public function isGhosted($property) { // MS-ASCMD 2.2.3.168: // An empty SUPPORTED container indicates that ALL elements able to be // ghosted ARE ghosted. A *missing* SUPPORTED tag indicates that NO // fields are ghosted - any ghostable properties are always considered // NOT ghosted. if (empty($this->_supported)) { return false; } if (current($this->_supported) == Horde_ActiveSync::ALL_GHOSTED && empty($this->_exists[$property])) { return true; } return array_search($property, $this->_supported) === false && empty($this->_exists[$property]); } /** * Recursively decodes the WBXML from input stream. This means that if this * message contains complex types (like Appointment.Recuurence for example) * the sub-objects are auto-instantiated and decoded as well. Places the * decoded objects in the local properties array. * * @param Horde_ActiveSync_Wbxml_Decoder The stream decoder * * @throws Horde_ActiveSync_Exception */ public function decodeStream(Horde_ActiveSync_Wbxml_Decoder &$decoder) { while (1) { $entity = $decoder->getElement(); if ($entity[Horde_ActiveSync_Wbxml::EN_TYPE] == Horde_ActiveSync_Wbxml::EN_TYPE_STARTTAG) { if (!($entity[Horde_ActiveSync_Wbxml::EN_FLAGS] & Horde_ActiveSync_Wbxml::EN_FLAGS_CONTENT)) { $map = $this->_mapping[$entity[Horde_ActiveSync_Wbxml::EN_TAG]]; if (!isset($map[self::KEY_TYPE])) { $this->{$map[self::KEY_ATTRIBUTE]} = ''; } elseif ($map[self::KEY_TYPE] == self::TYPE_DATE || $map[self::KEY_TYPE] == self::TYPE_DATE_DASHES ) { $this->{$map[self::KEY_ATTRIBUTE]} = ''; } continue; } // Found start tag if (!isset($this->_mapping[$entity[Horde_ActiveSync_Wbxml::EN_TAG]])) { $this->_logger->err(sprintf( 'Tag %s unexpected in type XML type %s.', $entity[Horde_ActiveSync_Wbxml::EN_TAG], get_class($this)) ); throw new Horde_ActiveSync_Exception('Unexpected tag'); } else { $map = $this->_mapping[$entity[Horde_ActiveSync_Wbxml::EN_TAG]]; if (isset($map[self::KEY_VALUES])) { // Handle arrays of attribute values while (1) { // Do not get start tag for an array without a container if (!(isset($map[self::KEY_PROPERTY]) && $map[self::KEY_PROPERTY] == self::PROPERTY_NO_CONTAINER) && !$decoder->getElementStartTag($map[self::KEY_VALUES])) { break; } if (isset($map[self::KEY_TYPE])) { $class = $map[self::KEY_TYPE]; $decoded = new $class(array( 'protocolversion' => $this->_version, 'logger' => $this->_logger) ); $decoded->commandType = $this->commandType; $decoded->decodeStream($decoder); } else { $decoded = $decoder->getElementContent(); } if (!isset($this->{$map[self::KEY_ATTRIBUTE]})) { $this->{$map[self::KEY_ATTRIBUTE]} = array($decoded); } else { $this->{$map[self::KEY_ATTRIBUTE]}[] = $decoded; } if (!$decoder->getElementEndTag()) { throw new Horde_ActiveSync_Exception('Missing expected wbxml end tag'); } if (isset($map[self::KEY_PROPERTY]) && $map[self::KEY_PROPERTY] == self::PROPERTY_NO_CONTAINER) { $e = $decoder->peek(); // Go back to the initial while if another block // of a non-container element is found. if ($e[Horde_ActiveSync_Wbxml::EN_TYPE] == Horde_ActiveSync_Wbxml::EN_TYPE_STARTTAG) { continue 2; } // Break on end tag because no other container // elements block end is reached. if ($e[Horde_ActiveSync_Wbxml::EN_TYPE] == Horde_ActiveSync_Wbxml::EN_TYPE_ENDTAG || empty($e)) { break; } } } // Do not get container end tag for an array without a container if (!(isset($map[self::KEY_PROPERTY]) && $map[self::KEY_PROPERTY] == self::PROPERTY_NO_CONTAINER) && !$decoder->getElementEndTag()) { return false; } } else { // Handle a simple attribute value if (isset($map[self::KEY_TYPE])) { // Complex type, decode recursively if ($map[self::KEY_TYPE] == self::TYPE_DATE || $map[self::KEY_TYPE] == self::TYPE_DATE_DASHES) { $decoded = $this->_parseDate($decoder->getElementContent()); } elseif ($map[self::KEY_TYPE] == self::TYPE_HEX) { $decoded = self::hex2bin($decoder->getElementContent()); } else { $class = $map[self::KEY_TYPE]; $subdecoder = new $class(array( 'protocolversion' => $this->_version, 'logger' => $this->_logger) ); $subdecoder->commandType = $this->commandType; $subdecoder->decodeStream($decoder); $decoded = $subdecoder; } } else { // Simple type, just get content $decoded = $decoder->getElementContent(); if ($decoded === false) { $decoded = ''; $this->_logger->notice(sprintf( 'Unable to get expected content for %s: Setting to an empty string.', $entity[Horde_ActiveSync_Wbxml::EN_TAG]) ); } } if (!$decoder->getElementEndTag()) { $this->_logger->err(sprintf( 'Unable to get end tag for %s.', $entity[Horde_ActiveSync_Wbxml::EN_TAG]) ); throw new Horde_ActiveSync_Exception('Missing expected wbxml end tag'); } $this->{$map[self::KEY_ATTRIBUTE]} = $decoded; } } } elseif ($entity[Horde_ActiveSync_Wbxml::EN_TYPE] == Horde_ActiveSync_Wbxml::EN_TYPE_ENDTAG) { $decoder->_ungetElement($entity); break; } else { $this->_logger->err('Unexpected content in type'); break; } } if (!$this->_validateDecodedValues()) { throw new Horde_ActiveSync_Exception(sprintf( 'Invalid values detected in %s.', get_class($this)) ); } } /** * Encodes this object (and any sub-objects) as wbxml to the output stream. * Output is ordered according to $_mapping * * @param Horde_ActiveSync_Wbxml_Encoder $encoder The wbxml stream encoder */ public function encodeStream(Horde_ActiveSync_Wbxml_Encoder &$encoder) { if (!$this->_preEncodeValidation()) { throw new Horde_ActiveSync_Exception(sprintf( 'Pre-encoding validation failded for %s item', get_class($this)) ); } foreach ($this->_mapping as $tag => $map) { if (isset($this->{$map[self::KEY_ATTRIBUTE]})) { // Variable is available if (is_object($this->{$map[self::KEY_ATTRIBUTE]}) && !($this->{$map[self::KEY_ATTRIBUTE]} instanceof Horde_Date)) { // Objects can do their own encoding $encoder->startTag($tag); $this->{$map[self::KEY_ATTRIBUTE]}->encodeStream($encoder); $encoder->endTag(); } elseif (isset($map[self::KEY_VALUES]) && is_array($this->{$map[self::KEY_ATTRIBUTE]})) { // Array of objects. Note that some array values must be // send as an empty tag if they contain no elements. if (count($this->{$map[self::KEY_ATTRIBUTE]})) { if (!isset($map[self::KEY_PROPERTY]) || $map[self::KEY_PROPERTY] != self::PROPERTY_NO_CONTAINER) { $encoder->startTag($tag); } foreach ($this->{$map[self::KEY_ATTRIBUTE]} as $element) { if (is_object($element)) { // Outputs object container (eg Attachment) $encoder->startTag($map[self::KEY_VALUES]); $element->encodeStream($encoder); $encoder->endTag(); } else { // Do not ever output empty items here if(strlen($element) > 0) { $encoder->startTag($map[self::KEY_VALUES]); $encoder->content($element); $encoder->endTag(); } } } if (!isset($map[self::KEY_PROPERTY]) || $map[self::KEY_PROPERTY] != self::PROPERTY_NO_CONTAINER) { $encoder->endTag(); } } elseif ($this->_checkSendEmpty($tag)) { $encoder->startTag($tag, null, true); } } else { // Simple type if (!is_resource($this->{$map[self::KEY_ATTRIBUTE]}) && strlen($this->{$map[self::KEY_ATTRIBUTE]}) == 0) { // Do not output empty items except for the following: if ($this->_checkSendEmpty($tag)) { $encoder->startTag($tag, $this->{$map[self::KEY_ATTRIBUTE]}, true); } continue; } elseif ($encoder->multipart && in_array($tag, array( Horde_ActiveSync::SYNC_DATA, Horde_ActiveSync::AIRSYNCBASE_DATA, Horde_ActiveSync_Request_ItemOperations::ITEMOPERATIONS_DATA) )) { $this->_logger->info('HANDLING MULTIPART OUTPUT'); $encoder->addPart($this->{$map[self::KEY_ATTRIBUTE]}); $encoder->startTag(Horde_ActiveSync_Request_ItemOperations::ITEMOPERATIONS_PART); $encoder->content((string)(count($encoder->getParts()) - 1)); $encoder->endTag(); continue; } $encoder->startTag($tag); if (isset($map[self::KEY_TYPE]) && ($map[self::KEY_TYPE] == self::TYPE_DATE || $map[self::KEY_TYPE] == self::TYPE_DATE_DASHES)) { if (!empty($this->{$map[self::KEY_ATTRIBUTE]})) { // don't output 1-1-1970 $encoder->content($this->_formatDate($this->{$map[self::KEY_ATTRIBUTE]}, $map[self::KEY_TYPE])); } } elseif (isset($map[self::KEY_TYPE]) && $map[self::KEY_TYPE] == self::TYPE_HEX) { $encoder->content(Horde_String::upper(bin2hex($this->{$map[self::KEY_ATTRIBUTE]}))); } elseif (isset($map[self::KEY_TYPE]) && $map[self::KEY_TYPE] == self::TYPE_MAPI_STREAM) { $encoder->content($this->{$map[self::KEY_ATTRIBUTE]}); } else { $encoder->content( $this->_checkEncoding($this->{$map[self::KEY_ATTRIBUTE]}, $tag)); } $encoder->endTag(); } } } } /** * Checks if the data needs to be encoded like e.g., when outputing binary * data in-line during ITEMOPERATIONS requests. Concrete classes should * override this if needed. * * @param mixed $data The data to check. A string or stream resource. * @param string $tag The tag we are outputing. * * @return mixed The encoded data. A string or stream resource with * a filter attached. */ protected function _checkEncoding($data, $tag) { if (is_resource($data)) { stream_filter_register('horde_null', 'Horde_Stream_Filter_Null'); stream_filter_register('horde_eol', 'Horde_Stream_Filter_Eol'); $this->_streamFilters[] = stream_filter_prepend($data, 'horde_null', STREAM_FILTER_READ); $this->_streamFilters[] = stream_filter_prepend($data, 'horde_eol', STREAM_FILTER_READ); } return $data; } /** * Checks to see if we should send an empty value. * * @param string $tag The tag name * * @return boolean */ protected function _checkSendEmpty($tag) { return false; } /** * Helper method to allow default values for unset properties. * * @param string $name The property name * @param stting $default The default value to return if $property is empty * * @return mixed */ protected function _getAttribute($name, $default = null) { if ((!is_array($this->_properties[$name]) && $this->_properties[$name] !== false) || is_array($this->_properties[$name])) { return $this->_properties[$name]; } else { return $default; } } /** * Oh yeah. This is beautiful. Exchange outputs date fields differently in * calendar items and emails. We could just always send one or the other, * but unfortunately nokia's 'Mail for exchange' depends on this quirk. * So we have to send a different date type depending on where it's used. * Used when encoding a date value to send to the client. * * @param Horde_Date $dt The Horde_Date object to format * (should normally be in local tz). * @param integer $type The type to format as (TYPE_DATE or TYPE_DATE_DASHES) * * @return string The formatted date */ protected function _formatDate(Horde_Date $dt, $type) { if ($type == Horde_ActiveSync_Message_Base::TYPE_DATE) { return $dt->setTimezone('UTC')->format('Ymd\THis\Z'); } elseif ($type == Horde_ActiveSync_Message_Base::TYPE_DATE_DASHES) { return $dt->setTimezone('UTC')->format('Y-m-d\TH:i:s\.000\Z'); } } /** * Get a Horde_Date from a timestamp, ensuring it's in the correct format. * Used when decoding an incoming date value from the client. * * @param string $ts The timestamp * * @return Horde_Date|boolean The Horde_Date or false if unable to decode. */ protected function _parseDate($ts) { if (preg_match("/(\d{4})[^0-9]*(\d{2})[^0-9]*(\d{2})(T(\d{2})[^0-9]*(\d{2})[^0-9]*(\d{2})(.\d+)?Z){0,1}$/", $ts, $matches)) { try { return new Horde_Date($ts); } catch (Horde_Date_Exception $e) {} } return false; } /** * Function which converts a hex entryid to a binary entryid. * * @param string $data The hexadecimal string * * @return string The binary data */ private static function hex2bin($data) { $len = strlen($data); $newdata = ''; for($i = 0; $i < $len; $i += 2) { $newdata .= pack('C', hexdec(substr($data, $i, 2))); } return $newdata; } }Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Message/Contact.php0000664000076500000240000005134412654565405021301 0ustar * @package ActiveSync */ /** * Horde_ActiveSync_Message_Contact:: * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2010-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync * * @property Horde_Date $anniversary * @property string $assistantname * @property string $assistnamephonenumber * @property Horde_Date $birthday * @property string $business2phonenumber * @property string $businesscity * @property string $businesscountry * @property string $businesspostalcode * @property string $businessstate * @property string $businessstreet * @property string $businessfaxnumber * @property string $businessphonenumber * @property string $carphonenumber * @property array $categories * @property array $children * @property string $companyname * @property string $department * @property string $email1address * @property string $email2address * @property string $email3address * @property string $fileas * @property string $firstname * @property string $home2phonenumber * @property string $homecity * @property string $homecountry * @property string $homepostalcode * @property string $homestate * @property string $homestreet * @property string $homefaxnumber * @property string $homephonenumber * @property string $jobtitle * @property string $lastname * @property string $middlename * @property string $mobilephonenumber * @property string $officelocation * @property string $othercity * @property string $othercountry * @property string $otherpostalcode * @property string $otherstate * @property string $otherstreet * @property string $pagernumber * @property string $radiophonenumber * @property string $spouse * @property string $suffix * @property string $title * @property string $webpage * @property string $yomicompanyname * @property string $yomifirstname * @property string $yomilastname * @property string $picture * @property string $customerid * @property string $governmentid * @property string $imaddress * @property string $imaddress2 * @property string $imaddress3 * @property string $managername * @property string $companymainphone * @property string $accountname * @property string $nickname * @property string $mms * @property string $alias (EAS >= 14.0 only) * @property string $weightedrank (EAS >= 14.0 only) * @property string $body (EAS 2.5 only) * @property integer $bodysize (EAS 2.5 only) * @property integer $bodytruncated (EAS 2.5 only) * @property integer $rtf (EAS 2.5 only) * @property Horde_ActiveSync_Message_AirSyncBaseBody $airsyncbasebody (EAS >= 12.0 only) */ class Horde_ActiveSync_Message_Contact extends Horde_ActiveSync_Message_Base { /* POOMCONTACTS */ const ANNIVERSARY = 'POOMCONTACTS:Anniversary'; const ASSISTANTNAME = 'POOMCONTACTS:AssistantName'; const ASSISTNAMEPHONENUMBER = 'POOMCONTACTS:AssistnamePhoneNumber'; const BIRTHDAY = 'POOMCONTACTS:Birthday'; const BODY = 'POOMCONTACTS:Body'; const BODYSIZE = 'POOMCONTACTS:BodySize'; const BODYTRUNCATED = 'POOMCONTACTS:BodyTruncated'; const BUSINESS2PHONENUMBER = 'POOMCONTACTS:Business2PhoneNumber'; const BUSINESSCITY = 'POOMCONTACTS:BusinessCity'; const BUSINESSCOUNTRY = 'POOMCONTACTS:BusinessCountry'; const BUSINESSPOSTALCODE = 'POOMCONTACTS:BusinessPostalCode'; const BUSINESSSTATE = 'POOMCONTACTS:BusinessState'; const BUSINESSSTREET = 'POOMCONTACTS:BusinessStreet'; const BUSINESSFAXNUMBER = 'POOMCONTACTS:BusinessFaxNumber'; const BUSINESSPHONENUMBER = 'POOMCONTACTS:BusinessPhoneNumber'; const CARPHONENUMBER = 'POOMCONTACTS:CarPhoneNumber'; const CATEGORIES = 'POOMCONTACTS:Categories'; const CATEGORY = 'POOMCONTACTS:Category'; const CHILDREN = 'POOMCONTACTS:Children'; const CHILD = 'POOMCONTACTS:Child'; const COMPANYNAME = 'POOMCONTACTS:CompanyName'; const DEPARTMENT = 'POOMCONTACTS:Department'; const EMAIL1ADDRESS = 'POOMCONTACTS:Email1Address'; const EMAIL2ADDRESS = 'POOMCONTACTS:Email2Address'; const EMAIL3ADDRESS = 'POOMCONTACTS:Email3Address'; const FILEAS = 'POOMCONTACTS:FileAs'; const FIRSTNAME = 'POOMCONTACTS:FirstName'; const HOME2PHONENUMBER = 'POOMCONTACTS:Home2PhoneNumber'; const HOMECITY = 'POOMCONTACTS:HomeCity'; const HOMECOUNTRY = 'POOMCONTACTS:HomeCountry'; const HOMEPOSTALCODE = 'POOMCONTACTS:HomePostalCode'; const HOMESTATE = 'POOMCONTACTS:HomeState'; const HOMESTREET = 'POOMCONTACTS:HomeStreet'; const HOMEFAXNUMBER = 'POOMCONTACTS:HomeFaxNumber'; const HOMEPHONENUMBER = 'POOMCONTACTS:HomePhoneNumber'; const JOBTITLE = 'POOMCONTACTS:JobTitle'; const LASTNAME = 'POOMCONTACTS:LastName'; const MIDDLENAME = 'POOMCONTACTS:MiddleName'; const MOBILEPHONENUMBER = 'POOMCONTACTS:MobilePhoneNumber'; const OFFICELOCATION = 'POOMCONTACTS:OfficeLocation'; const OTHERCITY = 'POOMCONTACTS:OtherCity'; const OTHERCOUNTRY = 'POOMCONTACTS:OtherCountry'; const OTHERPOSTALCODE = 'POOMCONTACTS:OtherPostalCode'; const OTHERSTATE = 'POOMCONTACTS:OtherState'; const OTHERSTREET = 'POOMCONTACTS:OtherStreet'; const PAGERNUMBER = 'POOMCONTACTS:PagerNumber'; const RADIOPHONENUMBER = 'POOMCONTACTS:RadioPhoneNumber'; const SPOUSE = 'POOMCONTACTS:Spouse'; const SUFFIX = 'POOMCONTACTS:Suffix'; const TITLE = 'POOMCONTACTS:Title'; const WEBPAGE = 'POOMCONTACTS:WebPage'; const YOMICOMPANYNAME = 'POOMCONTACTS:YomiCompanyName'; const YOMIFIRSTNAME = 'POOMCONTACTS:YomiFirstName'; const YOMILASTNAME = 'POOMCONTACTS:YomiLastName'; const RTF = 'POOMCONTACTS:Rtf'; const PICTURE = 'POOMCONTACTS:Picture'; /* POOMCONTACTS2 */ const CUSTOMERID = 'POOMCONTACTS2:CustomerId'; const GOVERNMENTID = 'POOMCONTACTS2:GovernmentId'; const IMADDRESS = 'POOMCONTACTS2:IMAddress'; const IMADDRESS2 = 'POOMCONTACTS2:IMAddress2'; const IMADDRESS3 = 'POOMCONTACTS2:IMAddress3'; const MANAGERNAME = 'POOMCONTACTS2:ManagerName'; const COMPANYMAINPHONE = 'POOMCONTACTS2:CompanyMainPhone'; const ACCOUNTNAME = 'POOMCONTACTS2:AccountName'; const NICKNAME = 'POOMCONTACTS2:NickName'; const MMS = 'POOMCONTACTS2:MMS'; /* EAS 14 (Only used in Recipient Information Cache responses) */ const ALIAS = 'POOMCONTACTS:Alias'; const WEIGHTEDRANK = 'POOMCONTACTS:WeightedRank'; public $categories = array(); /** * Property mapping. * * @var array */ protected $_mapping = array( self::ANNIVERSARY => array(self::KEY_ATTRIBUTE => 'anniversary', self::KEY_TYPE => self::TYPE_DATE_DASHES), self::ASSISTANTNAME => array(self::KEY_ATTRIBUTE => 'assistantname'), self::ASSISTNAMEPHONENUMBER => array(self::KEY_ATTRIBUTE => 'assistnamephonenumber'), self::BIRTHDAY => array(self::KEY_ATTRIBUTE => 'birthday', self::KEY_TYPE => self::TYPE_DATE_DASHES), self::BUSINESS2PHONENUMBER => array(self::KEY_ATTRIBUTE => 'business2phonenumber'), self::BUSINESSCITY => array(self::KEY_ATTRIBUTE => 'businesscity'), self::BUSINESSCOUNTRY => array(self::KEY_ATTRIBUTE => 'businesscountry'), self::BUSINESSPOSTALCODE => array(self::KEY_ATTRIBUTE => 'businesspostalcode'), self::BUSINESSSTATE => array(self::KEY_ATTRIBUTE => 'businessstate'), self::BUSINESSSTREET => array(self::KEY_ATTRIBUTE => 'businessstreet'), self::BUSINESSFAXNUMBER => array(self::KEY_ATTRIBUTE => 'businessfaxnumber'), self::BUSINESSPHONENUMBER => array(self::KEY_ATTRIBUTE => 'businessphonenumber'), self::CARPHONENUMBER => array(self::KEY_ATTRIBUTE => 'carphonenumber'), self::CHILDREN => array(self::KEY_ATTRIBUTE => 'children', self::KEY_VALUES => self::CHILD), self::COMPANYNAME => array(self::KEY_ATTRIBUTE => 'companyname'), self::DEPARTMENT => array(self::KEY_ATTRIBUTE => 'department'), self::EMAIL1ADDRESS => array(self::KEY_ATTRIBUTE => 'email1address'), self::EMAIL2ADDRESS => array(self::KEY_ATTRIBUTE => 'email2address'), self::EMAIL3ADDRESS => array(self::KEY_ATTRIBUTE => 'email3address'), self::FILEAS => array(self::KEY_ATTRIBUTE => 'fileas'), self::FIRSTNAME => array(self::KEY_ATTRIBUTE => 'firstname'), self::HOME2PHONENUMBER => array(self::KEY_ATTRIBUTE => 'home2phonenumber'), self::HOMECITY => array(self::KEY_ATTRIBUTE => 'homecity'), self::HOMECOUNTRY => array(self::KEY_ATTRIBUTE => 'homecountry'), self::HOMEPOSTALCODE => array(self::KEY_ATTRIBUTE => 'homepostalcode'), self::HOMESTATE => array(self::KEY_ATTRIBUTE => 'homestate'), self::HOMESTREET => array(self::KEY_ATTRIBUTE => 'homestreet'), self::HOMEFAXNUMBER => array(self::KEY_ATTRIBUTE => 'homefaxnumber'), self::HOMEPHONENUMBER => array(self::KEY_ATTRIBUTE => 'homephonenumber'), self::JOBTITLE => array(self::KEY_ATTRIBUTE => 'jobtitle'), self::LASTNAME => array(self::KEY_ATTRIBUTE => 'lastname'), self::MIDDLENAME => array(self::KEY_ATTRIBUTE => 'middlename'), self::MOBILEPHONENUMBER => array(self::KEY_ATTRIBUTE => 'mobilephonenumber'), self::OFFICELOCATION => array(self::KEY_ATTRIBUTE => 'officelocation'), self::OTHERCITY => array(self::KEY_ATTRIBUTE => 'othercity'), self::OTHERCOUNTRY => array(self::KEY_ATTRIBUTE => 'othercountry'), self::OTHERPOSTALCODE => array(self::KEY_ATTRIBUTE => 'otherpostalcode'), self::OTHERSTATE => array(self::KEY_ATTRIBUTE => 'otherstate'), self::OTHERSTREET => array(self::KEY_ATTRIBUTE => 'otherstreet'), self::PAGERNUMBER => array(self::KEY_ATTRIBUTE => 'pagernumber'), self::RADIOPHONENUMBER => array(self::KEY_ATTRIBUTE => 'radiophonenumber'), self::SPOUSE => array(self::KEY_ATTRIBUTE => 'spouse'), self::SUFFIX => array(self::KEY_ATTRIBUTE => 'suffix'), self::TITLE => array(self::KEY_ATTRIBUTE => 'title'), self::WEBPAGE => array(self::KEY_ATTRIBUTE => 'webpage'), self::YOMICOMPANYNAME => array(self::KEY_ATTRIBUTE => 'yomicompanyname'), self::YOMIFIRSTNAME => array(self::KEY_ATTRIBUTE => 'yomifirstname'), self::YOMILASTNAME => array(self::KEY_ATTRIBUTE => 'yomilastname'), self::PICTURE => array(self::KEY_ATTRIBUTE => 'picture'), self::CATEGORIES => array(self::KEY_ATTRIBUTE => 'categories', self::KEY_VALUES => self::CATEGORY), // POOMCONTACTS2 self::CUSTOMERID => array(self::KEY_ATTRIBUTE => 'customerid'), self::GOVERNMENTID => array(self::KEY_ATTRIBUTE => 'governmentid'), self::IMADDRESS => array(self::KEY_ATTRIBUTE => 'imaddress'), self::IMADDRESS2 => array(self::KEY_ATTRIBUTE => 'imaddress2'), self::IMADDRESS3 => array(self::KEY_ATTRIBUTE => 'imaddress3'), self::MANAGERNAME => array(self::KEY_ATTRIBUTE => 'managername'), self::COMPANYMAINPHONE => array(self::KEY_ATTRIBUTE => 'companymainphone'), self::ACCOUNTNAME => array(self::KEY_ATTRIBUTE => 'accountname'), self::NICKNAME => array(self::KEY_ATTRIBUTE => 'nickname'), self::MMS => array(self::KEY_ATTRIBUTE => 'mms'), ); /** * Property values. * * @var array */ protected $_properties = array( 'anniversary' => false, 'assistantname' => false, 'assistnamephonenumber' => false, 'birthday' => false, 'business2phonenumber' => false, 'businesscity' => false, 'businesscountry' => false, 'businesspostalcode' => false, 'businessstate' => false, 'businessstreet' => false, 'businessfaxnumber' => false, 'businessphonenumber' => false, 'carphonenumber' => false, 'children' => array(), 'companyname' => false, 'department' => false, 'email1address' => false, 'email2address' => false, 'email3address' => false, 'fileas' => false, 'firstname' => false, 'home2phonenumber' => false, 'homecity' => false, 'homecountry' => false, 'homepostalcode' => false, 'homestate' => false, 'homestreet' => false, 'homefaxnumber' => false, 'homephonenumber' => false, 'jobtitle' => false, 'lastname' => false, 'middlename' => false, 'mobilephonenumber' => false, 'officelocation' => false, 'othercity' => false, 'othercountry' => false, 'otherpostalcode' => false, 'otherstate' => false, 'otherstreet' => false, 'pagernumber' => false, 'radiophonenumber' => false, 'spouse' => false, 'suffix' => false, 'title' => false, 'webpage' => false, 'yomicompanyname' => false, 'yomifirstname' => false, 'yomilastname' => false, 'picture' => false, 'categories' => false, // POOMCONTACTS2 'customerid' => false, 'governmentid' => false, 'imaddress' => false, 'imaddress2' => false, 'imaddress3' => false, 'managername' => false, 'companymainphone' => false, 'accountname' => false, 'nickname' => false, 'mms' => false, ); /** * Const'r * * @see Horde_ActiveSync_Message_Base::__construct() */ public function __construct(array $options = array()) { parent::__construct($options); if ($this->_version < Horde_ActiveSync::VERSION_TWELVE) { $this->_mapping += array( self::BODY => array(self::KEY_ATTRIBUTE => 'body'), self::BODYSIZE => array(self::KEY_ATTRIBUTE => 'bodysize'), self::BODYTRUNCATED => array(self::KEY_ATTRIBUTE => 'bodytruncated'), self::RTF => array(self::KEY_ATTRIBUTE => 'rtf'), ); $this->_properties += array( 'body' => false, 'bodysize' => false, 'bodytruncated' => 0, 'rtf' => false ); } else { $this->_mapping += array( Horde_ActiveSync::AIRSYNCBASE_BODY => array(self::KEY_ATTRIBUTE => 'airsyncbasebody', self::KEY_TYPE => 'Horde_ActiveSync_Message_AirSyncBaseBody') ); $this->_properties += array( 'airsyncbasebody' => false ); if ($this->_version > Horde_ActiveSync::VERSION_TWELVEONE) { $this->_mapping += array( self::ALIAS => array(self::KEY_ATTRIBUTE => 'alias'), self::WEIGHTEDRANK => array(self::KEY_ATTRIBUTE => 'weightedrank') ); $this->_properties += array( 'alias' => false, 'weightedrank' => false ); } } } /** * Return message type * * @return string */ public function getClass() { return 'Contacts'; } /** * Check if we should send a specific property even if it's empty. * * @param string $tag The property tag. * * @return boolean */ protected function _checkSendEmpty($tag) { if ($tag == self::BODYTRUNCATED && $this->bodysize > 0) { return true; } return false; } /** * Override parent class so we can normalize the Date object before * returning it. * * @param string $ts The timestamp * * @return Horde_Date|boolean The Horde_Date object (UTC) or false if * unable to parse the date. */ protected function _parseDate($ts) { $date = parent::_parseDate($ts); // Since some clients send the date as YYYY-MM-DD only, the best we can // do is assume that it is in the same timezone as the user's default // timezone - so convert it to UTC and be done with it. if ($date->timezone != 'UTC') { $date->setTimezone('UTC'); } // @todo: Remove this in H6. if (empty($this->_device)) { return $date; } return $this->_device->normalizePoomContactsDates($date); } /** * Format a date string for sending to the EAS client. * * @param Horde_Date $dt The Horde_Date object to format * (should normally be in local tz). * @param integer $type The type to format as (TYPE_DATE or TYPE_DATE_DASHES) * * @return string The formatted date */ protected function _formatDate(Horde_Date $dt, $type) { if (empty($this->_device)) { $date = $dt; } else { $date = $this->_device->normalizePoomContactsDates($dt, true); } return parent::_formatDate($date, $type); } /** * Determines if the property specified has been ghosted by the client. * A property is ghosted if it is NOT listed in the SUPPORTED list sent * by the client AND is NOT present in the request data. * * @param string $property The property to check * * @return boolean */ public function isGhosted($property) { // MS-ASCMD 2.2.3.168: // An empty SUPPORTED container indicates that ALL elements able to be // ghosted ARE ghosted. A *missing* SUPPORTED tag indicates that NO // fields are ghosted - any ghostable properties are always considered // NOT ghosted. Some clients like iOS 4.x screw this up by not sending // any SUPPORTED container and also not sending the picture field during // edits. if ($property == $this->_mapping[self::PICTURE][self::KEY_ATTRIBUTE] && empty($this->_exists[$property]) && empty($this->_supported) && $this->_device->hasQuirk(Horde_ActiveSync_Device::QUIRK_NEEDS_SUPPORTED_PICTURE_TAG)) { return true; } return parent::isGhosted($property); } } Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Message/DeviceInformation.php0000664000076500000240000000574512654565405023317 0ustar * @package ActiveSync * @since 2.21.0 */ /** * Horde_ActiveSync_Message_DeviceInformation:: * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2010-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync * @since 2.21.0 * * @property string $model * @property string $imei * @property string $friendlyname * @property string $os * @property string $oslanguage * @property string $phonenumber * @property string $useragent * @property string $mobileoperator * @property string $enableoutboundsms */ class Horde_ActiveSync_Message_DeviceInformation extends Horde_ActiveSync_Message_Base { /** * Property mapping * * @var array */ protected $_mapping = array ( Horde_ActiveSync_Request_Settings::SETTINGS_MODEL => array(self::KEY_ATTRIBUTE => 'model'), Horde_ActiveSync_Request_Settings::SETTINGS_IMEI => array(self::KEY_ATTRIBUTE => 'imei'), Horde_ActiveSync_Request_Settings::SETTINGS_FRIENDLYNAME => array(self::KEY_ATTRIBUTE => 'friendlyname'), Horde_ActiveSync_Request_Settings::SETTINGS_OS => array(self::KEY_ATTRIBUTE => 'os'), Horde_ActiveSync_Request_Settings::SETTINGS_OSLANGUAGE => array(self::KEY_ATTRIBUTE => 'oslanguage'), Horde_ActiveSync_Request_Settings::SETTINGS_PHONENUMBER => array(self::KEY_ATTRIBUTE => 'phonenumber'), Horde_ActiveSync_Request_Settings::SETTINGS_USERAGENT => array(self::KEY_ATTRIBUTE => 'useragent'), Horde_ActiveSync_Request_Settings::SETTINGS_MOBILEOPERATOR => array(self::KEY_ATTRIBUTE => 'mobileoperator'), Horde_ActiveSync_Request_Settings::SETTINGS_ENABLEOUTBOUNDSMS => array(self::KEY_ATTRIBUTE => 'enableoutboundsms') ); /** * Property values. * * @var array */ protected $_properties = array( 'model' => false, 'imei' => false, 'friendlyname' => false, 'os' => false, 'oslanguage' => false, 'phonenumber' => false, 'useragent' => false, 'mobileoperator' => false, 'enableoutboundsms' => false ); }Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Message/Document.php0000664000076500000240000000377012654565405021464 0ustar * @package ActiveSync */ /** * Horde_ActiveSync_Message_DocumentLibrary * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2014-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync * */ class Horde_ActiveSync_Message_Document extends Horde_ActiveSync_Message_AirSyncBaseFileAttachment { /** * Property map * * @var array */ protected $_mapping = array( Horde_ActiveSync_Request_ItemOperations::ITEMOPERATIONS_RANGE => array(self::KEY_ATTRIBUTE => 'range'), Horde_ActiveSync_Request_ItemOperations::ITEMOPERATIONS_TOTAL => array(self::KEY_ATTRIBUTE => 'total'), Horde_ActiveSync_Request_ItemOperations::ITEMOPERATIONS_DATA => array(self::KEY_ATTRIBUTE => 'data'), Horde_ActiveSync_Request_ItemOperations::ITEMOPERATIONS_VERSION => array(self::KEY_ATTRIBUTE => 'version', self::KEY_TYPE => self::TYPE_DATE), ); /** * Property values * * @var array */ protected $_properties = array( 'range' => false, 'total' => false, 'data' => false, 'version' => false ); } Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Message/DocumentLibrary.php0000664000076500000240000000517412654565405023011 0ustar * @package ActiveSync */ /** * Horde_ActiveSync_Message_DocumentLibrary * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2014-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync * */ class Horde_ActiveSync_Message_DocumentLibrary extends Horde_ActiveSync_Message_Base { /** * Property mapping * * @var array */ protected $_mapping = array( Horde_ActiveSync::SYNC_DOCUMENTLIBRARY_LINKID => array(self::KEY_ATTRIBUTE => 'linkid'), Horde_ActiveSync::SYNC_DOCUMENTLIBRARY_DISPLAYNAME => array(self::KEY_ATTRIBUTE => 'displayname'), Horde_ActiveSync::SYNC_DOCUMENTLIBRARY_ISFOLDER => array(self::KEY_ATTRIBUTE => 'isfolder'), Horde_ActiveSync::SYNC_DOCUMENTLIBRARY_CREATIONDATE => array(self::KEY_ATTRIBUTE => 'creationdate', self::KEY_TYPE => self::TYPE_DATE_DASHES), Horde_ActiveSync::SYNC_DOCUMENTLIBRARY_LASTMODIFIEDDATE => array(self::KEY_ATTRIBUTE => 'lastmodifieddate', self::KEY_TYPE => self::TYPE_DATE_DASHES), Horde_ActiveSync::SYNC_DOCUMENTLIBRARY_ISHIDDEN => array(self::KEY_ATTRIBUTE => 'ishidden'), Horde_ActiveSync::SYNC_DOCUMENTLIBRARY_CONTENTLENGTH => array(self::KEY_ATTRIBUTE => 'contentlength'), Horde_ActiveSync::SYNC_DOCUMENTLIBRARY_CONTENTTYPE => array(self::KEY_ATTRIBUTE => 'contenttype') ); /** * Property values * * @var array */ protected $_properties = array( 'linkid' => false, 'displayname' => false, 'isfolder' => false, 'creationdate' => false, 'lastmodifieddate' => false, 'ishidden' => false, 'contentlength' => false, 'contenttype' => 'application/octet-stream' ); } Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Message/Exception.php0000664000076500000240000001646112654565405021645 0ustar * @package ActiveSync */ /** * Horde_ActiveSync_Message_Exception:: * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2010-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync * * @property mixed $string|Horde_Date timezone * @property Horde_Date $dtstamp * @property Horde_Date $starttime * @property string $subject * @property string $organizername * @property string $organizeremail * @property string $location * @property Horde_Date $endtime * @property integer $sensitivity * @property integer $busystatus * @property integer $alldayevent * @property integer $reminder * @property integer $meetingstatus * @property Horde_Date $exceptionstarttime (EAS <= 14.1 only). * @property integer $deleted * @property array $attendees * @property array $categories * @property Horde_Date $instanceid (EAS >= 16.0 only). */ class Horde_ActiveSync_Message_Exception extends Horde_ActiveSync_Message_Appointment { /** * Property mapping. * * @var array */ protected $_mapping = array( Horde_ActiveSync_Message_Appointment::POOMCAL_TIMEZONE => array(self::KEY_ATTRIBUTE => 'timezone'), Horde_ActiveSync_Message_Appointment::POOMCAL_DTSTAMP => array(self::KEY_ATTRIBUTE => 'dtstamp', self::KEY_TYPE => self::TYPE_DATE), Horde_ActiveSync_Message_Appointment::POOMCAL_STARTTIME => array(self::KEY_ATTRIBUTE => 'starttime', self::KEY_TYPE => self::TYPE_DATE), Horde_ActiveSync_Message_Appointment::POOMCAL_SUBJECT => array(self::KEY_ATTRIBUTE => 'subject'), Horde_ActiveSync_Message_Appointment::POOMCAL_ORGANIZERNAME => array(self::KEY_ATTRIBUTE => 'organizername'), Horde_ActiveSync_Message_Appointment::POOMCAL_ORGANIZEREMAIL => array (self::KEY_ATTRIBUTE => 'organizeremail'), Horde_ActiveSync_Message_Appointment::POOMCAL_ENDTIME => array(self::KEY_ATTRIBUTE => 'endtime', self::KEY_TYPE => self::TYPE_DATE), Horde_ActiveSync_Message_Appointment::POOMCAL_SENSITIVITY => array(self::KEY_ATTRIBUTE => 'sensitivity'), Horde_ActiveSync_Message_Appointment::POOMCAL_BUSYSTATUS => array(self::KEY_ATTRIBUTE => 'busystatus'), Horde_ActiveSync_Message_Appointment::POOMCAL_ALLDAYEVENT => array(self::KEY_ATTRIBUTE => 'alldayevent'), Horde_ActiveSync_Message_Appointment::POOMCAL_REMINDER => array(self::KEY_ATTRIBUTE => 'reminder'), Horde_ActiveSync_Message_Appointment::POOMCAL_MEETINGSTATUS => array(self::KEY_ATTRIBUTE => 'meetingstatus'), Horde_ActiveSync_Message_Appointment::POOMCAL_ATTENDEES => array(self::KEY_ATTRIBUTE => 'attendees', self::KEY_TYPE => 'Horde_ActiveSync_Message_Attendee', self::KEY_VALUES => Horde_ActiveSync_Message_Appointment::POOMCAL_ATTENDEE), Horde_ActiveSync_Message_Appointment::POOMCAL_CATEGORIES => array(self::KEY_ATTRIBUTE => 'categories', self::KEY_VALUES => Horde_ActiveSync_Message_Appointment::POOMCAL_CATEGORY), Horde_ActiveSync_Message_Appointment::POOMCAL_DELETED => array(self::KEY_ATTRIBUTE => 'deleted'), ); /** * Property values. * * @var array */ protected $_properties = array( 'timezone' => false, 'dtstamp' => false, 'starttime' => false, 'subject' => false, 'organizeremail' => false, 'organizername' => false, 'endtime' => false, 'sensitivity' => false, 'busystatus' => false, 'alldayevent' => false, 'reminder' => false, 'meetingstatus' => false, 'deleted' => false, 'attendees' => array(), 'categories' => array(), ); /** * Const'r * * @see Horde_ActiveSync_Message_Base::__construct() */ public function __construct(array $options = array()) { parent::__construct($options); // Removed in 16.0 if ($this->_version <= Horde_ActiveSync::VERSION_FOURTEENONE) { $this->_mapping += array( Horde_ActiveSync_Message_Appointment::POOMCAL_EXCEPTIONSTARTTIME => array(self::KEY_ATTRIBUTE => 'exceptionstarttime', self::KEY_TYPE => self::TYPE_DATE), Horde_ActiveSync_Message_Appointment::POOMCAL_LOCATION => array(self::KEY_ATTRIBUTE => 'location'), ); $this->_properties += array( 'exceptionstarttime' => false, 'location' => false, ); } if ($this->_version >= Horde_ActiveSync::VERSION_SIXTEEN) { $this->_mapping += array( Horde_ActiveSync::AIRSYNCBASE_LOCATION => array(self::KEY_ATTRIBUTE => 'location', self::KEY_TYPE => Horde_ActiveSync_Message_AirSyncBaseLocation), Horde_ActiveSync::AIRSYNCBASE_INSTANCEID => array(self::KEY_ATTRIBUTE => 'instanceid', self::KEY_TYPE => self::TYPE_DATE) ); $this->_properties += array( 'location' => false, 'instanceid' => false, ); } } /** * Sets the DELETED field on this exception * * @param boolean $flag */ public function setDeletedFlag($flag) { $this->_properties['deleted'] = $flag; } /** * Exception start time. This field seems to have different usages depending * on if this is a command request from the client or from the server. If * it's part of a request from client, then it represents the date of the * exception that is to be deleted. If it is from server, it represents the * date of the *original* recurring event. * * @return Horde_Date The exception's start time * @deprecated */ public function getExceptionStartTime() { return $this->_getAttribute('exceptionstarttime'); } /** * Set the exceptionStartTime value. * * @param Horde_Date $date The exceptionStartTime. * @deprecated */ public function setExceptionStartTime($date) { $this->exceptionstarttime = $date; } } Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Message/Flag.php0000664000076500000240000001173412654565405020556 0ustar * @package ActiveSync */ /** * Horde_ActiveSync_Message_Flag:: * * Portions of this class were ported from the Z-Push project: * File : wbxml.php * Project : Z-Push * Descr : WBXML mapping file * * Created : 01.10.2007 * * � Zarafa Deutschland GmbH, www.zarafaserver.de * This file is distributed under GPL-2.0. * Consult COPYING file for details * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2012-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync * * @property integer $flagstatus * @property integer $flagtype * @property Horde_Date $startdate * @property Horde_Date $utcstartdate * @property Horde_Date $duedate * @property Horde_Date $utcduedate * @property Horde_Date $datecompleted * @property integer $reminderset * @property integer $remindertime * @property string $subject * @property Horde_Date $ordinaldate * @property string $subordinaldate * @property integer $completetime */ class Horde_ActiveSync_Message_Flag extends Horde_ActiveSync_Message_Base { const POOMMAIL_FLAGSTATUS = 'POOMMAIL:FlagStatus'; const POOMMAIL_FLAGTYPE = 'POOMMAIL:FlagType'; const POOMMAIL_COMPLETETIME = 'POOMMAIL:CompleteTime'; const FLAG_STATUS_CLEAR = 0; const FLAG_STATUS_COMPLETE = 1; const FLAG_STATUS_ACTIVE = 2; /** * Property mapping * * @var array */ protected $_mapping = array( self::POOMMAIL_FLAGSTATUS => array(self::KEY_ATTRIBUTE => 'flagstatus'), self::POOMMAIL_FLAGTYPE => array(self::KEY_ATTRIBUTE => 'flagtype'), Horde_ActiveSync_Message_Task::POOMTASKS_STARTDATE => array(self::KEY_ATTRIBUTE => 'startdate', self::KEY_TYPE => self::TYPE_DATE_DASHES), Horde_ActiveSync_Message_Task::POOMTASKS_UTCSTARTDATE => array(self::KEY_ATTRIBUTE => 'utcstartdate', self::KEY_TYPE => self::TYPE_DATE_DASHES), Horde_ActiveSync_Message_Task::POOMTASKS_DUEDATE => array(self::KEY_ATTRIBUTE => 'duedate', self::KEY_TYPE => self::TYPE_DATE_DASHES), Horde_ActiveSync_Message_Task::POOMTASKS_UTCDUEDATE => array(self::KEY_ATTRIBUTE => 'utcduedate', self::KEY_TYPE => self::TYPE_DATE_DASHES), Horde_ActiveSync_Message_Task::POOMTASKS_DATECOMPLETED => array(self::KEY_ATTRIBUTE => 'datecompleted', self::KEY_TYPE => self::TYPE_DATE_DASHES), Horde_ActiveSync_Message_Task::POOMTASKS_REMINDERSET => array(self::KEY_ATTRIBUTE => 'reminderset'), Horde_ActiveSync_Message_Task::POOMTASKS_REMINDERTIME => array(self::KEY_ATTRIBUTE => 'remindertime', self::KEY_TYPE => self::TYPE_DATE_DASHES), Horde_ActiveSync_Message_Task::POOMTASKS_SUBJECT => array(self::KEY_ATTRIBUTE => 'subject'), Horde_ActiveSync_Message_Task::POOMTASKS_ORDINALDATE => array(self::KEY_ATTRIBUTE => 'ordinaldate', self::KEY_TYPE => self::TYPE_DATE_DASHES), Horde_ActiveSync_Message_Task::POOMTASKS_SUBORDINALDATE => array(self::KEY_ATTRIBUTE => 'subordinaldate'), self::POOMMAIL_COMPLETETIME => array(self::KEY_ATTRIBUTE => 'completetime'), ); /** * Property values. * * @var array */ protected $_properties = array( 'flagstatus' => false, 'flagtype' => false, 'startdate' => false, 'utcstartdate' => false, 'duedate' => false, 'utcduedate' => false, 'datecompleted' => false, 'reminderset' => false, 'remindertime' => false, 'subject' => false, 'ordinaldate' => false, 'subordinaldate' => false, 'completetime' => false, ); /** * Return the message class. * * @return string */ public function getClass() { return 'Flag'; } }Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Message/Folder.php0000664000076500000240000000537012654565405021117 0ustar * @package ActiveSync */ /** * Horde_ActiveSync_Message_Folder:: * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2009-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync * * @property string $parentid Identifier of parent folder, if applicable. * @property string $_serverid The private backend server id. * @property string $serverid Identifier of folder on the backend. * @property string $displayname Display name for folder. * @property integer $type Foldertype (Horde_Activesync:: constant). */ class Horde_ActiveSync_Message_Folder extends Horde_ActiveSync_Message_Base { /** * Id of the parent folder * * @var string */ public $parentid = false; /** * Property mapping. * * @var array */ protected $_mapping = array ( Horde_ActiveSync::FOLDERHIERARCHY_SERVERENTRYID => array (self::KEY_ATTRIBUTE => 'serverid'), Horde_ActiveSync::FOLDERHIERARCHY_PARENTID => array (self::KEY_ATTRIBUTE => 'parentid'), Horde_ActiveSync::FOLDERHIERARCHY_DISPLAYNAME => array (self::KEY_ATTRIBUTE => 'displayname'), Horde_ActiveSync::FOLDERHIERARCHY_TYPE => array (self::KEY_ATTRIBUTE => 'type') ); /** * Property values. * * @var array */ protected $_properties = array( 'serverid' => false, '_serverid' => false, '_parentid' => false, 'displayname' => false, 'type' => false, ); /** * Get message type. * * @return string */ public function getClass() { return 'Folders'; } }Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Message/Forwardee.php0000664000076500000240000000307512654565405021622 0ustar * @package ActiveSync */ /** * Horde_ActiveSync_Message_Picture:: Encapsulate the data to send in a * GAL response. * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync * */ class Horde_ActiveSync_Message_Forwardee extends Horde_ActiveSync_Message_Base { /** * Property mapping * * @var array */ protected $_mapping = array( Horde_ActiveSync_Message_SendMail::FORWARDEENAME => array(self::KEY_ATTRIBUTE => 'name'), Horde_ActiveSync_Message_SendMail::FORWARDEEEMAIL => array(self::KEY_ATTRIBUTE => 'email') ); /** * Property values. * * @var array */ protected $_properties = array( 'name' => false, 'email' => false ); }Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Message/GalPicture.php0000664000076500000240000000304112654565405021734 0ustar * @package ActiveSync */ /** * Horde_ActiveSync_Message_Picture:: Encapsulate the data to send in a * GAL response. * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2013-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync * */ class Horde_ActiveSync_Message_GalPicture extends Horde_ActiveSync_Message_Base { /** * Property mapping * * @var array */ protected $_mapping = array( Horde_ActiveSync::GAL_STATUS => array(self::KEY_ATTRIBUTE => 'status'), Horde_ActiveSync::GAL_DATA => array(self::KEY_ATTRIBUTE => 'data') ); /** * Property values. * * @var array */ protected $_properties = array( 'status' => false, 'data' => false ); }Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Message/Mail.php0000664000076500000240000004440112654565405020564 0ustar * @package ActiveSync */ /** * Horde_ActiveSync_Message_Mail:: * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2011-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync * * @property string $to * @property string $cc * @property string $from * @property string $subject * @property string $threadtopic * @property Horde_Date $datereceived * @property string $displayto * @property integer $importance * @property integer $mimetruncated * @property string $mimedata * @property integer $mimesize * @property integer $messageclass * @property Horde_ActiveSync_Message_MeetingRequest * $meetingrequest * @property string $reply_to * @property integer $read * @property cpid $integer The codepage id. * @property Horde_ActiveSync_Message_Attachments * $attachments (EAS 2.5 only). * @property integer $bodytruncated (EAS 2.5 only) * @property integer $bodysize (EAS 2.5 only) * @property stream|string $body (EAS 2.5 only) * @property integer $airsyncbasenativebodytype (EAS > 2.5 only). * @property Horde_ActiveSync_Message_AirSyncBaseBody * $airsyncbasebody (EAS > 2.5 only). * @property Horde_ActiveSync_Message_AirSyncBaseAttachments * $airsyncbaseattachments (EAS > 2.5 only). * @property integer $contentclass (EAS > 2.5 only). * @property Horde_ActiveSync_Message_Flag * $flag (EAS > 2.5 only). * @property boolean $isdraft (EAS 16.0 only). * @property string $bcc The bcc recipients (EAS 16.0 only). * @property boolean $send (EAS 16.0 only). * * // Internal properties. Not streamed to device. * @property string $messageid @since 2.4.0 * @property boolean $answered @since 2.4.0 * @property boolean $forwarded @since 2.4.0 */ class Horde_ActiveSync_Message_Mail extends Horde_ActiveSync_Message_Base { const POOMMAIL_ATTACHMENT = 'POOMMAIL:Attachment'; const POOMMAIL_ATTACHMENTS = 'POOMMAIL:Attachments'; const POOMMAIL_BODY = 'POOMMAIL:Body'; const POOMMAIL_BODYSIZE = 'POOMMAIL:BodySize'; const POOMMAIL_BODYTRUNCATED = 'POOMMAIL:BodyTruncated'; const POOMMAIL_DATERECEIVED = 'POOMMAIL:DateReceived'; const POOMMAIL_DISPLAYTO = 'POOMMAIL:DisplayTo'; const POOMMAIL_IMPORTANCE = 'POOMMAIL:Importance'; const POOMMAIL_MESSAGECLASS = 'POOMMAIL:MessageClass'; const POOMMAIL_SUBJECT = 'POOMMAIL:Subject'; const POOMMAIL_READ = 'POOMMAIL:Read'; const POOMMAIL_TO = 'POOMMAIL:To'; const POOMMAIL_CC = 'POOMMAIL:Cc'; const POOMMAIL_FROM = 'POOMMAIL:From'; const POOMMAIL_REPLY_TO = 'POOMMAIL:Reply-To'; const POOMMAIL_ALLDAYEVENT = 'POOMMAIL:AllDayEvent'; const POOMMAIL_CATEGORIES = 'POOMMAIL:Categories'; const POOMMAIL_CATEGORY = 'POOMMAIL:Category'; const POOMMAIL_DTSTAMP = 'POOMMAIL:DtStamp'; const POOMMAIL_ENDTIME = 'POOMMAIL:EndTime'; const POOMMAIL_INSTANCETYPE = 'POOMMAIL:InstanceType'; const POOMMAIL_BUSYSTATUS = 'POOMMAIL:BusyStatus'; const POOMMAIL_LOCATION = 'POOMMAIL:Location'; const POOMMAIL_MEETINGREQUEST = 'POOMMAIL:MeetingRequest'; const POOMMAIL_ORGANIZER = 'POOMMAIL:Organizer'; const POOMMAIL_RECURRENCEID = 'POOMMAIL:RecurrenceId'; const POOMMAIL_REMINDER = 'POOMMAIL:Reminder'; const POOMMAIL_RESPONSEREQUESTED = 'POOMMAIL:ResponseRequested'; const POOMMAIL_RECURRENCES = 'POOMMAIL:Recurrences'; const POOMMAIL_RECURRENCE = 'POOMMAIL:Recurrence'; const POOMMAIL_TYPE = 'POOMMAIL:Type'; const POOMMAIL_UNTIL = 'POOMMAIL:Until'; const POOMMAIL_OCCURRENCES = 'POOMMAIL:Occurrences'; const POOMMAIL_INTERVAL = 'POOMMAIL:Interval'; const POOMMAIL_DAYOFWEEK = 'POOMMAIL:DayOfWeek'; const POOMMAIL_DAYOFMONTH = 'POOMMAIL:DayOfMonth'; const POOMMAIL_WEEKOFMONTH = 'POOMMAIL:WeekOfMonth'; const POOMMAIL_MONTHOFYEAR = 'POOMMAIL:MonthOfYear'; const POOMMAIL_STARTTIME = 'POOMMAIL:StartTime'; const POOMMAIL_SENSITIVITY = 'POOMMAIL:Sensitivity'; const POOMMAIL_TIMEZONE = 'POOMMAIL:TimeZone'; const POOMMAIL_GLOBALOBJID = 'POOMMAIL:GlobalObjId'; const POOMMAIL_THREADTOPIC = 'POOMMAIL:ThreadTopic'; const POOMMAIL_MIMEDATA = 'POOMMAIL:MIMEData'; const POOMMAIL_MIMETRUNCATED = 'POOMMAIL:MIMETruncated'; const POOMMAIL_MIMESIZE = 'POOMMAIL:MIMESize'; const POOMMAIL_INTERNETCPID = 'POOMMAIL:InternetCPID'; // EAS 12.0 const POOMMAIL_CONTENTCLASS = 'POOMMAIL:ContentClass'; const POOMMAIL_FLAG = 'POOMMAIL:Flag'; // EAS 14.0 const POOMMAIL_COMPLETETIME = 'POOMMAIL:CompleteTime'; const POOMMAIL_DISALLOWNEWTIMEPROPOSAL = 'POOMMAIL:DisallowNewTimeProposal'; // EAS 14 POOMMAIL2 const POOMMAIL2_UMCALLERID = 'POOMMAIL2:UmCallerId'; const POOMMAIL2_UMUSERNOTES = 'POOMMAIL2:UmUserNotes'; const POOMMAIL2_UMATTDURATION = 'POOMMAIL2:UmAttDuration'; const POOMMAIL2_UMATTORDER = 'POOMMAIL2:UmAttOrder'; const POOMMAIL2_CONVERSATIONID = 'POOMMAIL2:ConversationId'; const POOMMAIL2_CONVERSATIONINDEX = 'POOMMAIL2:ConversationIndex'; const POOMMAIL2_LASTVERBEXECUTED = 'POOMMAIL2:LastVerbExecuted'; const POOMMAIL2_LASTVERBEXECUTIONTIME = 'POOMMAIL2:LastVerbExecutionTime'; const POOMMAIL2_RECEIVEDASBCC = 'POOMMAIL2:ReceivedAsBcc'; const POOMMAIL2_SENDER = 'POOMMAIL2:Sender'; const POOMMAIL2_CALENDARTYPE = 'POOMMAIL2:CalendarType'; const POOMMAIL2_ISLEAPMONTH = 'POOMMAIL2:IsLeapMonth'; const POOMMAIL2_ACCOUNTID = 'POOMMAIL2:AccountId'; const POOMMAIL2_FIRSTDAYOFWEEK = 'POOMMAIL2:FirstDayOfWeek'; // EAS 14.1 const POOMMAIL2_MEETINGMESSAGETYPE = 'POOMMAIL2:MeetingMessageType'; // EAS 16.0 const POOMMAIL2_ISDRAFT = 'POOMMAIL2:IsDraft'; const POOMMAIL2_BCC = 'POOMMAIL2:Bcc'; const POOMMAIL2_SEND = 'POOMMAIL2:Send'; /* Mail message types */ const CLASS_NOTE = 'IPM.Note'; const CLASS_MEETING_REQUEST = 'IPM.Schedule.Meeting.Request'; const CLASS_MEETING_NOTICE = 'IPM.Notification.Meeting'; /* Flags */ const FLAG_READ_UNSEEN = 0; const FLAG_READ_SEEN = 1; /* UTF-8 codepage id. */ const INTERNET_CPID_UTF8 = 65001; /* Importance */ const IMPORTANCE_LOW = 0; const IMPORTANCE_NORM = 1; const IMPORTANCE_HIGH = 2; /* Verbs */ const VERB_NONE = 0; const VERB_REPLY_SENDER = 1; const VERB_REPLY_ALL = 2; const VERB_FORWARD = 3; /** * Property mappings * * @var array */ protected $_mapping = array( self::POOMMAIL_TO => array(self::KEY_ATTRIBUTE => 'to'), self::POOMMAIL_CC => array(self::KEY_ATTRIBUTE => 'cc'), self::POOMMAIL_FROM => array(self::KEY_ATTRIBUTE => 'from'), self::POOMMAIL_SUBJECT => array(self::KEY_ATTRIBUTE => 'subject'), self::POOMMAIL_REPLY_TO => array(self::KEY_ATTRIBUTE => 'reply_to'), self::POOMMAIL_DATERECEIVED => array(self::KEY_ATTRIBUTE => 'datereceived', self::KEY_TYPE => self::TYPE_DATE_DASHES), self::POOMMAIL_DISPLAYTO => array(self::KEY_ATTRIBUTE => 'displayto'), self::POOMMAIL_THREADTOPIC => array(self::KEY_ATTRIBUTE => 'threadtopic'), self::POOMMAIL_IMPORTANCE => array(self::KEY_ATTRIBUTE => 'importance'), self::POOMMAIL_READ => array(self::KEY_ATTRIBUTE => 'read'), self::POOMMAIL_MIMETRUNCATED => array(self::KEY_ATTRIBUTE => 'mimetruncated' ), // Not used. self::POOMMAIL_MIMEDATA => array(self::KEY_ATTRIBUTE => 'mimedata', self::KEY_TYPE => 'KEY_TYPE_MAPI_STREAM'), self::POOMMAIL_MIMESIZE => array(self::KEY_ATTRIBUTE => 'mimesize' ), self::POOMMAIL_MESSAGECLASS => array(self::KEY_ATTRIBUTE => 'messageclass'), self::POOMMAIL_MEETINGREQUEST => array(self::KEY_ATTRIBUTE => 'meetingrequest', self::KEY_TYPE => 'Horde_ActiveSync_Message_MeetingRequest'), self::POOMMAIL_INTERNETCPID => array(self::KEY_ATTRIBUTE => 'cpid'), ); /** * Property values. * * @var array */ protected $_properties = array( 'to' => false, 'cc' => false, 'from' => false, 'subject' => false, 'threadtopic' => false, 'datereceived' => false, 'displayto' => false, 'importance' => false, 'mimetruncated' => false, 'mimedata' => false, 'mimesize' => false, 'messageclass' => false, 'meetingrequest' => false, 'reply_to' => false, 'read' => false, 'cpid' => false, ); /** * Const'r * * @see Horde_ActiveSync_Message_Base::__construct() */ public function __construct(array $options = array()) { parent::__construct($options); if ($this->_version == Horde_ActiveSync::VERSION_TWOFIVE) { $this->_mapping += array( self::POOMMAIL_ATTACHMENTS => array(self::KEY_ATTRIBUTE => 'attachments', self::KEY_TYPE => 'Horde_ActiveSync_Message_Attachment', self::KEY_VALUES => self::POOMMAIL_ATTACHMENT), self::POOMMAIL_BODYTRUNCATED => array(self::KEY_ATTRIBUTE => 'bodytruncated'), self::POOMMAIL_BODYSIZE => array(self::KEY_ATTRIBUTE => 'bodysize'), self::POOMMAIL_BODY => array(self::KEY_ATTRIBUTE => 'body'), ); $this->_properties += array( 'attachments' => false, 'bodytruncated' => false, 'bodysize' => false, 'body' => false, ); } if ($this->_version >= Horde_ActiveSync::VERSION_TWELVE) { $this->_mapping += array( Horde_ActiveSync::AIRSYNCBASE_NATIVEBODYTYPE => array(self::KEY_ATTRIBUTE => 'airsyncbasenativebodytype'), Horde_ActiveSync::AIRSYNCBASE_BODY => array(self::KEY_ATTRIBUTE => 'airsyncbasebody', self::KEY_TYPE=> 'Horde_ActiveSync_Message_AirSyncBaseBody'), Horde_ActiveSync::AIRSYNCBASE_ATTACHMENTS => array(self::KEY_ATTRIBUTE => 'airsyncbaseattachments', self::KEY_TYPE => 'Horde_ActiveSync_Message_AirSyncBaseAttachment', self::KEY_VALUES => Horde_ActiveSync::AIRSYNCBASE_ATTACHMENT ), self::POOMMAIL_FLAG => array(self::KEY_ATTRIBUTE => 'flag', self::KEY_TYPE => 'Horde_ActiveSync_Message_Flag'), self::POOMMAIL_CONTENTCLASS => array(self::KEY_ATTRIBUTE => 'contentclass'), ); $this->_properties += array( 'airsyncbasenativebodytype' => false, 'airsyncbasebody' => false, 'airsyncbaseattachments' => false, 'contentclass' => false, 'flag' => false, ); // Removed in 16.0 if ($this->_version <= Horde_ActiveSync::VERSION_FOURTEENONE) { $this->_mapping += array( self::POOMMAIL_LOCATION => array(self::KEY_ATTRIBUTE => 'location'), self::POOMMAIL_GLOBALOBJID => array(self::KEY_ATTRIBUTE => 'globalobjid') ); $this->_properties += array( 'location' => false, 'globalobjid' => false, ); } if ($this->_version >= Horde_ActiveSync::VERSION_FOURTEEN) { $this->_mapping += array( self::POOMMAIL_CATEGORIES => array(self::KEY_ATTRIBUTE => 'categories', self::KEY_VALUES => self::POOMMAIL_CATEGORY), self::POOMMAIL_CATEGORY => array(self::KEY_ATTRIBUTE => 'category'), self::POOMMAIL2_UMCALLERID => array(self::KEY_ATTRIBUTE => 'umcallerid'), self::POOMMAIL2_UMUSERNOTES => array(self::KEY_ATTRIBUTE => 'umusernotes'), self::POOMMAIL2_UMATTDURATION => array(self::KEY_ATTRIBUTE => 'umattduration'), self::POOMMAIL2_UMATTORDER => array(self::KEY_ATTRIBUTE => 'umattorder'), self::POOMMAIL2_CONVERSATIONID => array(self::KEY_ATTRIBUTE => 'conversationid'), self::POOMMAIL2_CONVERSATIONINDEX => array(self::KEY_ATTRIBUTE => 'conversationindex'), self::POOMMAIL2_LASTVERBEXECUTED => array(self::KEY_ATTRIBUTE => 'lastverbexecuted'), self::POOMMAIL2_LASTVERBEXECUTIONTIME => array(self::KEY_ATTRIBUTE => 'lastverbexecutiontime', self::KEY_TYPE => self::TYPE_DATE_DASHES), self::POOMMAIL2_RECEIVEDASBCC => array(self::KEY_ATTRIBUTE => 'receivedasbcc'), self::POOMMAIL2_SENDER => array(self::KEY_ATTRIBUTE => 'sender'), self::POOMMAIL2_CALENDARTYPE => array(self::KEY_ATTRIBUTE => 'calendartype'), self::POOMMAIL2_ISLEAPMONTH => array(self::KEY_ATTRIBUTE => 'isleapmonth'), self::POOMMAIL2_ACCOUNTID => array(self::KEY_ATTRIBUTE => 'accountid'), self::POOMMAIL2_FIRSTDAYOFWEEK => array(self::KEY_ATTRIBUTE => 'firstdayofweek') ); $this->_properties += array( 'umcallerid' => false, 'umusernotes' => false, 'umattduration' => false, 'umattorder' => false, 'conversationid' => false, 'conversationindex' => false, 'lastverbexecuted' => false, 'lastverbexecutiontime' => false, 'receivedasbcc' => false, 'sender' => false, 'calendartype' => false, 'isleapmonth' => false, 'accountid' => false, 'firstdayofweek' => false, 'categories' => array(), // Internal use 'messageid' => false, 'answered' => false, 'forwarded' => false, ); } if ($this->_version > Horde_ActiveSync::VERSION_FOURTEEN) { $this->_mapping += array( Horde_ActiveSync::AIRSYNCBASE_BODYPART => array(self::KEY_ATTRIBUTE => 'airsyncbasebodypart', self::KEY_TYPE => 'Horde_ActiveSync_Message_AirSyncBaseBodypart') ); $this->_properties += array( 'airsyncbasebodypart' => false ); } if ($this->_version >= Horde_ActiveSync::VERSION_SIXTEEN) { $this->_mapping += array( self::POOMMAIL2_ISDRAFT => array(self::KEY_ATTRIBUTE => 'isdraft'), self::POOMMAIL2_BCC => array(self::KEY_ATTRIBUTE => 'bcc'), self::POOMMAIL2_SEND => array(self::KEY_ATTRIBUTE => 'send'), Horde_ActiveSync::AIRSYNCBASE_LOCATION => array(self::KEY_ATTRIBUTE => 'location', Horde_ActiveSync_Message_Appointment::POOMCAL_UID => array(self::KEY_ATTRIBUTE => 'uid')), ); $this->_properties += array( 'isdraft' => false, 'bcc' => false, 'send' => false, 'location' => false, 'uid' => false, ); } } } /** * Return the class type for this object. * * @return string */ public function getClass() { return 'Email'; } /** * Checks to see if we should send an empty value. * * @param string $tag The tag name * * @return boolean */ protected function _checkSendEmpty($tag) { switch ($tag) { case self::POOMMAIL_FLAG: case self::POOMMAIL_CATEGORIES: return true; } return false; } } Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Message/MeetingRequest.php0000664000076500000240000002613312654565405022645 0ustar * @package ActiveSync */ /** * Horde_ActiveSync_Message_MeetingRequest * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2010-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync * * @property integer $alldayevent * @property Horde_Date $starttime * @property Horde_Date $dtstamp * @property Horde_Date $endtime * @property integer $instancetype * @property string $location * @property string $organizer * @property string $recurrenceid * @property integer $reminder * @property integer $responserequested * @property Horde_ActiveSync_Message_Recurrence $recurrences (Not currently supported). * @property integer $sensitivity * @property integer $busystatus * @property string|Horde_Date timezone * @proprety string globalobjid */ class Horde_ActiveSync_Message_MeetingRequest extends Horde_ActiveSync_Message_Base { /** * Property mapping. * * @var array */ protected $_mapping = array ( Horde_ActiveSync_Message_Mail::POOMMAIL_ALLDAYEVENT => array(self::KEY_ATTRIBUTE => 'alldayevent'), Horde_ActiveSync_Message_Mail::POOMMAIL_STARTTIME => array(self::KEY_ATTRIBUTE => 'starttime', self::KEY_TYPE => self::TYPE_DATE_DASHES), Horde_ActiveSync_Message_Mail::POOMMAIL_DTSTAMP => array(self::KEY_ATTRIBUTE => 'dtstamp', self::KEY_TYPE => self::TYPE_DATE_DASHES), Horde_ActiveSync_Message_Mail::POOMMAIL_ENDTIME => array(self::KEY_ATTRIBUTE => 'endtime', self::KEY_TYPE => self::TYPE_DATE_DASHES), Horde_ActiveSync_Message_Mail::POOMMAIL_INSTANCETYPE => array(self::KEY_ATTRIBUTE => 'instancetype'), Horde_ActiveSync_Message_Mail::POOMMAIL_LOCATION => array(self::KEY_ATTRIBUTE => 'location'), Horde_ActiveSync_Message_Mail::POOMMAIL_ORGANIZER => array(self::KEY_ATTRIBUTE => 'organizer'), Horde_ActiveSync_Message_Mail::POOMMAIL_RECURRENCEID => array(self::KEY_ATTRIBUTE => 'recurrenceid', self::KEY_TYPE => self::TYPE_DATE_DASHES), Horde_ActiveSync_Message_Mail::POOMMAIL_REMINDER => array(self::KEY_ATTRIBUTE => 'reminder'), Horde_ActiveSync_Message_Mail::POOMMAIL_RESPONSEREQUESTED => array(self::KEY_ATTRIBUTE => 'responserequested'), Horde_ActiveSync_Message_Mail::POOMMAIL_RECURRENCES => array(self::KEY_ATTRIBUTE => 'recurrences', self::KEY_TYPE => 'Horde_ActiveSync_Message_MeetingRequestRecurrence', self::KEY_VALUES => Horde_ActiveSync_Message_Mail::POOMMAIL_RECURRENCE), Horde_ActiveSync_Message_Mail::POOMMAIL_SENSITIVITY => array(self::KEY_ATTRIBUTE => 'sensitivity'), Horde_ActiveSync_Message_Mail::POOMMAIL_BUSYSTATUS => array(self::KEY_ATTRIBUTE => 'busystatus'), Horde_ActiveSync_Message_Mail::POOMMAIL_TIMEZONE => array(self::KEY_ATTRIBUTE => 'timezone'), Horde_ActiveSync_Message_Mail::POOMMAIL_GLOBALOBJID => array(self::KEY_ATTRIBUTE => 'globalobjid'), ); /** * Property values. * * @var array */ protected $_properties = array( 'alldayevent' => '0', 'starttime' => false, 'dtstamp' => false, 'endtime' => false, 'instancetype' => '0', // For now, no recurring meeting request support. 'location' => false, 'organizer' => false, 'recurrenceid' => false, 'reminder' => false, 'responserequested' => false, 'recurrences' => array(), 'sensitivity' => false, 'busystatus' => false, 'timezone' => false, 'globalobjid' => false ); /** * Copy of the vEvent object. * * @var Horde_Icalendar_Vevent */ protected $_vEvent; /** * Const'r * * @see Horde_ActiveSync_Message_Base::__construct() */ public function __construct(array $options = array()) { parent::__construct($options); if ($this->_version > Horde_ActiveSync::VERSION_FOURTEEN) { $this->_mapping += array( Horde_ActiveSync_Message_Mail::POOMMAIL2_MEETINGMESSAGETYPE => array(self::KEY_ATTRIBUTE => 'meetingmessagetype') ); $this->_properties += array( 'meetingmessagetype' => false ); } } /** * Create a meeting request from a vEvent. * * @param Horde_Icalendar_Vevent $vCal The vEvent. * * @throws Horde_ActiveSync_Exception */ public function fromvEvent($vCal) { try { $method = $vCal->getAttribute('METHOD'); } catch (Horde_Icalendar_Exception $e) { throw new Horde_ActiveSync_Exception('Unable to parse vEvent'); } foreach ($vCal->getComponents() as $component) { switch ($component->getType()) { case 'vEvent': $this->_vEvent = $component; $this->_parsevEvent($component, $method); break; case 'vTimeZone': // Not sure what to do with Timezone yet/how to get it into // a TZ structure etc... For now, defaults to default timezone (as the // specs say it should for iCal without tz specified). default: break; } } // Ensure we actually have a vEvent to parse. if (empty($this->_vEvent)) { return; } $tz = new Horde_Mapi_Timezone(); try { $this->timezone = $tz->getSyncTZFromOffsets( $tz->getOffsetsFromDate(new Horde_Date($this->_vEvent->getAttribute('DTSTART'))) ); } catch (Horde_Icalendar_Exception $e) {} $this->alldayevent = (int)$this->_isAllDay(); } /** * Return the vEvent object the request/response is based on. * * @return Horde_Icalendar_vEvent */ public function getvEvent() { return $this->_vEvent; } /** * Parses a vEvent into the message properties. * * @param Horde_Icalendar_Vevent $vevent The vEvent to parse. * @param string $method The method (e.g., 'REQUEST'). * * @throws Horde_ActiveSync_Exception */ protected function _parsevEvent($vevent, $method = 'REQUEST') { if ($method == 'REQUEST') { $this->responserequested = '1'; } else { $this->responserequested = '0'; } try { $organizer = parse_url($vevent->getAttribute('ORGANIZER')); $this->organizer = $organizer['path']; } catch (Horde_Icalendar_Exception $e) {} try { $this->globalobjid = Horde_Mapi::createGoid($vevent->getAttribute('UID')); $this->starttime = new Horde_Date($vevent->getAttribute('DTSTART')); } catch (Horde_Exception $e) { throw new Horde_ActiveSync_Exception($e); } try { $this->dtstamp = new Horde_Date($vevent->getAttribute('DTSTAMP')); } catch (Horde_Exception $e) {} try { $this->endtime = new Horde_Date($vevent->getAttribute('DTEND')); $this->location = Horde_String::truncate($vevent->getAttribute('LOCATION'), 255); } catch (Horde_Icalendar_Exception $e) {} try { $class = $vevent->getAttribute('CLASS'); if (!is_array($class)) { $this->sensitivity = $class == 'PRIVATE' ? Horde_ActiveSync_Message_Appointment::SENSITIVITY_PRIVATE : ($class == 'CONFIDENTIAL' ? Horde_ActiveSync_Message_Appointment::SENSITIVITY_CONFIDENTIAL : ($class == 'PERSONAL' ? Horde_ActiveSync_Message_Appointment::SENSITIVITY_PERSONAL : Horde_ActiveSync_Message_Appointment::SENSITIVITY_NORMAL)); } } catch (Horde_Icalendar_Exception $e) {} try { $status = $vevent->getAttribute('STATUS'); if (!is_array($status)) { $status = Horde_String::upper($status); $this->busystatus = $status == 'TENTATIVE' ? Horde_ActiveSync_Message_Appointment::BUSYSTATUS_TENTATIVE : ($status == 'CONFIRMED' ? Horde_ActiveSync_Message_Appointment::BUSYSTATUS_BUSY : Horde_ActiveSync_Message_Appointment::BUSYSTATUS_FREE); } } catch (Horde_Icalendar_Exception $e) {} // vCalendar 1.0 alarms try { $alarm = $vevent->getAttribute('AALARM'); if (!is_array($alarm) && intval($alarm)) { $this->reminder = intval($this->starttime->timestamp() - $alarm); } } catch (Horde_Icalendar_Exception $e) {} // vCalendar 2.0 alarms foreach ($vevent->getComponents() as $alarm) { if (!($alarm instanceof Horde_Icalendar_Valarm)) { continue; } try { $trigger = $alarm->getAttribute('TRIGGER'); $triggerParams = $alarm->getAttribute('TRIGGER', true); } catch (Horde_Icalendar_Exception $e) { continue; } if (isset($triggerParams['VALUE']) && $triggerParams['VALUE'] == 'DATE-TIME') { if (isset($triggerParams['RELATED']) && $triggerParams['RELATED'] == 'END') { $this->reminder = intval($this->endtime->timestamp() - $trigger); } else { $this->reminder = intval($this->starttime->timestamp() - $trigger); } } else { $this->reminder = -intval($trigger); } } } /** * Return if this is a request for an all day meeting. * * @return boolean */ protected function _isAllDay() { return ($this->starttime->hour == 0 && $this->starttime->min == 0 && $this->starttime->sec == 0 && (($this->endtime->hour == 23 && $this->endtime->min == 59) || ($this->endtime->hour == 0 && $this->endtime->min == 0 && $this->endtime->sec == 0 && ($this->endtime->mday > $this->starttime->mday || $this->endtime->month > $this->starttime->month || $this->endtime->year > $this->starttime->year)))); } }Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Message/Note.php0000664000076500000240000000535112654565405020610 0ustar * @package ActiveSync */ /** * Horde_ActiveSync_Message_Note:: * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2010-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync * * @property string $subject The note's subject. * @property Horde_ActiveSync_Message_AirSyncBaseBody $body The note's body. * @property string $messageclass The note's message class. * @property array $categories The note's categories. * @property Horde_Date $lastmodified The note's last modification date. */ class Horde_ActiveSync_Message_Note extends Horde_ActiveSync_Message_Base { const SUBJECT = 'Notes:Subject'; const MESSAGECLASS = 'Notes:MessageClass'; const LASTMODIFIEDDATE = 'Notes:LastModifiedDate'; const CATEGORIES = 'Notes:Categories'; const CATEGORY = 'Notes:Category'; public $categories = array(); /** * Property mapping * * @var array */ protected $_mapping = array ( Horde_ActiveSync::AIRSYNCBASE_BODY => array(self::KEY_ATTRIBUTE => 'body', self::KEY_TYPE => 'Horde_ActiveSync_Message_AirSyncBaseBody'), self::CATEGORIES => array(self::KEY_ATTRIBUTE => 'categories', self::KEY_VALUES => self::CATEGORY), self::LASTMODIFIEDDATE => array(self::KEY_ATTRIBUTE => 'lastmodified', self::KEY_TYPE => self::TYPE_DATE), self::MESSAGECLASS => array(self::KEY_ATTRIBUTE => 'messageclass'), self::SUBJECT => array(self::KEY_ATTRIBUTE => 'subject') ); /** * Property values. * * @var array */ protected $_properties = array( 'body' => false, 'lastmodified' => false, 'messageclass' => false, 'subject' => false ); /** * Return this object's folder class * * @return string */ public function getClass() { return 'Notes'; } }Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Message/Oof.php0000664000076500000240000000511112654565405020420 0ustar * @package ActiveSync * @since 2.21.0 */ /** * Horde_ActiveSync_Message_Oof:: * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2010-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync * @since 2.21.0 * * @property integer $state * @property Horde_Date $starttime * @property Horde_Date $endtime * @property Horde_ActiveSync_Message_OofMessage $message * @property string $bodytype */ class Horde_ActiveSync_Message_Oof extends Horde_ActiveSync_Message_Base { public $messages = array(); /** * Property mapping * * @var array */ protected $_mapping = array ( Horde_ActiveSync_Request_Settings::SETTINGS_OOFSTATE => array(self::KEY_ATTRIBUTE => 'state'), Horde_ActiveSync_Request_Settings::SETTINGS_STARTTIME => array(self::KEY_ATTRIBUTE => 'starttime', self::KEY_TYPE => self::TYPE_DATE_DASHES), Horde_ActiveSync_Request_Settings::SETTINGS_ENDTIME => array(self::KEY_ATTRIBUTE => 'endtime', self::KEY_TYPE => self::TYPE_DATE_DASHES), Horde_ActiveSync_Request_Settings::SETTINGS_OOFMESSAGE => array( self::KEY_ATTRIBUTE => 'messages', self::KEY_TYPE => 'Horde_ActiveSync_Message_OofMessage', self::KEY_VALUES => Horde_ActiveSync_Request_Settings::SETTINGS_OOFMESSAGE, self::KEY_PROPERTY => self::PROPERTY_NO_CONTAINER ), Horde_ActiveSync_Request_Settings::SETTINGS_BODYTYPE => array(self::KEY_ATTRIBUTE => 'bodytype'), ); /** * Property values. * * @var array */ protected $_properties = array( 'state' => false, 'starttime' => false, 'endtime' => false, 'bodytype' => false ); }Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Message/OofMessage.php0000664000076500000240000000511012654565405021724 0ustar * @package ActiveSync * @since 2.21.0 */ /** * Horde_ActiveSync_Message_OofMessage:: * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2010-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync * @since 2.21.0 * * @property boolean $internal * @property boolean $externalknown * @property boolean $externalunknown * @property boolean $enabled * @property string $reply * @property string $bodytype */ class Horde_ActiveSync_Message_OofMessage extends Horde_ActiveSync_Message_Base { public $internal; public $externalknown; public $externalunknown; /** * Property mapping * * @var array */ protected $_mapping = array ( Horde_ActiveSync_Request_Settings::SETTINGS_APPLIESTOINTERNAL => array(self::KEY_ATTRIBUTE => 'internal'), Horde_ActiveSync_Request_Settings::SETTINGS_APPLIESTOEXTERNALKNOWN => array(self::KEY_ATTRIBUTE => 'externalknown'), Horde_ActiveSync_Request_Settings::SETTINGS_APPLIESTOEXTERNALUNKNOWN => array(self::KEY_ATTRIBUTE => 'externalunknown'), Horde_ActiveSync_Request_Settings::SETTINGS_ENABLED => array(self::KEY_ATTRIBUTE => 'enabled'), Horde_ActiveSync_Request_Settings::SETTINGS_REPLYMESSAGE => array(self::KEY_ATTRIBUTE => 'reply'), Horde_ActiveSync_Request_Settings::SETTINGS_BODYTYPE => array(self::KEY_ATTRIBUTE => 'bodytype') ); /** * Property values. * * @var array */ protected $_properties = array( 'enabled' => false, 'reply' => false, 'bodytype' => false, ); /** * Checks to see if we should send an empty value. * * @param string $tag The tag name * * @return boolean */ protected function _checkSendEmpty($tag) { return true; } }Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Message/RecipientInformation.php0000664000076500000240000000422712654565405024034 0ustar * @package ActiveSync */ /** * Horde_ActiveSync_Message_RecipientInformation:: * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2010-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync * * @property string $email1address * @property string $fileas * @property string $alias (EAS >= 14.0 only) * @property string $weightedrank (EAS >= 14.0 only) */ class Horde_ActiveSync_Message_RecipientInformation extends Horde_ActiveSync_Message_Base { /** * Property mapping. * * @var array */ protected $_mapping = array( Horde_ActiveSync_Message_Contact::EMAIL1ADDRESS => array(self::KEY_ATTRIBUTE => 'email1address'), Horde_ActiveSync_Message_Contact::FILEAS => array(self::KEY_ATTRIBUTE => 'fileas'), Horde_ActiveSync_Message_Contact::ALIAS => array(self::KEY_ATTRIBUTE => 'alias'), Horde_ActiveSync_Message_Contact::WEIGHTEDRANK => array(self::KEY_ATTRIBUTE => 'weightedrank'), ); /** * Property values. * * @var array */ protected $_properties = array( 'email1address' => false, 'fileas' => false, 'alias' => false, 'weightedrank' => false, ); /** * Return message type * * @return string */ public function getClass() { return 'RI'; } } Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Message/Recurrence.php0000664000076500000240000001237212654565405022001 0ustar * @package ActiveSync */ /** * Horde_ActiveSync_Message_Recurrence:: * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2010-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync * * @property integer $type * @property Horde_Date $until * @property string $occurrences * @property integer $interval * @property integer $dayofweek * @property integer $dayofmonth * @property integer $weekofmonth * @property integer $monthofyear */ class Horde_ActiveSync_Message_Recurrence extends Horde_ActiveSync_Message_Base { /* MS AS Recurrence types */ const TYPE_DAILY = 0; const TYPE_WEEKLY = 1; const TYPE_MONTHLY = 2; const TYPE_MONTHLY_NTH = 3; const TYPE_YEARLY = 5; const TYPE_YEARLYNTH = 6; const CALENDAR_TYPE_DEFAULT = 0; const CALENDAR_TYPE_GREGORIAN = 1; const CALENDAR_TYPE_GREGORIAN_US = 2; const CALENDAR_TYPE_JAPANESE = 3; const CALENDAR_TYPE_TAIWAN = 4; const CALENDAR_TYPE_KOREAN = 5; const CALENDAR_TYPE_HIJRI = 6; const CALENDAR_TYPE_THAI = 7; const CALENDAR_TYPE_HEBREW = 8; const CALENDAR_TYPE_GREGORIAN_FRENCH = 9; const CALENDAR_TYPE_GREGORIAN_ARABIC = 10; const CALENDAR_TYPE_GREGORIAN_TRANSLITERATED = 11; /* FDOW mapping for EAS 14.1 */ const FIRSTDAY_SUNDAY = 0; const FIRSTDAY_MONDAY = 1; const FIRSTDAY_TUESDAY = 2; const FIRSTDAY_WEDNESDAY = 3; const FIRSTDAY_THURSDAY = 4; const FIRSTDAY_FRIDAY = 5; const FIRSTDAY_SATURDAY = 6; /** * Property mapping. * * @var array */ protected $_mapping = array ( Horde_ActiveSync_Message_Appointment::POOMCAL_TYPE => array (self::KEY_ATTRIBUTE => 'type'), Horde_ActiveSync_Message_Appointment::POOMCAL_UNTIL => array (self::KEY_ATTRIBUTE => 'until', self::KEY_TYPE => self::TYPE_DATE), Horde_ActiveSync_Message_Appointment::POOMCAL_OCCURRENCES => array (self::KEY_ATTRIBUTE => 'occurrences'), Horde_ActiveSync_Message_Appointment::POOMCAL_INTERVAL => array (self::KEY_ATTRIBUTE => 'interval'), Horde_ActiveSync_Message_Appointment::POOMCAL_DAYOFWEEK => array (self::KEY_ATTRIBUTE => 'dayofweek'), Horde_ActiveSync_Message_Appointment::POOMCAL_DAYOFMONTH => array (self::KEY_ATTRIBUTE => 'dayofmonth'), Horde_ActiveSync_Message_Appointment::POOMCAL_WEEKOFMONTH => array (self::KEY_ATTRIBUTE => 'weekofmonth'), Horde_ActiveSync_Message_Appointment::POOMCAL_MONTHOFYEAR => array (self::KEY_ATTRIBUTE => 'monthofyear') ); /** * Property values. * * @var array */ protected $_properties = array( 'type' => false, 'until' => false, 'occurrences' => false, 'interval' => false, 'dayofweek' => false, 'dayofmonth' => false, 'weekofmonth' => false, 'monthofyear' => false, ); /** * Const'r * * @see Horde_ActiveSync_Message_Base::__construct() */ public function __construct(array $options = array()) { parent::__construct($options); if ($this->_version >= Horde_ActiveSync::VERSION_FOURTEEN) { $this->_mapping += array( Horde_ActiveSync_Message_Appointment::POOMCAL_CALENDARTYPE => array(self::KEY_ATTRIBUTE => 'calendartype'), Horde_ActiveSync_Message_Appointment::POOMCAL_ISLEAPMONTH => array(self::KEY_ATTRIBUTE => 'isleapmonth')); $this->_properties += array( 'calendartype' => false, 'isleapmonth' => false); } if ($this->_version >= Horde_ActiveSync::VERSION_FOURTEENONE) { $this->_mapping += array( Horde_ActiveSync_Message_Appointment::POOMCAL_FIRSTDAYOFWEEK => array(self::KEY_ATTRIBUTE => 'firstdayofweek') ); $this->_properties += array( 'firstdayofweek' => false ); } } } Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Message/ResolveRecipientsPicture.php0000664000076500000240000000343312654565405024703 0ustar * @package ActiveSync */ /** * Horde_ActiveSync_Message_ResolveRecipientsPicture:: Encapsulate the picture * data to send in a RESOLVERECIPIENTS response. * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2013-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync * * @property integer $status The status of the recipient's picture. * @property string|stream data The picture data. * */ class Horde_ActiveSync_Message_ResolveRecipientsPicture extends Horde_ActiveSync_Message_Base { /** * Property mapping * * @var array */ protected $_mapping = array( Horde_ActiveSync_Message_ResolveRecipients::TAG_STATUS => array(self::KEY_ATTRIBUTE => 'status'), Horde_ActiveSync_Message_ResolveRecipients::TAG_DATA => array(self::KEY_ATTRIBUTE => 'data') ); /** * Property values. * * @var array */ protected $_properties = array( 'status' => false, 'data' => false ); }Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Message/SendMail.php0000664000076500000240000001413012654565405021372 0ustar * @package ActiveSync */ /** * Horde_ActiveSync_Message_SendMail:: * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2013-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync * * @property string $clientid The client's temporary clientid for this * item. * @property boolean $saveinsent Flag to indicate whether to save in sent * mail. * @property boolean $replacemime Flag to indicate we are replacing the * Full MIME data (i.e., not a SMART item). * @property string $accountid The accountid. * @property Horde_ActiveSync_Message_SendMailSource $source * The email source. * @property string|stream mime The MIME contents of the message. * @property string $templateid The templateid. * @property string $forwardees An array of Forwardee objects. * EAS 16.0 Only. */ class Horde_ActiveSync_Message_SendMail extends Horde_ActiveSync_Message_Base { const COMPOSEMAIL_SENDMAIL = 'ComposeMail:SendMail'; const COMPOSEMAIL_SMARTFORWARD = 'ComposeMail:SmartForward'; const COMPOSEMAIL_SMARTREPLY = 'ComposeMail:SmartReply'; const COMPOSEMAIL_SAVEINSENTITEMS = 'ComposeMail:SaveInSentItems'; const COMPOSEMAIL_REPLACEMIME = 'ComposeMail:ReplaceMime'; const COMPOSEMAIL_TYPE = 'ComposeMail:Type'; const COMPOSEMAIL_SOURCE = 'ComposeMail:Source'; const COMPOSEMAIL_MIME = 'ComposeMail:MIME'; const COMPOSEMAIL_CLIENTID = 'ComposeMail:ClientId'; const COMPOSEMAIL_STATUS = 'ComposeMail:Status'; const COMPOSEMAIL_ACCOUNTID = 'ComposeMail:AccountId'; // 16.0 const COMPOSEMAIL_FORWARDEES = 'ComposeMail:Forwardees'; const COMPOSEMAIL_FORWARDEE = 'ComposeMail:Forwardee'; const COMPOSEMAIL_FORWARDEENAME = 'ComposeMail:ForwardeeName'; const COMPOSEMAIL_FORWARDEEEMAIL = 'ComposeMail:ForwardeeEmail'; /** * Property mapping * * @var array */ protected $_mapping = array ( self::COMPOSEMAIL_CLIENTID => array(self::KEY_ATTRIBUTE => 'clientid'), self::COMPOSEMAIL_SAVEINSENTITEMS => array(self::KEY_ATTRIBUTE => 'saveinsent'), self::COMPOSEMAIL_REPLACEMIME => array(self::KEY_ATTRIBUTE => 'replacemime'), self::COMPOSEMAIL_ACCOUNTID => array(self::KEY_ATTRIBUTE => 'accountid'), self::COMPOSEMAIL_SOURCE => array(self::KEY_ATTRIBUTE => 'source', self::KEY_TYPE => 'Horde_ActiveSync_Message_SendMailSource'), self::COMPOSEMAIL_MIME => array(self::KEY_ATTRIBUTE => 'mime'), Horde_ActiveSync::RM_TEMPLATEID => array(self::KEY_ATTRIBUTE => 'templateid') ); /** * Property values. * * @var array */ protected $_properties = array( 'clientid' => false, 'saveinsent' => false, 'replacemime' => false, 'accountid' => false, 'source' => false, 'mime' => false, 'templateid' => false, ); /** * Const'r * * @see Horde_ActiveSync_Message_Base::__construct() */ public function __construct(array $options = array()) { parent::__construct($options); if ($this->_version >= Horde_ActiveSync::VERSION_SIXTEEN) { $this->_mapping += array( self::COMPOSEMAIL_FORWARDEES => array(self::KEY_ATTRIBUTE => 'forwardees', self::KEY_TYPE => 'Horde_ActiveSync_Message_Forwardee', self::KEY_VALUES => self::COMPOSEMAIL_FORWARDEE), ); $this->_properties += array( 'forwardees' => false ); } } public function &__get($property) { // The saveinsent is an empty tag, and is considered true if it is // present. // Deal with the empty tags that are considered true if they are present switch ($property) { case 'saveinsent': case 'replacemime': $return = $this->_properties[$property] !== false; return $return; } return parent::__get($property); } /** * Return this object's folder class * * @return string */ public function getClass() { return 'SendMail'; } /** * Check if a field should be sent to the device even if it is empty. * * @param string $tag The field tag. * * @return boolean */ protected function _checkSendEmpty($tag) { if ($tag == self::COMPOSEMAIL_SAVEINSENTITEMS || $tag == self::COMPOSEMAIL_REPLACEMIME) { return true; } return false; } }Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Message/SendMailSource.php0000664000076500000240000000563012654565405022560 0ustar * @package ActiveSync */ /** * Horde_ActiveSync_Message_SendMailSource:: * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2013-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync * * @property string $folderid The item's folderid. * @property string $itemid The item's itemid. * @property string $longid The item's longid. * @property string $instanceid The item's instanceid. */ class Horde_ActiveSync_Message_SendMailSource extends Horde_ActiveSync_Message_Base { const COMPOSEMAIL_FOLDERID = 'ComposeMail:FolderId'; const COMPOSEMAIL_ITEMID = 'ComposeMail:ItemId'; const COMPOSEMAIL_LONGID = 'ComposeMail:LongId'; const COMPOSEMAIL_INSTANCEID = 'ComposeMail:InstanceId'; /** * Property mapping * * @var array */ protected $_mapping = array ( self::COMPOSEMAIL_FOLDERID => array(self::KEY_ATTRIBUTE => 'folderid'), self::COMPOSEMAIL_ITEMID => array(self::KEY_ATTRIBUTE => 'itemid'), self::COMPOSEMAIL_LONGID => array(self::KEY_ATTRIBUTE => 'longid'), self::COMPOSEMAIL_INSTANCEID => array(self::KEY_ATTRIBUTE => 'instanceid') ); /** * Property values. * * @var array */ protected $_properties = array( 'folderid' => false, 'itemid' => false, 'longid' => false, 'instanceid' => false, ); /** * Return this object's folder class * * @return string */ public function getClass() { return 'SendMailSource'; } }Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Message/Task.php0000664000076500000240000003455012654565405020610 0ustar * @package ActiveSync */ /** * Horde_ActiveSync_Message_Task:: * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2010-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync * * @property boolean $complete Completion flag * @property Horde_Date $datecompleted The date the task was completed, in UTC. * @property Horde_Date $utcduedate The date this task is due, in UTC. * @property integer $importance The importance flag. * @property Horde_ActiveSync_Message_TaskRecurrence $recurrence * The recurrence object. * @property integer $sensitivity The sensitivity flag. * @property Horde_Date $utcstartdate The date this task starts, in UTC. * @property string $subject The task subject. * @property array $categories An array of categories. * @property string $body The task body (EAS Version < 12.0) * @property boolean $bodytruncated Truncation flag (EAS Version < 12.0) * @property Horde_ActiveSync_Message_AirSyncBaseBody $airsyncbasebody * The task body (EAS Version >= 12.0) */ class Horde_ActiveSync_Message_Task extends Horde_ActiveSync_Message_Base { /* POOMTASKS */ const POOMTASKS_BODY = 'POOMTASKS:Body'; const POOMTASKS_BODYSIZE = 'POOMTASKS:BodySize'; const POOMTASKS_BODYTRUNCATED = 'POOMTASKS:BodyTruncated'; const POOMTASKS_CATEGORIES = 'POOMTASKS:Categories'; const POOMTASKS_CATEGORY = 'POOMTASKS:Category'; const POOMTASKS_COMPLETE = 'POOMTASKS:Complete'; const POOMTASKS_DATECOMPLETED = 'POOMTASKS:DateCompleted'; const POOMTASKS_DUEDATE = 'POOMTASKS:DueDate'; const POOMTASKS_UTCDUEDATE = 'POOMTASKS:UtcDueDate'; const POOMTASKS_IMPORTANCE = 'POOMTASKS:Importance'; const POOMTASKS_RECURRENCE = 'POOMTASKS:Recurrence'; const POOMTASKS_TYPE = 'POOMTASKS:Type'; const POOMTASKS_START = 'POOMTASKS:Start'; const POOMTASKS_UNTIL = 'POOMTASKS:Until'; const POOMTASKS_OCCURRENCES = 'POOMTASKS:Occurrences'; const POOMTASKS_INTERVAL = 'POOMTASKS:Interval'; const POOMTASKS_DAYOFWEEK = 'POOMTASKS:DayOfWeek'; const POOMTASKS_DAYOFMONTH = 'POOMTASKS:DayOfMonth'; const POOMTASKS_WEEKOFMONTH = 'POOMTASKS:WeekOfMonth'; const POOMTASKS_MONTHOFYEAR = 'POOMTASKS:MonthOfYear'; const POOMTASKS_REGENERATE = 'POOMTASKS:Regenerate'; const POOMTASKS_DEADOCCUR = 'POOMTASKS:DeadOccur'; const POOMTASKS_REMINDERSET = 'POOMTASKS:ReminderSet'; const POOMTASKS_REMINDERTIME = 'POOMTASKS:ReminderTime'; const POOMTASKS_SENSITIVITY = 'POOMTASKS:Sensitivity'; const POOMTASKS_STARTDATE = 'POOMTASKS:StartDate'; const POOMTASKS_UTCSTARTDATE = 'POOMTASKS:UtcStartDate'; const POOMTASKS_SUBJECT = 'POOMTASKS:Subject'; const POOMTASKS_RTF = 'POOMTASKS:Rtf'; // EAS 12.0 const POOMTASKS_ORDINALDATE = 'POOMTASKS:OrdinalDate'; const POOMTASKS_SUBORDINALDATE = 'POOMTASKS:SubOrdinalDate'; // EAS 14 const POOMTASKS_CALENDARTYPE = 'POOMTASKS:CalendarType'; const POOMTASKS_ISLEAPMONTH = 'POOMTASKS:IsLeapMonth'; const POOMTASKS_FIRSTDAYOFWEEK = 'POOMTASKS::FirstDayOfWeek'; const TASK_COMPLETE_TRUE = 1; const TASK_COMPLETE_FALSE = 0; const IMPORTANCE_LOW = 0; const IMPORTANCE_NORMAL = 1; const IMPORTANCE_HIGH = 2; const REMINDER_SET_FALSE = 0; const REMINDER_SET_TRUE = 1; /** * DOW mapping for DATE to MASK * * @var array */ protected $_dayOfWeekMap = array( Horde_Date::DATE_SUNDAY => Horde_Date::MASK_SUNDAY, Horde_Date::DATE_MONDAY => Horde_Date::MASK_MONDAY, Horde_Date::DATE_TUESDAY => Horde_Date::MASK_TUESDAY, Horde_Date::DATE_WEDNESDAY => Horde_Date::MASK_WEDNESDAY, Horde_Date::DATE_THURSDAY => Horde_Date::MASK_THURSDAY, Horde_Date::DATE_FRIDAY => Horde_Date::MASK_FRIDAY, Horde_Date::DATE_SATURDAY => Horde_Date::MASK_SATURDAY, ); /** * Property mapping * * @var array */ protected $_mapping = array ( self::POOMTASKS_COMPLETE => array (self::KEY_ATTRIBUTE => 'complete'), self::POOMTASKS_DATECOMPLETED => array (self::KEY_ATTRIBUTE => 'datecompleted', self::KEY_TYPE => self::TYPE_DATE_DASHES), self::POOMTASKS_DUEDATE => array (self::KEY_ATTRIBUTE => 'duedate', self::KEY_TYPE => self::TYPE_DATE_DASHES), self::POOMTASKS_UTCDUEDATE => array (self::KEY_ATTRIBUTE => 'utcduedate', self::KEY_TYPE => self::TYPE_DATE_DASHES), self::POOMTASKS_IMPORTANCE => array (self::KEY_ATTRIBUTE => 'importance'), self::POOMTASKS_RECURRENCE => array (self::KEY_ATTRIBUTE => 'recurrence', self::KEY_TYPE => 'Horde_ActiveSync_Message_TaskRecurrence'), self::POOMTASKS_REMINDERSET => array (self::KEY_ATTRIBUTE => 'reminderset'), self::POOMTASKS_REMINDERTIME => array (self::KEY_ATTRIBUTE => 'remindertime', self::KEY_TYPE => self::TYPE_DATE_DASHES), self::POOMTASKS_SENSITIVITY => array (self::KEY_ATTRIBUTE => 'sensitiviy'), self::POOMTASKS_STARTDATE => array (self::KEY_ATTRIBUTE => 'startdate', self::KEY_TYPE => self::TYPE_DATE_DASHES), self::POOMTASKS_UTCSTARTDATE => array (self::KEY_ATTRIBUTE => 'utcstartdate', self::KEY_TYPE => self::TYPE_DATE_DASHES), self::POOMTASKS_SUBJECT => array (self::KEY_ATTRIBUTE => 'subject'), self::POOMTASKS_CATEGORIES => array (self::KEY_ATTRIBUTE => 'categories', self::KEY_VALUES => self::POOMTASKS_CATEGORY), ); /** * Property values. * * @var array */ protected $_properties = array( 'subject' => false, 'importance' => false, 'categories' => array(), 'startdate' => false, 'duedate' => false, 'utcduedate' => false, 'complete' => false, 'datecompleted' => false, 'remindertime' => false, 'sensitiviy' => false, 'reminderset' => false, 'deadoccur' => false, 'recurrence' => false, 'regenerate' => false, 'sensitiviy' => false, 'utcstartdate' => false, ); /** * Const'r * * @see Horde_ActiveSync_Message_Base::__construct() */ public function __construct(array $options = array()) { parent::__construct($options); if ($this->_version < Horde_ActiveSync::VERSION_TWELVE) { $this->_mapping += array( self::POOMTASKS_BODY => array(self::KEY_ATTRIBUTE => 'body'), self::POOMTASKS_RTF => array(self::KEY_ATTRIBUTE => 'rtf'), self::POOMTASKS_BODYTRUNCATED => array(self::KEY_ATTRIBUTE => 'bodytruncated') ); $this->_properties += array( 'body' => false, 'rtf' => false, 'bodytruncated' => 0, ); } else { $this->_mapping += array( Horde_ActiveSync::AIRSYNCBASE_BODY => array(self::KEY_ATTRIBUTE => 'airsyncbasebody', self::KEY_TYPE=> 'Horde_ActiveSync_Message_AirSyncBaseBody'), ); $this->_properties += array( 'airsyncbasebody' => false, ); } } /** * Set the importance * * @param integer $importance A IMPORTANCE_* flag */ public function setImportance($importance) { if (is_null($importance)) { $importance = self::IMPORTANCE_NORMAL; } $this->_properties['importance'] = $importance; } /** * Get the task importance level * * @return integer A IMPORTANCE_* constant */ public function getImportance() { return $this->_getAttribute('importance', self::IMPORTANCE_NORMAL); } /** * Set the reminder datetime * * @param Horde_Date $datetime The time to trigger the alarm in local tz. */ public function setReminder(Horde_Date $datetime) { $this->_properties['remindertime'] = $datetime; $this->_properties['reminderset'] = self::REMINDER_SET_TRUE; } /** * Get the reminder time. * * @return Horde_Date in local tz */ public function getReminder() { if (!$this->_getAttribute('reminderset')) { return false; } return $this->_getAttribute('remindertime'); } /** * Set recurrence information for this task * * @param Horde_Date_Recurrence $recurrence */ public function setRecurrence(Horde_Date_Recurrence $recurrence) { $r = Horde_ActiveSync::messageFactory('TaskRecurrence'); // Map the type fields switch ($recurrence->recurType) { case Horde_Date_Recurrence::RECUR_DAILY: $r->type = Horde_ActiveSync_Message_Recurrence::TYPE_DAILY; break; case Horde_Date_Recurrence::RECUR_WEEKLY: $r->type = Horde_ActiveSync_Message_Recurrence::TYPE_WEEKLY; $r->dayofweek = $recurrence->getRecurOnDays(); break; case Horde_Date_Recurrence::RECUR_MONTHLY_DATE: $r->type = Horde_ActiveSync_Message_Recurrence::TYPE_MONTHLY; $r->dayofmonth = $recurrence->start->mday; break; case Horde_Date_Recurrence::RECUR_MONTHLY_WEEKDAY; $r->type = Horde_ActiveSync_Message_Recurrence::TYPE_MONTHLY_NTH; $r->weekofmonth = ceil($recurrence->start->mday / 7); $r->dayofweek = $this->_dayOfWeekMap[$recurrence->start->dayOfWeek()]; break; case Horde_Date_Recurrence::RECUR_YEARLY_DATE: $r->type = Horde_ActiveSync_Message_Recurrence::TYPE_YEARLY; break; case Horde_Date_Recurrence::RECUR_YEARLY_WEEKDAY: $r->type = Horde_ActiveSync_Message_Recurrence::TYPE_YEARLYNTH; $r->dayofweek = $this->_dayOfWeekMap[$recurrence->start->dayOfWeek()]; $r->weekofmonth = ceil($recurrence->start->mday / 7); $r->monthofyear = $recurrence->start->month; break; } if (!empty($recurrence->recurInterval)) { $r->interval = $recurrence->recurInterval; } // AS messages can only have one or the other (or none), not both if ($recurrence->hasRecurCount()) { $r->occurrences = $recurrence->getRecurCount(); } elseif ($recurrence->hasRecurEnd()) { $r->until = $recurrence->getRecurEnd(); } // Set the start of the recurrence series. $r->start = clone $this->duedate; $this->_properties['recurrence'] = $r; } /** * Obtain a recurrence object. Note this returns a Horde_Date_Recurrence * object, not Horde_ActiveSync_Message_Recurrence. * * @return Horde_Date_Recurrence */ public function getRecurrence() { if (!$recurrence = $this->_getAttribute('recurrence')) { return false; } $d = clone($this->getDueDate()); // $d->setTimezone($this->getTimezone()); $rrule = new Horde_Date_Recurrence($d); /* Map MS AS type field to Horde_Date_Recurrence types */ switch ($recurrence->type) { case Horde_ActiveSync_Message_Recurrence::TYPE_DAILY: $rrule->setRecurType(Horde_Date_Recurrence::RECUR_DAILY); break; case Horde_ActiveSync_Message_Recurrence::TYPE_WEEKLY: $rrule->setRecurType(Horde_Date_Recurrence::RECUR_WEEKLY); $rrule->setRecurOnDay($recurrence->dayofweek); break; case Horde_ActiveSync_Message_Recurrence::TYPE_MONTHLY: $rrule->setRecurType(Horde_Date_Recurrence::RECUR_MONTHLY_DATE); break; case Horde_ActiveSync_Message_Recurrence::TYPE_MONTHLY_NTH: $rrule->setRecurType(Horde_Date_Recurrence::RECUR_MONTHLY_WEEKDAY); $rrule->setRecurOnDay($recurrence->dayofweek); break; case Horde_ActiveSync_Message_Recurrence::TYPE_YEARLY: $rrule->setRecurType(Horde_Date_Recurrence::RECUR_YEARLY_DATE); break; case Horde_ActiveSync_Message_Recurrence::TYPE_YEARLYNTH: $rrule->setRecurType(Horde_Date_Recurrence::RECUR_YEARLY_WEEKDAY); $rrule->setRecurOnDay($recurrence->dayofweek); break; } if ($rcnt = $recurrence->occurrences) { $rrule->setRecurCount($rcnt); } if ($runtil = $recurrence->until) { $rrule->setRecurEnd(new Horde_Date($runtil)); } if ($interval = $recurrence->interval) { $rrule->setRecurInterval($interval); } return $rrule; } /** * Return this object's folder class * * @return string */ public function getClass() { return 'Tasks'; } /** * Check if a field should be sent to the device even if it is empty. * * @param string $tag The field tag. * * @return boolean */ protected function _checkSendEmpty($tag) { if ($tag == self::POOMTASKS_BODYTRUNCATED && $this->bodysize > 0) { return true; } return false; } }Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Message/TaskRecurrence.php0000664000076500000240000001103712654565405022621 0ustar * @package ActiveSync */ /** * Horde_ActiveSync_Message_TaskRecurrence:: * * Portions of this class were ported from the Z-Push project: * File : wbxml.php * Project : Z-Push * Descr : WBXML mapping file * * Created : 01.10.2007 * * � Zarafa Deutschland GmbH, www.zarafaserver.de * This file is distributed under GPL-2.0. * Consult COPYING file for details * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2012-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync * * @property integer $type * @property Horde_Date $start * @property Horde_Date $until * @property string $occurrences * @property integer $interval * @property integer $dayofweek * @property integer $dayofmonth * @property integer $weekofmonth * @property integer $monthofyear */ class Horde_ActiveSync_Message_TaskRecurrence extends Horde_ActiveSync_Message_Base { /* MS AS Recurrence types */ const TYPE_DAILY = 0; const TYPE_WEEKLY = 1; const TYPE_MONTHLY = 2; const TYPE_MONTHLY_NTH = 3; const TYPE_YEARLY = 5; const TYPE_YEARLYNTH = 6; /** * Property mapping. * * @var array */ protected $_mapping = array ( Horde_ActiveSync_Message_Task::POOMTASKS_REGENERATE => array (self::KEY_ATTRIBUTE => 'regenerate'), Horde_ActiveSync_Message_Task::POOMTASKS_INTERVAL => array(self::KEY_ATTRIBUTE => 'interval'), Horde_ActiveSync_Message_Task::POOMTASKS_START => array(self::KEY_ATTRIBUTE => 'start', self::KEY_TYPE => self::TYPE_DATE_DASHES), Horde_ActiveSync_Message_Task::POOMTASKS_TYPE => array(self::KEY_ATTRIBUTE => 'type'), Horde_ActiveSync_Message_Task::POOMTASKS_UNTIL => array(self::KEY_ATTRIBUTE => 'until', self::KEY_TYPE => self::TYPE_DATE_DASHES), Horde_ActiveSync_Message_Task::POOMTASKS_OCCURRENCES => array(self::KEY_ATTRIBUTE => 'occurrences'), Horde_ActiveSync_Message_Task::POOMTASKS_DAYOFWEEK => array(self::KEY_ATTRIBUTE => 'dayofweek'), Horde_ActiveSync_Message_Task::POOMTASKS_DAYOFMONTH => array(self::KEY_ATTRIBUTE => 'dayofmonth'), Horde_ActiveSync_Message_Task::POOMTASKS_WEEKOFMONTH => array(self::KEY_ATTRIBUTE => 'weekofmonth'), Horde_ActiveSync_Message_Task::POOMTASKS_MONTHOFYEAR => array(self::KEY_ATTRIBUTE => 'monthofyear'), Horde_ActiveSync_Message_Task::POOMTASKS_DEADOCCUR => array (self::KEY_ATTRIBUTE => 'deadoccur'), Horde_ActiveSync_Message_Task::POOMTASKS_CALENDARTYPE => array(self::KEY_ATTRIBUTE => 'calendartype'), Horde_ActiveSync_Message_Task::POOMTASKS_ISLEAPMONTH => array(self::KEY_ATTRIBUTE => 'isleapmonth'), Horde_ActiveSync_Message_Task::POOMTASKS_FIRSTDAYOFWEEK => array(self::KEY_ATTRIBUTE => 'firstdayofweek'), ); /** * Property values. * * @var array */ protected $_properties = array( 'type' => false, 'start' => false, 'until' => false, 'occurrences' => false, 'interval' => false, 'dayofweek' => false, 'dayofmonth' => false, 'weekofmonth' => false, 'monthofyear' => false, 'regenerate' => false, 'deadoccur' => false, 'calendartype' => false, 'isleapmonth' => false, 'firstdayofweek' => false, ); }Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Mime/Iterator.php0000664000076500000240000001132712654565405020777 0ustar * @author Michael Slusarz * @category Horde * @copyright 2015-2016 Horde LLC * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1 * @package ActiveSync * @since 2.29.0 */ class Horde_ActiveSync_Mime_Iterator implements Countable, Iterator { /** * Flag to ignore parts that EAS considers attachments. * * @var boolean */ protected $_ignoreAttachments; /** * Base part. * * @var Horde_Mime_Part */ protected $_part; /** * State data. * * @var object */ protected $_state; /** * Constructor. */ public function __construct(Horde_Mime_Part $part, $ignoreAttachments = false) { $this->_ignoreAttachments = $ignoreAttachments; $this->_part = $part; } /* Countable methods. */ /** * Returns the number of message parts. * * @return integer Number of message parts. */ public function count() { return count(iterator_to_array($this)); } protected function _isAttachment($part) { if ($part->getDisposition() == 'attachment') { return true; } $id = $part->getMimeId(); $mime_type = $part->getType(); switch ($mime_type) { case 'text/plain': if (!($this->_part->findBody('plain') == $id)) { return true; } return false; case 'text/html': if (!($this->_part->findBody('html') == $id)) { return true; } return false; case 'application/pkcs7-signature': case 'application/x-pkcs7-signature': return false; } list($ptype,) = explode('/', $mime_type, 2); switch ($ptype) { case 'message': return in_array($mime_type, array('message/rfc822', 'message/disposition-notification')); case 'multipart': return false; default: return true; } } /** * Return whether or not to allow recursion into a mime part when iterating * all of the parts. So far, only disallows this for message/rfc822 parts * to prevent each mime part of the rfc822 part to display as an attachment. * * @param Horde_Mime_Part $part The part to check. * * @return boolean True is we can descend into the part. False otherwise. */ protected function _allowRecursion($part) { return !in_array($part->getType(), array('message/rfc822')); } /* RecursiveIterator methods. */ /** */ public function current() { return $this->valid() ? $this->_state->current : null; } /** */ public function key() { return ($curr = $this->current()) ? $curr->getMimeId() : null; } /** */ public function next() { if (!isset($this->_state)) { return; } $out = $this->_state->current->getPartByIndex($this->_state->index++); if ($out) { if (($this->_ignoreAttachments && $this->_isAttachment($out)) || !$this->_allowRecursion($this->_state->current)) { return $this->next(); } $this->_state->recurse[] = array( $this->_state->current, $this->_state->index ); $this->_state->current = $out; $this->_state->index = 0; } elseif ($tmp = array_pop($this->_state->recurse)) { $this->_state->current = $tmp[0]; $this->_state->index = $tmp[1]; $this->next(); } else { unset($this->_state); } } /** */ public function rewind() { $this->_state = new stdClass; $this->_state->current = $this->_part; $this->_state->index = 0; $this->_state->recurse = array(); } /** */ public function valid() { return !empty($this->_state); } } Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Request/Autodiscover.php0000664000076500000240000002372512654565405022423 0ustar * @package ActiveSync */ /** * ActiveSync Handler for Autodiscover requests. * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2012-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync * @internal */ class Horde_ActiveSync_Request_Autodiscover extends Horde_ActiveSync_Request_Base { /** * Handle request * * @return text The content type of the response (text/xml). */ public function handle(Horde_Controller_Request $request = null) { $parser = xml_parser_create(); xml_parse_into_struct( $parser, $this->_decoder->getStream()->getString(), $values); // Get $_SERVER $server = $request->getServerVars(); // Obtain the credentials sent by the client. // NOTE: Some broken clients *cough* android *cough* don't send the // actual XML data structure at all, but instead use the email address // as the username in the HTTP_AUTHENTICATION data. There are so many // things wrong with this, but try to work around it if we can. $credentials = new Horde_ActiveSync_Credentials($this->_activeSync); $username = $credentials->username; if (empty($values) && empty($username)) { throw new Horde_Exception_AuthenticationFailure('No username provided.'); } elseif (!empty($values)) { // Override the username; AUTODISCOVER MUST use email address. $credentials->username = $values[2]['value']; } if (!$this->_activeSync->authenticate($credentials)) { throw new Horde_Exception_AuthenticationFailure(); } if (!empty($values)) { $params = array('request_schema' => trim($values[0]['attributes']['XMLNS'])); // Response Schema is not in a set place. foreach ($values as $value) { if ($value['tag'] == 'ACCEPTABLERESPONSESCHEMA') { $params['response_schema'] = trim($value['value']); break; } } } else { // Assume broken clients want these schemas. $params = array( 'request_schema' => 'http://schemas.microsoft.com/exchange/autodiscover/mobilesync/requestschema/2006', 'response_schema' => 'http://schemas.microsoft.com/exchange/autodiscover/mobilesync/responseschema/2006' ); } $results = $this->_driver->autoDiscover($params); if (empty($results['raw_xml'])) { $this->_encoder->getStream()->add($this->_buildResponseString($results)); } else { // The backend is taking control of the XML. $this->_encoder->getStream()->add($results['raw_xml']); } return 'text/xml'; } /** * Noop. This class overrides the handle method. */ protected function _handle() { } /** * Build the appropriate response string to send back to the client. * * @param array $properties An array containing any needed properties. * Required properties for mobile sync: * - request_schema: The request schema sent by the client. * - response_schema: The schema the client indicated it can accept. * - culture: The culture value (normally 'en:en'). * - display_name: The user's configured display name. * - email: The user's email address. * - url: The url of the Microsoft-Servers-ActiveSync endpoint for this * user to use. * * Properties used for Outlook schema: * - imap: Array describing the IMAP server. * - pop: Array describing the POP3 server. * - smtp: Array describing the SMTP server. * * @return string The XML to return to the client. */ protected function _buildResponseString($properties) { // Default response is for mobilesync. if (empty($properties['request_schema']) || stripos($properties['request_schema'], 'autodiscover/mobilesync') !== false) { return ' ' . $properties['culture'] . ' ' . $properties['display_name'] . ' ' . $properties['email'] . ' MobileSync ' . $properties['url'] . ' ' . $properties['url'] . ' '; } elseif (stripos($properties['request_schema'], 'autodiscover/outlook') !== false) { if (empty($properties['response_schema'])) { // Missing required response_schema return $this->_buildFailureResponse($properties['email'], '600', 'http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a'); } $xml = ' ' . $properties['display_name'] . ' email settings'; if (!empty($properties['imap'])) { $xml .= ' IMAP ' . $properties['imap']['host'] . ' ' . $properties['imap']['port'] . ' ' . $properties['username'] . ' off off ' . ($properties['imap']['ssl'] ? 'on' : 'off') . ' on '; } if (!empty($properties['pop'])) { $xml .= ' POP3 ' . $properties['pop']['host'] . ' ' . $properties['pop']['port'] . ' ' . $properties['username'] . ' off off ' . ($properties['pop']['ssl'] ? 'on' : 'off') . ' on '; } if (!empty($properties['smtp'])) { $xml .= ' SMTP ' . $properties['smtp']['host'] . ' ' . $properties['smtp']['port'] . ' ' . $properties['username'] . ' off off ' . ($properties['smtp']['ssl'] ? 'on' : 'off') . ' on ' . ($properties['smtp']['popauth'] ? 'on' : 'off') . ' '; } $xml .= ' '; return $xml; } else { // Unknown request. return $this->_buildFailureResponse($properties['email'], '600', $properties['response_schema']); } } /** * Output failure response code. * * @param string $email The email of the user attempting Autodiscover. * @param string $status An appropriate status code for the error. E.g., * 600 - Invalid response. * 601 - Provider not found for requested schema. * @param string $response_schema The response schema value. * * @return string The XML to send to the client. */ protected function _buildFailureResponse($email, $status, $response_schema) { return ' en:us ' . $email . ' ' . $status . ' Unable to autoconfigure the supplied email address. MailUser '; } } Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Request/Base.php0000664000076500000240000002233612654565405020623 0ustar * @package ActiveSync */ /** * Base class for handlig ActiveSync requests. * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2010-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync * @internal */ abstract class Horde_ActiveSync_Request_Base { /** * Driver for communicating with the backend datastore. * * @var Horde_ActiveSync_Driver_Base */ protected $_driver; /** * State driver * * @var Horde_ActiveSync_State_Base */ protected $_state; /** * Encoder * * @var Horde_ActiveSync_Wbxml_Encoder */ protected $_encoder; /** * Decoder * * @var Horde_ActiveSync_Wbxml_Decoder */ protected $_decoder; /** * Request object * * @var Horde_Controller_Request_Http */ protected $_request; /** * Whether we require provisioned devices. * Valid values are: * - Horde_ActiveSync::PROVISIONING_FORCE: Accept ONLY provisioned devices * - Horde_ActiveSync::PROVISIONING_LOOSE: Force provisioning if device * supports provisioning, allow non-provisionable devices as well. * - Horde_ActiveSync::PROVISIONING_NONE: Allow any device. * * @var integer */ protected $_provisioning; /** * Used to track what error code to send back to client on failure * * @var integer */ protected $_statusCode = 0; /** * ActiveSync server * * @var Horde_ActiveSync */ protected $_activeSync; /** * Logger * * @var Horde_Log_Logger */ protected $_logger; /** * The device info * * @var Horde_ActiveSync_Device */ protected $_device; /** * The procid * * @var integer */ protected $_procid; /** * Const'r * * @param Horde_ActiveSync $as The ActiveSync server. * @param Horde_ActiveSync_Device $device The device descriptor. * * @return Horde_ActiveSync_Request_Base */ public function __construct(Horde_ActiveSync $as) { // Server $this->_activeSync = $as; // Backend driver $this->_driver = $as->driver; // Wbxml handlers $this->_encoder = $as->encoder; $this->_decoder = $as->decoder; // The http request $this->_request = $as->request; // Provisioning support $this->_provisioning = $as->provisioning; // Get the state object $this->_state = $as->state; // Device info $this->_device = $as->device; // Procid $this->_procid = getmypid(); } /** * Ensure the client's policy key is current. * * @param string $sentKey The policykey sent to us by the client * @param string $requestType The type of request we are handling. A * Horde_ActiveSync constant. * * @return boolean */ public function checkPolicyKey($sentKey, $requestType = null) { $this->_logger->info(sprintf( '[%s] Checking policykey for device: %s user: %s', $this->_procid, $this->_device->id, $this->_driver->getUser())); // Use looseprovisioning? if (empty($sentKey) && !$this->_device->enforceProvisioning() && $this->_provisioning === Horde_ActiveSync::PROVISIONING_LOOSE) { $sentKey = null; $this->_logger->info(sprintf( '[%s] Allowing %s to connect since PROVISIONING_LOOSE is true and is either non-provisionable or has broken provisioning.', $this->_procid, $this->_device->id)); } elseif (empty($sentKey) && $this->_device->isNonProvisionable()) { // Check for non-provisionable, but allowable, devices. $this->_logger->info(sprintf( '[%s] Allowing %s to connect since it is non-provisionable.', $this->_procid, $this->_device->id)); $sentKey = null; } // Don't attempt if we don't care if ($this->_provisioning !== Horde_ActiveSync::PROVISIONING_NONE) { // Get the stored key $storedKey = $this->_state->getPolicyKey($this->_device->id); $this->_logger->info(sprintf( '[%s] Stored key: %s', $this->_procid, $storedKey)); // Did we request a remote wipe? if ($this->_state->getDeviceRWStatus($this->_device->id) == Horde_ActiveSync::RWSTATUS_PENDING) { $this->_requireProvisionWbxml($requestType, Horde_ActiveSync_Status::REMOTEWIPE_REQUESTED); return false; } // Validate the stored key against the device key, honoring // the value of _provisioning. if ((empty($storedKey) || $storedKey != $sentKey) && ($this->_provisioning != Horde_ActiveSync::PROVISIONING_LOOSE || ($this->_provisioning == Horde_ActiveSync::PROVISIONING_LOOSE && !is_null($sentKey)))) { // We send the headers AND the WBXML if EAS 12.1+ since some // devices report EAS 14.1 but don't accept the WBXML. $this->_activeSync->provisioningRequired(); if ($this->_device->version > Horde_ActiveSync::VERSION_TWELVEONE) { if (empty($sentKey)) { $status = Horde_ActiveSync_Status::DEVICE_NOT_PROVISIONED; } else { $status = Horde_ActiveSync_Status::INVALID_POLICY_KEY; } $this->_requireProvisionWbxml($requestType, Horde_ActiveSync_Status::DEVICE_NOT_PROVISIONED); } return false; } } // Either successfully validated, or we didn't care enough to check. $this->_logger->info(sprintf( '[%s] Policykey: %s verified.', $this->_procid, $sentKey)); return true; } /** * Set the logger. * * @var Horde_Log_Logger */ public function setLogger(Horde_Log_Logger $logger) { $this->_logger = $logger; } /** * Handle the request. * * @return boolean */ public function handle() { $this->_logger->info(sprintf( '[%s] Request being handled for device: %s, Supporting protocol version: %s, Using Horde_ActiveSync v%s', $this->_procid, $this->_device->id, $this->_device->version, Horde_ActiveSync::LIBRARY_VERSION) ); $this->_logger->info(sprintf( '[%s] GET VARIABLES: %s', $this->_procid, print_r($this->_activeSync->getGetVars(), true))); try { return $this->_handle(); } catch (Exception $e) { $this->_logger->err($e->getMessage()); throw $e; } } /** * Clean up after initial pairing. Initial pairing can happen either as a * result of either a FOLDERSYNC or PROVISION command, depending on the * device capabilities. */ protected function _cleanUpAfterPairing() { // Android sends a bogus device id of 'validate' during initial // handshake. This data is never used again, and the resulting // FOLDERSYNC response is ignored by the client. Remove the entry, // to avoid having 2 device entries for every android client. if ($this->_device->id == 'validate') { $this->_logger->info(sprintf( '[%s] Removing state for bogus VALIDATE device.', $this->_procid)); $this->_state->removeState(array('devId' => 'validate')); } } /** * Send WBXML to indicate provisioning is required. * * @param string $requestType The type of request we are handling. * @param integer $status The reason we need to provision. */ protected function _requireProvisionWbxml($requestType, $status) { $this->_encoder->startWBXML(); $this->_encoder->startTag($requestType); $this->_encoder->startTag(Horde_ActiveSync::SYNC_STATUS); $this->_encoder->content($status); $this->_encoder->endTag(); $this->_encoder->endTag(); } /** * Implementation method for handling request. * * @return string|boolean Content-Type of results if not wbxml, or boolean. */ abstract protected function _handle(); } Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Request/FolderCreate.php0000664000076500000240000002235712654565405022313 0ustar * @package ActiveSync */ /** * Handle FolderCreate requests. * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2009-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync * @internal */ class Horde_ActiveSync_Request_FolderCreate extends Horde_ActiveSync_Request_Base { const FOLDERCREATE = 'FolderHierarchy:FolderCreate'; const FOLDERDELETE = 'FolderHierarchy:FolderDelete'; const FOLDERUPDATE = 'FolderHierarchy:FolderUpdate'; const STATUS_SUCCESS = 1; const STATUS_ERROR = 6; const STATUS_KEYMISM = 9; /** * Handle request. * * Notes: For FOLDERCREATE requests or non-email collections, the parent * seems to be set to the root of that collection type and the EAS type is * included. For new root email folders, the parent is set to ROOT. * For FOLDERCHANGE requests, the type is NOT included so the backend must * be able to determine the folder type given the folder's id only. Also, * the parent element does not seem to be transmitted by the clients I * have tested, so even though it is included when creating the new * folder, it is NOT included when edting that same folder. *sigh* * * @return boolean */ protected function _handle() { $status = self::STATUS_SUCCESS; $create = $update = $delete = false; $this->_logger->info(sprintf( '[%s] Handling FOLDER[CREATE|DELETE|CHANGE] command.', $this->_device->id) ); $el = $this->_decoder->getElement(); if ($el[Horde_ActiveSync_Wbxml::EN_TYPE] != Horde_ActiveSync_Wbxml::EN_TYPE_STARTTAG) { throw new Horde_ActiveSync_Exception('Protocol Error'); } if ($el[Horde_ActiveSync_Wbxml::EN_TAG] == self::FOLDERCREATE) { $create = true; } elseif ($el[Horde_ActiveSync_Wbxml::EN_TAG] == self::FOLDERUPDATE) { $update = true; } elseif ($el[Horde_ActiveSync_Wbxml::EN_TAG] == self::FOLDERDELETE) { $delete = true; } if (!$create && !$update && !$delete) { $this->_logger->err('No CREATE/UPDATE/DELETE specified'); throw new Horde_ActiveSync_Exception('Protocol Error'); } // SyncKey if (!$this->_decoder->getElementStartTag(Horde_ActiveSync::FOLDERHIERARCHY_SYNCKEY)) { throw new Horde_ActiveSync_Exception('Protocol Error'); } $synckey = $this->_decoder->getElementContent(); if (!$this->_decoder->getElementEndTag()) { $this->_logger->err('No FOLDERSYNCKEY'); throw new Horde_ActiveSync_Exception('Protocol Error'); } // Server_uid (the EAS uid for this collection). // Another place we have to work around broken Blackberry clients that // send an empty SERVERENTRYID tag (Bug #13351, #12370). $server_uid = false; if ($this->_decoder->getElementStartTag(Horde_ActiveSync::FOLDERHIERARCHY_SERVERENTRYID)) { $server_uid = $this->_decoder->getElementContent(); if ($server_uid !== false && !$this->_decoder->getElementEndTag()) { throw new Horde_ActiveSync_Exception('Protocol Error - expecting '); } } if (!$delete) { $parentid = false; if ($this->_decoder->getElementStartTag(Horde_ActiveSync::FOLDERHIERARCHY_PARENTID)) { $parentid = $this->_decoder->getElementContent(); if (!$this->_decoder->getElementEndTag()) { throw new Horde_ActiveSync_Exception('Protocol Error'); } } if (!$this->_decoder->getElementStartTag(Horde_ActiveSync::FOLDERHIERARCHY_DISPLAYNAME)) { throw new Horde_ActiveSync_Exception('Protocol Error'); } $displayname = $this->_decoder->getElementContent(); if (!$this->_decoder->getElementEndTag()) { throw new Horde_ActiveSync_Exception('Protocol Error'); } $type = false; if ($this->_decoder->getElementStartTag(Horde_ActiveSync::FOLDERHIERARCHY_TYPE)) { $type = $this->_decoder->getElementContent(); if (!$this->_decoder->getElementEndTag()) { throw new Horde_ActiveSync_Exception('Protocol Error'); } } } if (!$this->_decoder->getElementEndTag()) { throw new Horde_ActiveSync_Exception('Protocol Error'); } $collections = $this->_activeSync->getCollectionsObject(); try { $collections->initHierarchySync($synckey); $newsynckey = $this->_state->getNewSyncKey($synckey); } catch (Horde_ActiveSync_Exception $e) { $status = self::STATUS_KEYMISM; } if ($status == self::STATUS_SUCCESS) { // Configure importer with last state $importer = $this->_activeSync->getImporter(); $importer->init($this->_state); if (!$delete) { try { $folder = $importer->importFolderChange($server_uid, $displayname, $parentid, $type); } catch (Horde_ActiveSync_Exception $e) { $this->_logger->err($e->getMessage()); if ($e->getCode() == Horde_ActiveSync_Exception::UNSUPPORTED) { $status = Horde_ActiveSync_Status::UNEXPECTED_ITEM_CLASS; } else { $status = self::STATUS_ERROR; } } } else { try { $importer->importFolderDeletion($server_uid); } catch (Horde_ActiveSync_Exception $e) { $status = self::STATUS_ERROR; } } } $this->_encoder->startWBXML(); if ($create) { if ($status == self::STATUS_SUCCESS) { $collections->updateFolderInHierarchy($folder, true); $collections->save(); } $this->_encoder->startTag(self::FOLDERCREATE); $this->_encoder->startTag(Horde_ActiveSync::FOLDERHIERARCHY_STATUS); $this->_encoder->content($status); $this->_encoder->endTag(); if ($status == self::STATUS_SUCCESS) { $this->_encoder->startTag(Horde_ActiveSync::FOLDERHIERARCHY_SYNCKEY); $this->_encoder->content($newsynckey); $this->_encoder->endTag(); $this->_encoder->startTag(Horde_ActiveSync::FOLDERHIERARCHY_SERVERENTRYID); $this->_encoder->content($folder->serverid); $this->_encoder->endTag(); } $this->_encoder->endTag(); } elseif ($update) { if ($status == self::STATUS_SUCCESS) { $collections->updateFolderInHierarchy($folder, true); $collections->save(); } $this->_encoder->startTag(self::FOLDERUPDATE); $this->_encoder->startTag(Horde_ActiveSync::FOLDERHIERARCHY_STATUS); $this->_encoder->content($status); $this->_encoder->endTag(); $this->_encoder->startTag(Horde_ActiveSync::FOLDERHIERARCHY_SYNCKEY); $this->_encoder->content($newsynckey); $this->_encoder->endTag(); $this->_encoder->endTag(); } elseif ($delete) { if ($status == self::STATUS_SUCCESS) { $collections->deleteFolderFromHierarchy($server_uid); } $this->_encoder->startTag(self::FOLDERDELETE); $this->_encoder->startTag(Horde_ActiveSync::FOLDERHIERARCHY_STATUS); $this->_encoder->content($status); $this->_encoder->endTag(); $this->_encoder->startTag(Horde_ActiveSync::FOLDERHIERARCHY_SYNCKEY); $this->_encoder->content($newsynckey); $this->_encoder->endTag(); $this->_encoder->endTag(); } $this->_encoder->endTag(); if ($status == self::STATUS_SUCCESS) { $this->_state->setNewSyncKey($newsynckey); $this->_state->save(); } return true; } }Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Request/FolderSync.php0000664000076500000240000002405712654565405022023 0ustar * @package ActiveSync */ /** * Handle FolderSync requests. * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2009-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync * @internal */ class Horde_ActiveSync_Request_FolderSync extends Horde_ActiveSync_Request_Base { const ADD = 'FolderHierarchy:Add'; const REMOVE = 'FolderHierarchy:Remove'; const UPDATE = 'FolderHierarchy:Update'; /* SYNC Status response codes */ const STATUS_SUCCESS = 1; const STATUS_SERVERERROR = 6; const STATUS_TIMEOUT = 8; const STATUS_KEYMISM = 9; const STATUS_PROTOERR = 10; /** * Handle the request. * * @return boolean */ protected function _handle() { // Be optimistic $this->_statusCode = self::STATUS_SUCCESS; $this->_logger->info(sprintf( '[%s] Handling FOLDERSYNC command.', $this->_procid)); // Check policy if (!$this->checkPolicyKey($this->_activeSync->getPolicyKey(), Horde_ActiveSync::FOLDERHIERARCHY_FOLDERSYNC)) { return true; } // Check global errors from pairing. if ($error = $this->_activeSync->checkGlobalError()) { $this->_statusCode = $error; $this->_handleError(); return true; } // Start parsing input if (!$this->_decoder->getElementStartTag(Horde_ActiveSync::FOLDERHIERARCHY_FOLDERSYNC)) { $this->_logger->err('[Horde_ActiveSync::handleFolderSync] No input to parse'); $this->_statusCode = self::STATUS_PROTOERR; $this->_handleError(); return true; } // Get the current synckey from client if (!$this->_decoder->getElementStartTag(Horde_ActiveSync::FOLDERHIERARCHY_SYNCKEY)) { $this->_logger->err('[Horde_ActiveSync::handleFolderSync] No input to parse'); $this->_statusCode = self::STATUS_PROTOERR; $this->_handleError(); return true; } $synckey = $this->_decoder->getElementContent(); if (!$this->_decoder->getElementEndTag()) { $this->_logger->err('[Horde_ActiveSync::handleFolderSync] No input to parse'); $this->_statusCode = self::STATUS_PROTOERR; $this->_handleError(); return true; } // Prepare the collections handler. $collections = $this->_activeSync->getCollectionsObject(); try { $seenfolders = $collections->initHierarchySync($synckey); } catch (Horde_ActiveSync_Exception $e) { $this->_statusCode = self::STATUS_KEYMISM; $this->_handleError($e); return true; } // Track if we have changes or not $changes = false; // Deal with folder hierarchy changes if ($this->_decoder->getElementStartTag(Horde_ActiveSync::FOLDERHIERARCHY_CHANGES)) { // Ignore if present if ($this->_decoder->getElementStartTag(Horde_ActiveSync::FOLDERHIERARCHY_COUNT)) { $this->_decoder->getElementContent(); if (!$this->_decoder->getElementEndTag()) { $this->_statusCode = self::STATUS_PROTOERR; $this->_handleError(); return true; } } // Process the incoming changes to folders $element = $this->_decoder->getElement(); if ($element[Horde_ActiveSync_Wbxml::EN_TYPE] != Horde_ActiveSync_Wbxml::EN_TYPE_STARTTAG) { $this->_statusCode = self::STATUS_PROTOERR; $this->_handleError(); return true; } // Configure importer with last state // @todo - integrate this with the collection manager. $importer = $this->_activeSync->getImporter(); $importer->init($this->_state, false); while (1) { $folder = Horde_ActiveSync::messageFactory('Folder'); if (!$folder->decodeStream($this->_decoder)) { break; } switch ($element[Horde_ActiveSync_Wbxml::EN_TAG]) { case SYNC_ADD: case SYNC_MODIFY: $new_server = $importer->importFolderChange( $folder->serverid, $folder->displayname); $serverid = $new_server->serverid; if (!in_array($serverid, $seenfolders)) { $seenfolders[] = $serverid; $collections->updateFolderInHierarchy($folder); } else { $collections->updateFolderInHierarchy($folder, true); } $changes = true; break; case SYNC_REMOVE: $importer->importFolderDeletion($folder->serverid); if (($sid = array_search($folder->serverid, $seenfolders)) !== false) { unset($seenfolders[$sid]); $seenfolders = array_values($seenfolders); } $collections->deleteFolderFromHierarchy($folder->serverid); $changes = true; break; } } if (!$this->_decoder->getElementEndTag()) { $this->_statusCode = self::STATUS_PROTOERR; $this->_handleError(); return true; } } if (!$this->_decoder->getElementEndTag()) { $this->_statusCode = self::STATUS_PROTOERR; $this->_handleError(); return true; } // Start sending server -> client changes $newsynckey = $this->_state->getNewSyncKey($synckey); $exporter = new Horde_ActiveSync_Connector_Exporter($this->_activeSync); $exporter->setChanges($collections->getHierarchyChanges(), false); // Perform the actual sync operation while($exporter->sendNextChange()); // Output our WBXML reply now $this->_encoder->StartWBXML(); $this->_encoder->startTag(Horde_ActiveSync::FOLDERHIERARCHY_FOLDERSYNC); $this->_encoder->startTag(Horde_ActiveSync::FOLDERHIERARCHY_STATUS); $this->_encoder->content($this->_statusCode); $this->_encoder->endTag(); $this->_encoder->startTag(Horde_ActiveSync::FOLDERHIERARCHY_SYNCKEY); $this->_encoder->content((($changes || $exporter->count > 0) ? $newsynckey : $synckey)); $this->_encoder->endTag(); $this->_encoder->startTag(Horde_ActiveSync::FOLDERHIERARCHY_CHANGES); // Validate/clean up the hierarchy changes. $collections->validateHierarchyChanges($exporter, $seenfolders); $collections->updateHierarchyKey($changes || $exporter->count > 0 ? $newsynckey : $synckey); $this->_encoder->startTag(Horde_ActiveSync::FOLDERHIERARCHY_COUNT); $this->_encoder->content($exporter->count); $this->_encoder->endTag(); if (count($exporter->changed) > 0) { foreach ($exporter->changed as $key => $folder) { if (isset($folder->serverid) && in_array($folder->serverid, $seenfolders)) { $this->_encoder->startTag(self::UPDATE); } else { $seenfolders[] = $folder->serverid; $this->_encoder->startTag(self::ADD); } $folder->encodeStream($this->_encoder); $this->_encoder->endTag(); $collections->updateFolderInHierarchy($folder); } } if (count($exporter->deleted) > 0) { foreach ($exporter->deleted as $folder_uid) { $this->_encoder->startTag(self::REMOVE); $this->_encoder->startTag(Horde_ActiveSync::FOLDERHIERARCHY_SERVERENTRYID); $this->_encoder->content($folder_uid); $this->_encoder->endTag(); $this->_encoder->endTag(); $collections->deleteFolderFromHierarchy($folder_uid); } } $this->_encoder->endTag(); $this->_encoder->endTag(); // Save state, clean if ($exporter->count) { $this->_state->setNewSyncKey($newsynckey); $this->_state->save(); } $this->_cleanUpAfterPairing(); $collections->save(); return true; } /** * Helper function for sending error responses * * @param Exception $e The exception. */ private function _handleError($e = null) { if (!is_null($e)) { $this->_logger->err($e->getMessage()); } $this->_encoder->startWBXML(); $this->_encoder->startTag(Horde_ActiveSync::FOLDERHIERARCHY_FOLDERSYNC); $this->_encoder->startTag(Horde_ActiveSync::FOLDERHIERARCHY_STATUS); $this->_encoder->content($this->_statusCode); $this->_encoder->endTag(); $this->_encoder->endTag(); } }Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Request/GetAttachment.php0000664000076500000240000000535712654565405022505 0ustar * @package ActiveSync */ /** * Handle GetAttachment requests. * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2011-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync * @internal */ class Horde_ActiveSync_Request_GetAttachment extends Horde_ActiveSync_Request_Base { /** * Handle request * * @return string The content-type of the attachment */ protected function _handle() { $this->_logger->info(sprintf( '[%s] Handling GETATTACHMENT command.', $this->_procid) ); $get = $this->_activeSync->getGetVars(); if (empty($get['AttachmentName'])) { return false; } $attname = $get['AttachmentName']; $this->_logger->info(sprintf( '[%s] Fetching attachment: %s', $this->_device->id, $attname)); $att = $this->_driver->getAttachment($attname); // Send the content-type header in case the attachment is large enough // to trigger the output buffer to be flushed. $this->_activeSync->contentTypeHeader($att['content-type']); // Output the attachment data to the stream. if (is_resource($att['data'])) { $this->_logger->info('Copying attachment data directly from stream to stream.'); rewind($att['data']); } else { $this->_logger->info('Writing attachment data from string to stream.'); } $this->_encoder->getStream()->add($att['data']); // Indicate the content-type // @TODO This is for BC only. Can remove for H6. return $att['content-type']; } }Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Request/GetHierarchy.php0000664000076500000240000000375412654565405022332 0ustar * @package ActiveSync */ /** * Handle GetHierarchy requests. * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2009-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync * @internal */ class Horde_ActiveSync_Request_GetHierarchy extends Horde_ActiveSync_Request_Base { /** * Handle request * * @return boolean */ public function handle() { $folders = $this->_driver->getFolders(); if (!$folders) { return false; } $this->_encoder->StartWBXML(); $this->_encoder->startTag(self::FOLDERHIERARCHY_FOLDERS); foreach ($folders as $folder) { $this->_encoder->startTag(self::FOLDERHIERARCHY_FOLDER); $folder->encodeStream($this->_encoder); $this->_encoder->endTag(); } $this->_encoder->endTag(); return true; } }Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Request/GetItemEstimate.php0000664000076500000240000002567612654565405023015 0ustar * @package ActiveSync */ /** * Handle GetItemEstimate requests. * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2009-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync * @internal */ class Horde_ActiveSync_Request_GetItemEstimate extends Horde_ActiveSync_Request_Base { /** Status Codes **/ const STATUS_SUCCESS = 1; const STATUS_INVALIDCOL = 2; const STATUS_NOTPRIMED = 3; const STATUS_KEYMISM = 4; /* Request tag constants */ const GETITEMESTIMATE = 'GetItemEstimate:GetItemEstimate'; const VERSION = 'GetItemEstimate:Version'; const FOLDERS = 'GetItemEstimate:Folders'; const FOLDER = 'GetItemEstimate:Folder'; const FOLDERTYPE = 'GetItemEstimate:FolderType'; const FOLDERID = 'GetItemEstimate:FolderId'; const DATETIME = 'GetItemEstimate:DateTime'; const ESTIMATE = 'GetItemEstimate:Estimate'; const RESPONSE = 'GetItemEstimate:Response'; const STATUS = 'GetItemEstimate:Status'; /** * Handle the request * * @return boolean * @throws Horde_ActiveSync_Exception */ protected function _handle() { $this->_logger->info(sprintf( '[%s] Handling GETITEMESTIMATE command.', $this->_device->id) ); if (!$this->checkPolicyKey($this->_activeSync->getPolicyKey(), self::GETITEMESTIMATE)) { return true; } $status = array(); $gStatus = self::STATUS_SUCCESS; $collections = $this->_activeSync->getCollectionsObject(); if (!$this->_decoder->getElementStartTag(self::GETITEMESTIMATE) || !$this->_decoder->getElementStartTag(self::FOLDERS)) { return false; } while ($this->_decoder->getElementStartTag(self::FOLDER)) { $options = array(); $cStatus = self::STATUS_SUCCESS; while (($type = ($this->_decoder->getElementStartTag(self::FOLDERTYPE) ? self::FOLDERTYPE : ($this->_decoder->getElementStartTag(self::FOLDERID) ? self::FOLDERID : ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_FILTERTYPE) ? Horde_ActiveSync::SYNC_FILTERTYPE : ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_SYNCKEY) ? Horde_ActiveSync::SYNC_SYNCKEY : ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_CONVERSATIONMODE) ? Horde_ActiveSync::SYNC_CONVERSATIONMODE : ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_OPTIONS) ? Horde_ActiveSync::SYNC_OPTIONS : -1))))))) != -1) { switch ($type) { case self::FOLDERTYPE: $class = $this->_decoder->getElementContent(); if (!$this->_decoder->getElementEndTag()) { return false; } break; case self::FOLDERID: $collectionid = $this->_decoder->getElementContent(); if (!$this->_decoder->getElementEndTag()) { return false; } break; case Horde_ActiveSync::SYNC_CONVERSATIONMODE: $conversationmode = $this->_decoder->getElementContent(); if ($conversationmode !== false && !$this->_decoder->getElementEndTag()) { throw new Horde_ActiveSync_Exception('Protocol Error'); } elseif ($conversationmode === false) { $conversationmode = true; } break; case Horde_ActiveSync::SYNC_FILTERTYPE: $filtertype = $this->_decoder->getElementContent(); if (!$this->_decoder->getElementEndTag()) { return false; } break; case Horde_ActiveSync::SYNC_SYNCKEY: $synckey = $this->_decoder->getElementContent(); if (empty($synckey)) { $cStatus = self::STATUS_NOTPRIMED; } if (!$this->_decoder->getElementEndTag()) { return false; } break; case Horde_ActiveSync::SYNC_OPTIONS: // EAS > 12.1 only. while (1) { $firstOption = true; if ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_FOLDERTYPE)) { $class = $this->_decoder->getElementContent(); if (!$this->_decoder->getElementEndTag()) { throw new Horde_ActiveSync_Exception('Protocol Error'); } } elseif ($firstOption) { // Default options? } $firstOption = false; if ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_FILTERTYPE)) { // Set filtertype? self::$decoder->getElementContent()); $filtertype = $this->_decoder->getElementContent(); if (!$this->_decoder->getElementEndTag()) { throw new Horde_ActiveSync_Exception('Protocol Error'); } } if ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_WINDOWSIZE)) { // Setwindowsize ($maxitems = self::$decoder->getElementContent()); if (!$this->_decoder->getElementEndTag()) { throw new Horde_ActiveSync_Exception('Protocol Error'); } } // Only supported for 'RI' searches - which we don't support // need to parse it though to avoid wbxml errors. if ($this->_decoder->getELementStartTag(Horde_ActiveSync::SYNC_MAXITEMS)) { if ($collectionid != 'RI') { $gStatus = self::STATUS_INVALIDCOL; } if (!$this->_decoder->getElementEndTag()) { throw new Horde_ActiveSync_Exception('Protocol Error'); } } $elm = $this->_decoder->peek(); if ($elm[Horde_ActiveSync_Wbxml::EN_TYPE] == Horde_ActiveSync_Wbxml::EN_TYPE_ENDTAG) { $this->_decoder->getElementEndTag(); break; } } } } // End the FOLDER element if (!$this->_decoder->getElementEndTag()) { return false; } // Build the collection array $collection = array(); $collection['synckey'] = $synckey; $collection['filtertype'] = !empty($filtertype) ? $filtertype : false; $collection['id'] = $collectionid; $collection['conversationmode'] = isset($conversationmode) ? $conversationmode : false; $status[$collection['id']] = $cStatus; if (!empty($class)) { $collection['class'] = $class; } else { $needCache = true; } $collections->addCollection($collection); } if (!empty($needCache)) { $collections->validateFromCache(); } // End Folders $this->_decoder->getElementEndTag(); // End GETITEMESTIMATE $this->_decoder->getElementEndTag(); $this->_encoder->startWBXML(); $this->_encoder->startTag(self::GETITEMESTIMATE); foreach ($collections as $collection) { $status = self::STATUS_SUCCESS; try { $collections->initCollectionState($collection); $count = $collections->getCollectionChangeCount(); } catch (Horde_ActiveSync_Exception_StaleState $e) { $this->_logger->warn(sprintf('[%s] Stale state detected: %s', getmypid(), $e->getMessage()) ); $status = self::STATUS_KEYMISM; } catch (Horde_ActiveSync_Exception_StateGone $e) { $this->_logger->warn('State Gone. Terminating GETITEMESTIMATE'); $status = self::STATUS_NOTPRIMED; } catch (Horde_ActiveSync_Exception $e) { $this->_logger->err('Unknown error in GETITEMESTIMATE'); $status = self::STATUS_NOTPRIMED; } $this->_encoder->startTag(self::RESPONSE); $this->_encoder->startTag(self::STATUS); $this->_encoder->content($status); $this->_encoder->endTag(); $this->_encoder->startTag(self::FOLDER); if ($this->_device->version <= Horde_ActiveSync::VERSION_TWELVE) { $this->_encoder->startTag(self::FOLDERTYPE); $this->_encoder->content($collection['class']); $this->_encoder->endTag(); } $this->_encoder->startTag(self::FOLDERID); $this->_encoder->content($collection['id']); $this->_encoder->endTag(); if ($status == self::STATUS_SUCCESS) { $this->_encoder->startTag(self::ESTIMATE); $this->_encoder->content($count); $this->_encoder->endTag(); } $this->_encoder->endTag(); $this->_encoder->endTag(); } $this->_encoder->endTag(); return true; } } Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Request/ItemOperations.php0000664000076500000240000004672612654565405022724 0ustar * @package ActiveSync */ /** * ActiveSync Handler for ItemOperations requests * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2012-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync * @internal */ class Horde_ActiveSync_Request_ItemOperations extends Horde_ActiveSync_Request_SyncBase { const ITEMOPERATIONS_ITEMOPERATIONS = 'ItemOperations:ItemOperations'; const ITEMOPERATIONS_FETCH = 'ItemOperations:Fetch'; const ITEMOPERATIONS_STORE = 'ItemOperations:Store'; const ITEMOPERATIONS_OPTIONS = 'ItemOperations:Options'; const ITEMOPERATIONS_RANGE = 'ItemOperations:Range'; const ITEMOPERATIONS_TOTAL = 'ItemOperations:Total'; const ITEMOPERATIONS_PROPERTIES = 'ItemOperations:Properties'; const ITEMOPERATIONS_DATA = 'ItemOperations:Data'; const ITEMOPERATIONS_STATUS = 'ItemOperations:Status'; const ITEMOPERATIONS_RESPONSE = 'ItemOperations:Response'; const ITEMOPERATIONS_VERSION = 'ItemOperations:Version'; const ITEMOPERATIONS_SCHEMA = 'ItemOperations:Schema'; const ITEMOPERATIONS_PART = 'ItemOperations:Part'; const ITEMOPERATIONS_EMPTYFOLDERCONTENT = 'ItemOperations:EmptyFolderContent'; const ITEMOPERATIONS_DELETESUBFOLDERS = 'ItemOperations:DeleteSubFolders'; const ITEMOPERATIONS_USERNAME = 'ItemOperations:UserName'; const ITEMOPERATIONS_PASSWORD = 'ItemOperations:Password'; // 14.0 const ITEMOPERATIONS_MOVE = 'ItemOperations:Move'; const ITEMOPERATIONS_DSTFLDID = 'ItemOperations:DstFldId'; const ITEMOPERATIONS_CONVERSATIONID = 'ItemOperations:ConversationId'; const ITEMOPERATIONS_MOVEALWAYS = 'ItemOperations:MoveAlways'; /* Status */ const STATUS_SUCCESS = 1; const STATUS_PROTERR = 2; const STATUS_SERVERERR = 3; // 4 - 13 are Document library related. const STATUS_ATTINVALID = 15; const STATUS_POLICYERR = 16; const STATUS_PARTSUCCESS = 17; const STATUS_CREDENTIALS = 18; const STATUS_PROTERR_OPTIONS = 155; const STATUS_NOT_SUPPORTED = 156; /** * Handle the request. * * @return string The Content-Type of the attachment data. */ protected function _handle() { $this->_logger->info(sprintf( '[%s] Handling ITEMOPERATIONS command.', $this->_device->id) ); $this->_statusCode = self::STATUS_SUCCESS; if (!$this->_decoder->getElementStartTag(self::ITEMOPERATIONS_ITEMOPERATIONS)) { throw new Horde_ActiveSync_Exception('Protocol Error'); } // The current itemoperation task $thisio = array(); $mimesupport = 0; while (($reqtype = ($this->_decoder->getElementStartTag(self::ITEMOPERATIONS_FETCH) ? self::ITEMOPERATIONS_FETCH : ($this->_decoder->getElementStartTag(self::ITEMOPERATIONS_EMPTYFOLDERCONTENT) ? self::ITEMOPERATIONS_EMPTYFOLDERCONTENT : -1))) != -1) { if ($reqtype == self::ITEMOPERATIONS_FETCH) { $thisio['type'] = 'fetch'; while (($reqtag = ($this->_decoder->getElementStartTag(self::ITEMOPERATIONS_STORE) ? self::ITEMOPERATIONS_STORE : ($this->_decoder->getElementStartTag(self::ITEMOPERATIONS_OPTIONS) ? self::ITEMOPERATIONS_OPTIONS : ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_SERVERENTRYID) ? Horde_ActiveSync::SYNC_SERVERENTRYID : ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_FOLDERID) ? Horde_ActiveSync::SYNC_FOLDERID : ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_DOCUMENTLIBRARY_LINKID) ? Horde_ActiveSync::SYNC_DOCUMENTLIBRARY_LINKID : ($this->_decoder->getElementStartTag(Horde_ActiveSync::AIRSYNCBASE_FILEREFERENCE) ? Horde_ActiveSync::AIRSYNCBASE_FILEREFERENCE : ($this->_decoder->getElementStartTag(Horde_ActiveSync_Request_Search::SEARCH_LONGID) ? Horde_ActiveSync_Request_Search::SEARCH_LONGID : -1)))))))) != -1) { if ($reqtag == self::ITEMOPERATIONS_OPTIONS) { while (($thisoption = ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_MIMESUPPORT) ? Horde_ActiveSync::SYNC_MIMESUPPORT : ($this->_decoder->getElementStartTag(Horde_ActiveSync::AIRSYNCBASE_BODYPREFERENCE) ? Horde_ActiveSync::AIRSYNCBASE_BODYPREFERENCE : ($this->_decoder->getElementStartTag(Horde_ActiveSync::AIRSYNCBASE_BODYPARTPREFERENCE) ? Horde_ActiveSync::AIRSYNCBASE_BODYPARTPREFERENCE : ($this->_decoder->getElementStartTag(self::ITEMOPERATIONS_SCHEMA) ? self::ITEMOPERATIONS_SCHEMA : ($this->_decoder->getElementStartTag(self::ITEMOPERATIONS_RANGE) ? self::ITEMOPERATIONS_RANGE : ($this->_decoder->getElementStartTag(self::ITEMOPERATIONS_USERNAME) ? self::ITEMOPERATIONS_USERNAME : ($this->_decoder->getElementStartTag(self::ITEMOPERATIONS_PASSWORD) ? self::ITEMOPERATIONS_PASSWORD : ($this->_decoder->getElementStartTag(Horde_ActiveSync::RM_SUPPORT) ? Horde_ActiveSync::RM_SUPPORT : -1))))))))) != -1) { switch ($thisoption) { case Horde_ActiveSync::SYNC_MIMESUPPORT: $mimesupport = $this->_decoder->getElementContent(); $this->_decoder->getElementEndTag(); break; case Horde_ActiveSync::AIRSYNCBASE_BODYPREFERENCE: $this->_bodyPrefs($thisio); break; case Horde_ActiveSync::AIRSYNCBASE_BODYPARTPREFERENCE: $this->_bodyPartPrefs($thisio); break; case Horde_ActiveSync::RM_SUPPORT: $this->_rightsManagement($thisio); break; case self::ITEMOPERATIONS_PASSWORD: $thisio['password'] = $this->_decoder->getElementContent(); break; case self::ITEMOPERATIONS_USERNAME: $thisio['username'] = $this->_decoder->getElementContent(); break; case self::ITEMOPERATIONS_RANGE: $thisio['range'] = $this->_decoder->getElementContent(); break; case self::ITEMOPERATIONS_SCHEMA: while (1) { $el = $this->_decoder->getElement(); $e = $this->_decoder->peek(); if ($e[Horde_ActiveSync_Wbxml::EN_TYPE] == Horde_ActiveSync_Wbxml::EN_TYPE_ENDTAG) { $this->_decoder->getElementEndTag(); break; } } } } } elseif ($reqtag == self::ITEMOPERATIONS_STORE) { $thisio['store'] = $this->_decoder->getElementContent(); } elseif ($reqtag == Horde_ActiveSync_Request_Search::SEARCH_LONGID) { $thisio['searchlongid'] = $this->_decoder->getElementContent(); } elseif ($reqtag == Horde_ActiveSync::AIRSYNCBASE_FILEREFERENCE) { $thisio['airsyncbasefilereference'] = $this->_decoder->getElementContent(); } elseif ($reqtag == Horde_ActiveSync::SYNC_SERVERENTRYID) { $thisio['serverentryid'] = $this->_decoder->getElementContent(); } elseif ($reqtag == Horde_ActiveSync::SYNC_FOLDERID) { $thisio['folderid'] = $this->_decoder->getElementContent(); } elseif ($reqtag == Horde_ActiveSync::SYNC_DOCUMENTLIBRARY_LINKID) { $thisio['documentlibrarylinkid'] = $this->_decoder->getElementContent(); } $e = $this->_decoder->peek(); if ($e[Horde_ActiveSync_Wbxml::EN_TYPE] == Horde_ActiveSync_Wbxml::EN_TYPE_ENDTAG) { $this->_decoder->getElementEndTag(); } } $itemoperations[] = $thisio; $this->_decoder->getElementEndTag(); // end SYNC_ITEMOPERATIONS_FETCH } elseif ($reqtype == self::ITEMOPERATIONS_EMPTYFOLDERCONTENT) { $thisio['type'] = 'empty'; while (($tag = ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_FOLDERID) ? Horde_ActiveSync::SYNC_FOLDERID : ($this->_decoder->getElementStartTag(self::ITEMOPERATIONS_OPTIONS) ? self::ITEMOPERATIONS_OPTIONS : -1))) != -1) { if ($tag == Horde_ActiveSync::SYNC_FOLDERID) { $thisio['folderid'] = $this->_decoder->getElementContent(); } elseif ($tag == self::ITEMOPERATIONS_OPTIONS) { $this->_decoder->getElementStartTag(self::ITEMOPERATIONS_DELETESUBFOLDERS); $thisio['delete_subfolders'] = $this->_decoder->getElementContent(); $this->_decoder->getElementEndTag(); } $this->_decoder->getElementEndTag(); } $this->_decoder->getElementEndTag(); // SYNC_ITEMSOPERATIONS_EMPTYFOLDERCONTENT $itemoperations[] = $thisio; } } $this->_decoder->getElementEndTag(); // end SYNC_ITEMOPERATIONS_ITEMOPERATIONS $this->_encoder->startWBXML($this->_activeSync->multipart); $this->_encoder->startTag(self::ITEMOPERATIONS_ITEMOPERATIONS); $this->_encoder->startTag(self::ITEMOPERATIONS_STATUS); $this->_encoder->content(self::STATUS_SUCCESS); $this->_encoder->endTag(); $this->_encoder->startTag(self::ITEMOPERATIONS_RESPONSE); $collections = $this->_activeSync->getCollectionsObject(); foreach($itemoperations as $value) { switch($value['type']) { case 'fetch' : switch(Horde_String::lower($value['store'])) { case 'mailbox' : $this->_encoder->startTag(self::ITEMOPERATIONS_FETCH); if (isset($value['airsyncbasefilereference'])) { // filereference is already in the backend serverid format // since it is taken from the AIRSYNCBASE_FILEREFERENCE try { $msg = $this->_driver->itemOperationsGetAttachmentData($value['airsyncbasefilereference']); } catch (Horde_ActiveSync_Exception $e) { $this->_statusCode = self::STATUS_ATTINVALID; } if (!$this->_encoder->multipart) { $msg->total = $this->_getDataSize($msg->data); $msg->range = '0-' . ($msg->total - 1); } $this->_outputStatus(); $this->_encoder->startTag(Horde_ActiveSync::AIRSYNCBASE_FILEREFERENCE); $this->_encoder->content($value['airsyncbasefilereference']); $this->_encoder->endTag(); } elseif (isset($value['searchlongid'])) { $this->_outputStatus(); $this->_encoder->startTag(Horde_ActiveSync_Request_Search::SEARCH_LONGID); $this->_encoder->content($value['searchlongid']); $this->_encoder->endTag(); $this->_encoder->startTag(Horde_ActiveSync::SYNC_FOLDERTYPE); $this->_encoder->content('Email'); $this->_encoder->endTag(); $msg = $this->_driver->itemOperationsFetchMailbox($value['searchlongid'], $value['bodyprefs'], $mimesupport); } else { $this->_outputStatus(); if (isset($value['folderid']) && isset($value['serverentryid'])) { $this->_encoder->startTag(Horde_ActiveSync::SYNC_FOLDERID); $this->_encoder->content($value['folderid']); $this->_encoder->endTag(); $this->_encoder->startTag(Horde_ActiveSync::SYNC_SERVERENTRYID); $this->_encoder->content($value['serverentryid']); $this->_encoder->endTag(); $this->_encoder->startTag(Horde_ActiveSync::SYNC_FOLDERTYPE); $this->_encoder->content('Email'); $this->_encoder->endTag(); $mailbox = $collections->getBackendIdForFolderUid($value['folderid']); $msg = $this->_driver->fetch( $mailbox, $value['serverentryid'], array( 'bodyprefs' => $value['bodyprefs'], 'mimesupport' => $mimesupport) ); } } if ($this->_statusCode == self::STATUS_SUCCESS) { $this->_encoder->startTag(self::ITEMOPERATIONS_PROPERTIES); $msg->encodeStream($this->_encoder); $this->_encoder->endTag(); } $this->_encoder->endTag(); break; case 'documentlibrary': $this->_encoder->startTag(self::ITEMOPERATIONS_FETCH); try { $u = $this->_driver->itemOperationsGetDocumentLibraryLink($value['documentlibrarylinkid'], array()); $doc = Horde_ActiveSync::messageFactory('Document'); $doc->range = '0-' . ($u['content-length'] - 1); $doc->total = $u['content-length']; $doc->data = $u['data']->stream; $doc->version = $u['modified']; } catch (Horde_ActiveSync_Exception $e) { $this->_status = self::STATUS_NOT_SUPPORTED; } $this->_outputStatus(); $this->_encoder->startTag(Horde_ActiveSync::SYNC_DOCUMENTLIBRARY_LINKID); $this->_encoder->content($u['linkid']); $this->_encoder->endTag(); $this->_encoder->startTag(self::ITEMOPERATIONS_PROPERTIES); $doc->encodeStream($this->_encoder); $this->_encoder->endTag(); $this->_encoder->endTag(); break; default : $this->_logger->warn(sprintf( '[%s] %s not supported by HANDLEITEMOPERATIONS.', $this->_device->id, $value['type']) ); break; } break; case 'empty': // @todo remove check for H6. if (method_exists($this->_driver, 'itemOperationsEmptyFolder')) { $map = array_flip($this->_state->getFolderUidToBackendIdMap()); $value['folderid'] = $map[$value['folderid']]; $this->_logger->info(sprintf( '[%s] Handling EMPTYFOLDERCONTENT for collection %s.', $this->_device->id, $value['folderid'])); try { $this->_driver->itemOperationsEmptyFolder($value); } catch (Horde_ActiveSync_Exception $e) { $this->_status = self::STATUS_NOT_SUPPORTED; } } else { $this->_logger->err(sprintf( '[%s] EMPTYFOLDERCONTENT not supported by driver.', $this->_device->id, $value['type']) ); } break; default : $this->_logger->err(sprintf( '[%s] %s not supported by HANDLEITEMOPERATIONS.', $this->_device->id, $value['type']) ); break; } } $this->_encoder->endTag(); //end SYNC_ITEMOPERATIONS_RESPONSE $this->_encoder->endTag(); //end SYNC_ITEMOPERATIONS_ITEMOPERATIONS // @TODO This is for BC, remove in H6. return $this->_encoder->multipart ? 'application/vnd.ms-sync.multipart' : 'application/vnd.ms-sync.wbxml'; } /** * Helper to send the status output. */ protected function _outputStatus() { $this->_encoder->startTag(self::ITEMOPERATIONS_STATUS); $this->_encoder->content($this->_statusCode); $this->_encoder->endTag(); } /** * Return the size of the specified data. * * @param string|stream The data to obtain the size of. * * @return integer The size of the data. */ protected function _getDataSize($data) { if (is_resource($data)) { rewind($data); fseek($data, 0, SEEK_END); return ftell($data); } else { return strlen($data); } } protected function _handleError(array $data, $error) { $this->_decoder->getElementEndTag(); // end SYNC_ITEMOPERATIONS_ITEMOPERATIONS $this->_encoder->startWBXML($this->_activeSync->multipart); $this->_encoder->startTag(self::ITEMOPERATIONS_ITEMOPERATIONS); $this->_outputStatus(); $this->_encoder->endTag(); } }Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Request/MeetingResponse.php0000664000076500000240000001635612654565405023065 0ustar * @package ActiveSync */ /** * Horde_ActiveSync_Request_MeetingResponse:: * * Portions of this class were ported from the Z-Push project: * File : wbxml.php * Project : Z-Push * Descr : WBXML mapping file * * Created : 01.10.2007 * * © Zarafa Deutschland GmbH, www.zarafaserver.de * This file is distributed under GPL-2.0. * Consult COPYING file for details * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2009-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync * @internal */ class Horde_ActiveSync_Request_MeetingResponse extends Horde_ActiveSync_Request_Base { const MEETINGRESPONSE_CALENDARID = 'MeetingResponse:CalendarId'; const MEETINGRESPONSE_FOLDERID = 'MeetingResponse:FolderId'; const MEETINGRESPONSE_MEETINGRESPONSE = 'MeetingResponse:MeetingResponse'; const MEETINGRESPONSE_REQUESTID = 'MeetingResponse:RequestId'; const MEETINGRESPONSE_REQUEST = 'MeetingResponse:Request'; const MEETINGRESPONSE_RESULT = 'MeetingResponse:Result'; const MEETINGRESPONSE_STATUS = 'MeetingResponse:Status'; const MEETINGRESPONSE_USERRESPONSE = 'MeetingResponse:UserResponse'; const MEETINGRESPONSE_VERSION = 'MeetingResponse:Version'; // 14.1 const MEETINGRESPONSE_INSTANCEID = 'MeetingResponse:InstanceId'; // 16.0 @todo const MEETINGRESPONSE_SENDRESPONSE = 'MeetingResponse:SendResposne'; // Response constants const RESPONSE_ACCEPTED = 1; const RESPONSE_TENTATIVE = 2; const RESPONSE_DECLINED = 3; // Status constants const STATUS_SUCCESS = 1; const STATUS_INVALID_REQUEST = 2; const STATUS_STATE_ERROR = 3; const STATUS_SERVER_ERROR = 4; /** * Handle request * * @return boolean */ protected function _handle() { $requests = array(); if (!$this->_decoder->getElementStartTag(self::MEETINGRESPONSE_MEETINGRESPONSE)) { throw new Horde_ActiveSync_Exception('Protocol Error'); } while ($this->_decoder->getElementStartTag(self::MEETINGRESPONSE_REQUEST)) { $req = array(); while (($tag = ($this->_decoder->getElementStartTag(self::MEETINGRESPONSE_USERRESPONSE) ? self::MEETINGRESPONSE_USERRESPONSE : ($this->_decoder->getElementStartTag(self::MEETINGRESPONSE_FOLDERID) ? self::MEETINGRESPONSE_FOLDERID : ($this->_decoder->getElementStartTag(self::MEETINGRESPONSE_REQUESTID) ? self::MEETINGRESPONSE_REQUESTID : -1)))) != -1) { switch ($tag) { case self::MEETINGRESPONSE_USERRESPONSE: $req['response'] = $this->_decoder->getElementContent(); if (!$this->_decoder->getElementEndTag()) { throw new Horde_ActiveSync_Exception('Protocol Error'); } break; case self::MEETINGRESPONSE_FOLDERID: $req['folderid'] = $this->_activeSync->getCollectionsObject() ->getBackendIdForFolderUid($this->_decoder->getElementContent()); if (!$this->_decoder->getElementEndTag()) { throw new Horde_ActiveSync_Exception('Protocol Error'); } break; case self::MEETINGRESPONSE_REQUESTID: $req['requestid'] = $this->_decoder->getElementContent(); if (!$this->_decoder->getElementEndTag()) { throw new Horde_ActiveSync_Exception('Protocol Error'); } break; } } $requests[] = $req; // if (!$this->_decoder->getElementEndTag()) { throw new Horde_ActiveSync_Exception('Protocol Error'); } } // if (!$this->_decoder->getElementEndTag()) { throw new Horde_ActiveSync_Exception('Protocol Error'); } // Start output, simply the error code, plus the ID of the calendar item // that was generated by the accept of the meeting response $this->_encoder->StartWBXML(); $this->_encoder->startTag(self::MEETINGRESPONSE_MEETINGRESPONSE); foreach ($requests as $req) { try { $uid = $this->_driver->meetingResponse($req); $status = self::STATUS_SUCCESS; } catch (Horde_Exception_NotFound $e) { $status = self::STATUS_SERVER_ERROR; } catch (Horde_ActiveSync_Exception $e) { // Outlook seems to sometimes send the response from the // calendar folder instead of the mailbox regardless of where // the message is replied to from this will obviously fail, // so we should try one last time to get the folder from the // INBOX. If it was moved to some other mail folder, we have to // just give up. $this->_logger->info(sprintf('[%s] Trying to find meeting request in INBOX.', $this->_procid)); $req['folderid'] = 'INBOX'; try { $uid = $this->_driver->meetingResponse($req); $status = self::STATUS_SUCCESS; $this->_logger->info(sprintf('[%s] Successfully found meeting response in INBOX.', $this->_procid)); } catch (Horde_ActiveSync_Exception $e) { $this->_logger->err(sprintf('[%s] Meeting request unable to be located.', $this->_procid)); $status = self::STATUS_INVALID_REQUEST; } } $this->_encoder->startTag(self::MEETINGRESPONSE_RESULT); $this->_encoder->startTag(self::MEETINGRESPONSE_REQUESTID); $this->_encoder->content($req['requestid']); $this->_encoder->endTag(); $this->_encoder->startTag(self::MEETINGRESPONSE_STATUS); $this->_encoder->content($status); $this->_encoder->endTag(); if ($status == self::STATUS_SUCCESS) { $this->_encoder->startTag(self::MEETINGRESPONSE_CALENDARID); $this->_encoder->content($uid); $this->_encoder->endTag(); } $this->_encoder->endTag(); } $this->_encoder->endTag(); return true; } }Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Request/MoveItems.php0000664000076500000240000001413612654565405021660 0ustar * @package ActiveSync */ /** * Handle MoveItems requests. * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2012-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync * @internal */ class Horde_ActiveSync_Request_MoveItems extends Horde_ActiveSync_Request_Base { /* Wbxml constants */ const MOVES = 'Move:Moves'; const MOVE = 'Move:Move'; const SRCMSGID = 'Move:SrcMsgId'; const SRCFLDID = 'Move:SrcFldId'; const DSTFLDID = 'Move:DstFldId'; const RESPONSE = 'Move:Response'; const STATUS = 'Move:Status'; const DSTMSGID = 'Move:DstMsgId'; /* keys */ const SRCMSGKEY = 'srcmsgid'; const SRCFLDKEY = 'srcfldid'; const DSTFLDKEY = 'dstfldid'; /* Status */ const STATUS_INVALID_SRC = 1; const STATUS_INVALID_DST = 2; const STATUS_SUCCESS = 3; const STATUS_SAME_FOLDERS = 4; const STATUS_SERVER_ERR = 5; /** * Handle request * * @return boolean */ protected function _handle() { $this->_logger->info(sprintf( '[%s] Handling MoveItems command.', $this->_procid) ); if (!$this->_decoder->getElementStartTag(self::MOVES)) { throw new Horde_ActiveSync_Exception('Protocol Error'); } $moves = array(); while ($this->_decoder->getElementStartTag(self::MOVE)) { $move = array(); if ($this->_decoder->getElementStartTag(self::SRCMSGID)) { $move[self::SRCMSGKEY] = $this->_decoder->getElementContent(); if(!$this->_decoder->getElementEndTag()) break; } if ($this->_decoder->getElementStartTag(self::SRCFLDID)) { $move[self::SRCFLDKEY] = $this->_decoder->getElementContent(); if (!$this->_decoder->getElementEndTag()) { break; } } if ($this->_decoder->getElementStartTag(self::DSTFLDID)) { $move[self::DSTFLDKEY] = $this->_decoder->getElementContent(); if (!$this->_decoder->getElementEndTag()) { break; } } $moves[] = $move; if (!$this->_decoder->getElementEndTag()) { throw new Horde_ActiveSync_Exception('Protocol Error'); } } if (!$this->_decoder->getElementEndTag()) { throw new Horde_ActiveSync_Exception('Protocol Error'); } // Start response $this->_encoder->StartWBXML(); $this->_encoder->startTag(self::MOVES); // Can't do these all at once since the device may send any combination // of src and dest mailboxes in the same request, though oddly enough // the server response only needs to include the message uids, not // the mailbox identifier. foreach ($moves as $move) { $status = self::STATUS_SUCCESS; $this->_encoder->startTag(self::RESPONSE); $this->_encoder->startTag(self::SRCMSGID); $this->_encoder->content($move[self::SRCMSGKEY]); $this->_encoder->endTag(); if ($move[self::SRCFLDKEY] == $move[self::DSTFLDKEY]) { $status = self::STATUS_SAME_FOLDERS; } else { $importer = $this->_activeSync->getImporter(); $importer->init($this->_state, $move[self::SRCFLDKEY]); try { $move_res = $importer->importMessageMove( array($move[self::SRCMSGKEY]), $move[self::DSTFLDKEY]); if (empty($move_res['results'][$move[self::SRCMSGKEY]])) { // Hm. Specs say to send INVALID_SRC if the msg is // already moved, or no longer present, but that seems // to lead to the client keeping the item and continuously // retrying. We can't fake the move since we need a // new uid..... $status = in_array($move[self::SRCMSGKEY], $move_res['missing']) ? self::STATUS_INVALID_SRC : self::STATUS_SERVER_ERR; } else { $new_msgid = $move_res['results'][$move[self::SRCMSGKEY]]; } } catch (Horde_ActiveSync_Exception $e) { $this->_logger->err($e->getMessage()); $status = self::STATUS_INVALID_SRC; } } $this->_encoder->startTag(self::STATUS); $this->_encoder->content($status); $this->_encoder->endTag(); if ($status == self::STATUS_SUCCESS) { $this->_encoder->startTag(self::DSTMSGID); $this->_encoder->content($new_msgid); $this->_encoder->endTag(); } $this->_encoder->endTag(); } $this->_encoder->endTag(); return true; } }Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Request/Ping.php0000664000076500000240000003170312654565405020644 0ustar * @package ActiveSync */ /** * Handle PING requests. * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2009-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync * @internal */ class Horde_ActiveSync_Request_Ping extends Horde_ActiveSync_Request_Base { /* Status Constants */ const STATUS_NOCHANGES = 1; const STATUS_NEEDSYNC = 2; const STATUS_MISSING = 3; const STATUS_PROTERROR = 4; const STATUS_HBOUTOFBOUNDS = 5; const STATUS_MAXFOLDERS = 6; const STATUS_FOLDERSYNCREQD = 7; const STATUS_SERVERERROR = 8; /* PING Wbxml entities */ const PING = 'Ping:Ping'; const STATUS = 'Ping:Status'; const HEARTBEATINTERVAL = 'Ping:HeartbeatInterval'; const FOLDERS = 'Ping:Folders'; const FOLDER = 'Ping:Folder'; const SERVERENTRYID = 'Ping:ServerEntryId'; const FOLDERTYPE = 'Ping:FolderType'; const MAXFOLDERS = 'Ping:MaxFolders'; const VERSION = 'Ping:Version'; /** * The device's PING configuration (obtained from state) * * @var array */ protected $_pingSettings; /** * Validate the configured/requested heartbeat * Will set self::_statusCode appropriately in case of an invalid interval. * * @param integer $lifetime The heartbeat to verify * * @return integer The valid heartbeat value to use. */ protected function _checkHeartbeat($lifetime) { if (!empty($this->_pingSettings['forcedheartbeat'])) { return $this->_pingSettings['forcedheartbeat']; } if ($lifetime !== 0 && $lifetime < $this->_pingSettings['heartbeatmin']) { $this->_statusCode = self::STATUS_HBOUTOFBOUNDS; $lifetime = $this->_pingSettings['heartbeatmin']; } elseif ($lifetime > $this->_pingSettings['heartbeatmax']) { $this->_statusCode = self::STATUS_HBOUTOFBOUNDS; $lifetime = $this->_pingSettings['heartbeatmax']; } return $lifetime; } /** * Handle a PING command from the client. PING is sent periodically by * the client to tell the server what folders we are interested in * monitoring for changes. If no changes are detected by the server during * the 'heartbeat' interval, the server sends back a status of * self::STATUS_NOCHANGES to indicate heartbeat expired and the client * should re-issue the PING command. If a change has been found, the client * is sent a self::STATUS_NEEDSYNC and should issue a SYNC command. * * @return boolean */ protected function _handle() { $now = time(); $forceCacheSave = false; $this->_logger->info(sprintf( '[%s] Handling PING command received at timestamp: %s.', $this->_procid, $now)); // Check global errors. if ($error = $this->_activeSync->checkGlobalError()) { $this->_statusCode = $error; $this->_handleGlobalError(); return true; } // Initialize the collections handler. try { $collections = $this->_activeSync->getCollectionsObject(); } catch (Horde_ActiveSync_Exception $e) { $this->_status = self::STATUS_SERVERERROR; $this->_handleGlobalError(); return true; } // Get the current ping settings. $this->_pingSettings = $this->_driver->getHeartbeatConfig(); $interval = $this->_pingSettings['waitinterval']; if (!$heartbeat = $collections->getHeartbeat()) { $heartbeat = !empty($this->_pingSettings['heartbeatdefault']) ? $this->_pingSettings['heartbeatdefault'] : 60; $this->_logger->info(sprintf( '[%s] Cached heartbeat is %s', $this->_procid, $heartbeat)); } $this->_statusCode = self::STATUS_NOCHANGES; // Either handle the empty request or decode a full request. if (!$this->_decoder->getElementStartTag(self::PING)) { $this->_logger->info(sprintf( '[%s] Empty PING request.', $this->_procid)); $isEmpty = true; $collections->loadCollectionsFromCache(); if ($collections->collectionCount() == 0 || !$collections->havePingableCollections()) { $this->_logger->warn(sprintf( '[%s] Empty PING request with no cached collections. Request full PING.', $this->_procid)); $this->_statusCode = self::STATUS_MISSING; $this->_handleGlobalError(); return true; } } else { $isEmpty = false; if ($this->_decoder->getElementStartTag(self::HEARTBEATINTERVAL)) { if (!$heartbeat = $this->_checkHeartbeat($this->_decoder->getElementContent())) { $heartbeat = $this->_pingSettings['heartbeatdefault']; } $collections->setHeartbeat(array('hbinterval' => $heartbeat)); $forceCacheSave = true; $this->_decoder->getElementEndTag(); } $this->_logger->info(sprintf( '[%s] Actual heartbeat value in use is %s.', $this->_procid, $heartbeat)); if ($this->_decoder->getElementStartTag(self::FOLDERS)) { while ($this->_decoder->getElementStartTag(self::FOLDER)) { $collection = array(); if ($this->_decoder->getElementStartTag(self::SERVERENTRYID)) { $collection['id'] = $this->_decoder->getElementContent(); $this->_decoder->getElementEndTag(); } if ($this->_decoder->getElementStartTag(self::FOLDERTYPE)) { $collection['class'] = $this->_decoder->getElementContent(); $this->_decoder->getElementEndTag(); } $this->_decoder->getElementEndTag(); try { // Explicitly asked for a collection, make sure we have // a key, but silently ignore the collection if we don't // Otherwise, this can set up a PING loop in broken // iOS clients that request collections in PING before // they issue an initial SYNC for them. $collections->addCollection($collection, true); } catch (Horde_ActiveSync_Exception_StateGone $e) { } } // Since PING sends all or none (no PARTIAL) we update the // pingable flags so we have it for an empty PING. $collections->validateFromCache(); $collections->updatePingableFlag(); if (!$this->_decoder->getElementEndTag()) { throw new Horde_ActiveSync_Exception('Protocol Error'); } } else { // No FOLDERS supplied, use the cache. $collections->loadCollectionsFromCache(); if ($collections->collectionCount() == 0) { $this->_logger->warn(sprintf( '[%s] Empty PING request with no cached collections. Request full PING.', $this->_procid)); $this->_statusCode = self::STATUS_MISSING; $this->_handleGlobalError(); return true; } } if (!$this->_decoder->getElementEndTag()) { throw new Horde_ActiveSync_Exception('Protocol Error'); } } // Start waiting for changes, but only if we don't have any errors if ($this->_statusCode == self::STATUS_NOCHANGES) { $changes = $collections->pollForChanges($heartbeat, $interval, array('pingable' => true)); if ($changes !== true && $changes !== false) { // If we received a status indicating we need to issue a full // PING, but we already did, treat it as a status_needsync. if (!$isEmpty && $changes == Horde_ActiveSync_Collections::COLLECTION_ERR_PING_NEED_FULL) { $changes = Horde_ActiveSync_Collections::COLLECTION_ERR_SYNC_REQUIRED; } switch ($changes) { case Horde_ActiveSync_Collections::COLLECTION_ERR_PING_NEED_FULL: $this->_statusCode = self::STATUS_MISSING; $this->_handleGlobalError(); return true; case Horde_ActiveSync_Collections::COLLECTION_ERR_STALE: $this->_logger->info(sprintf( '[%s] Changes in cache detected during PING, exiting here.', $this->_procid)); return true; case Horde_ActiveSync_Collections::COLLECTION_ERR_FOLDERSYNC_REQUIRED; $this->_statusCode = self::STATUS_FOLDERSYNCREQD; $this->_handleGlobalError(); return true; case Horde_ActiveSync_Collections::COLLECTION_ERR_SYNC_REQUIRED; $this->_statusCode = self::STATUS_NEEDSYNC; break; default: if ($this->_device->version < Horde_ActiveSync::VERSION_FOURTEEN) { $this->_logger->warn(sprintf( '[%s] Version is < 14.0, returning false since we have no PINGABLE collections.', $this->_procid)); return false; } else { $this->_logger->warn(sprintf( '[%s] Version is >= 14.0 returning status code 132 since we have no PINGABLE collections.', $this->_procid)); $this->_statusCode = Horde_ActiveSync_Status::STATEFILE_NOT_FOUND; $this->_handleGlobalError(); return true; } } } elseif ($changes) { $collections->save(true); $this->_statusCode = self::STATUS_NEEDSYNC; } elseif ($forceCacheSave) { $collections->save(true); } } // Send response $this->_encoder->StartWBXML(); $this->_encoder->startTag(self::PING); $this->_encoder->startTag(self::STATUS); $this->_encoder->content($this->_statusCode); $this->_encoder->endTag(); if ($this->_statusCode == self::STATUS_HBOUTOFBOUNDS) { $this->_encoder->startTag(self::HEARTBEATINTERVAL); $this->_encoder->content($heartbeat); $this->_encoder->endTag(); } elseif ($collections->collectionCount() && $this->_statusCode != self::STATUS_NOCHANGES) { $this->_encoder->startTag(self::FOLDERS); foreach ($collections as $id => $collection) { if ($collections->getChangesFlag($id)) { $this->_encoder->startTag(self::FOLDER); $this->_encoder->content($id); $this->_encoder->endTag(); } } $this->_encoder->endTag(); } $this->_encoder->endTag(); return true; } /** * Helper for sending error status results. * * @param boolean $limit Send the SYNC_LIMIT error if true. */ protected function _handleGlobalError() { $this->_encoder->StartWBXML(); $this->_encoder->startTag(self::PING); $this->_encoder->startTag(self::STATUS); $this->_encoder->content($this->_statusCode); $this->_encoder->endTag(); $this->_encoder->endTag(); } } Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Request/Provision.php0000664000076500000240000003744212654565405021745 0ustar * @package ActiveSync */ /** * Hanlde Provision requests. * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2009-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync * @internal */ class Horde_ActiveSync_Request_Provision extends Horde_ActiveSync_Request_Base { /* Status Constants */ const STATUS_SUCCESS = 1; const STATUS_PROTERROR = 2; // Global status const STATUS_NOTDEFINED = 2; // Policy status const STATUS_SERVERERROR = 3; // Global const STATUS_POLICYUNKNOWN = 3; // Policy const STATUS_DEVEXTMANAGED = 4; // Global const STATUS_POLICYCORRUPT = 4; // Policy const STATUS_POLKEYMISM = 5; /* Client -> Server Status */ const STATUS_CLIENT_SUCCESS = 1; const STATUS_CLIENT_PARTIAL = 2; // Only pin was enabled. const STATUS_CLIENT_FAILED = 3; // No policies applied at all. const STATUS_CLIENT_THIRDPARTY = 4; // Client provisioned by 3rd party? /** * Handle the Provision request. This is a 3-phase process. Phase 1 is * actually the enforcement, when the server rejects a request and forces * the client to perform this PROVISION request...so we are handling phase * 2 (download policies) and 3 (acknowledge policies) here. * * @return boolean * @throws Horde_ActiveSync_Exception */ protected function _handle() { // Be optimistic $status = self::STATUS_SUCCESS; $policyStatus = self::STATUS_SUCCESS; if ($error = $this->_activeSync->checkGlobalError()) { $this->_globalError($error); return true; } // Start by assuming we are in stage 2 $phase2 = true; if (!$this->_decoder->getElementStartTag(Horde_ActiveSync::PROVISION_PROVISION)) { return $this->_globalError(self::STATUS_PROTERROR); } // Handle remote wipe status response for Android devices. if ($this->_decoder->getElementStartTag(Horde_ActiveSync::PROVISION_REMOTEWIPE)) { if (!$this->_decoder->getElementStartTag(Horde_ActiveSync::PROVISION_STATUS)) { return $this->_globalError(self::STATUS_PROTERROR); } $status = $this->_decoder->getElementContent(); if (!$this->_decoder->getElementEndTag() || !$this->_decoder->getElementEndTag()) { return $this->_globalError(self::STATUS_PROTERROR); } if ($status == self::STATUS_CLIENT_SUCCESS) { $this->_state->setDeviceRWStatus($this->_devId, Horde_ActiveSync::RWSTATUS_WIPED); } $policytype = Horde_ActiveSync::POLICYTYPE_XML; } else { if ($deviceinfo = $this->_handleSettings()) { $deviceinfo['version'] = $this->_device->version; $this->_device->setDeviceProperties($deviceinfo); $this->_device->save(); } if (!$this->_decoder->getElementStartTag(Horde_ActiveSync::PROVISION_POLICIES) || !$this->_decoder->getElementStartTag(Horde_ActiveSync::PROVISION_POLICY)) { return $this->_globalError(self::STATUS_PROTERROR); } // iOS (at least 5.0.1) incorrectly sends a STATUS tag before the // REMOTEWIPE response. if (!$this->_decoder->getElementStartTag(Horde_ActiveSync::PROVISION_POLICYTYPE)) { if ($this->_decoder->getElementStartTag(Horde_ActiveSync::PROVISION_STATUS)) { $this->_decoder->getElementContent(); $this->_decoder->getElementEndTag(); // status } } else { $policytype = $this->_decoder->getElementContent(); if ($this->_device->version < Horde_ActiveSync::VERSION_TWELVE && $policytype != Horde_ActiveSync::POLICYTYPE_XML) { $this->_logger->err('EAS version < 12.0 but policy type is not POLICYTYPE_XML'); $policyStatus = self::STATUS_POLICYUNKNOWN; } if ($this->_device->version >= Horde_ActiveSync::VERSION_TWELVE && $policytype != Horde_ActiveSync::POLICYTYPE_WBXML) { $this->_logger->err('EAS version >= 12.0 but policy type is not POLICYTYPE_WBXML'); $policyStatus = self::STATUS_POLICYUNKNOWN; } if (!$this->_decoder->getElementEndTag()) {//policytype return $this->_globalError(self::STATUS_PROTERROR); } } // POLICYKEY is only sent by client in phase 3 if ($this->_decoder->getElementStartTag(Horde_ActiveSync::PROVISION_POLICYKEY)) { $policykey = $this->_decoder->getElementContent(); $this->_logger->info('[' . $this->_device->id .'] PHASE 3 policykey sent from client: ' . $policykey); if (!$this->_decoder->getElementEndTag() || !$this->_decoder->getElementStartTag(Horde_ActiveSync::PROVISION_STATUS)) { return $this->_globalError(self::STATUS_PROTERROR); } if ($this->_decoder->getElementContent() != self::STATUS_SUCCESS) { $this->_logger->err('Policy not accepted by device: ' . $this->_device->id); if ($this->_provisioning == Horde_ActiveSync::PROVISIONING_LOOSE) { // Loose provisioning, don't error out, just don't reqiure provision. $this->_sendNoProvisionNeededResponse($status); return true; } $policyStatus = self::STATUS_POLICYCORRUPT; } if (!$this->_decoder->getElementEndTag()) { return $this->_globalError(self::STATUS_PROTERROR); } $phase2 = false; } if (!$this->_decoder->getElementEndTag() || !$this->_decoder->getElementEndTag()) { return $this->_globalError(self::STATUS_PROTERROR); } // Handle remote wipe status for other devices if ($this->_decoder->getElementStartTag(Horde_ActiveSync::PROVISION_REMOTEWIPE)) { if (!$this->_decoder->getElementStartTag(Horde_ActiveSync::PROVISION_STATUS)) { return $this->_globalError(self::STATUS_PROTERROR); } $status = $this->_decoder->getElementContent(); if (!$this->_decoder->getElementEndTag() || !$this->_decoder->getElementEndTag()) { return $this->_globalError(self::STATUS_PROTERROR); } if ($status == self::STATUS_CLIENT_SUCCESS) { $this->_state->setDeviceRWStatus($this->_device->id, Horde_ActiveSync::RWSTATUS_WIPED); } } } if (!$this->_decoder->getElementEndTag()) { //provision return $this->_globalError(self::STATUS_PROTERROR); } // Check to be sure that we *need* to PROVISION if ($this->_provisioning === false) { $this->_sendNoProvisionNeededResponse($status); return true; } // Start handling request and sending output $this->_encoder->StartWBXML(); // End of Phase 3 - We create the "final" policy key, store it, then // send it to the client. if (!$phase2) { // Verify intermediate key $this->_logger->info(sprintf( 'Verifying Phase 3 policykey: From Device: %s, Stored: %s', $policykey, $this->_device->policykey) ); if ($this->_state->getPolicyKey($this->_device->id) != $policykey) { $policyStatus = self::STATUS_POLKEYMISM; } else { // Set the final key $policykey = $this->_state->generatePolicyKey(); $this->_state->setPolicyKey($this->_device->id, $policykey); $this->_state->setDeviceRWStatus($this->_device->id, Horde_ActiveSync::RWSTATUS_OK); } $this->_cleanUpAfterPairing(); } elseif (empty($policykey)) { // This is phase2 - we need to set the intermediate key $policykey = $this->_state->generatePolicyKey(); $this->_logger->info(sprintf( 'Generating PHASE2 policy key: %s', $policykey)); $this->_state->setPolicyKey($this->_device->id, $policykey); } // If we are phase2 we need to check this here, before the status is // sent. Prevents devices not supporting the required policies from // being able to connect. if ($phase2 && $status == self::STATUS_SUCCESS && $policyStatus == self::STATUS_SUCCESS && $this->_provisioning == Horde_ActiveSync::PROVISIONING_FORCE) { $policyHandler = new Horde_ActiveSync_Policies( $this->_encoder, $this->_device->version, $this->_driver->getCurrentPolicy($deviceinfo) ); if (!$policyHandler->validatePolicyVersion()) { $this->_handleVersionMismatch(); return true; } } $this->_encoder->startTag(Horde_ActiveSync::PROVISION_PROVISION); $this->_encoder->startTag(Horde_ActiveSync::PROVISION_STATUS); $this->_encoder->content($status); $this->_encoder->endTag(); $this->_encoder->startTag(Horde_ActiveSync::PROVISION_POLICIES); $this->_encoder->startTag(Horde_ActiveSync::PROVISION_POLICY); $this->_encoder->startTag(Horde_ActiveSync::PROVISION_POLICYTYPE); $this->_encoder->content($policytype); $this->_encoder->endTag(); $this->_encoder->startTag(Horde_ActiveSync::PROVISION_STATUS); $this->_encoder->content($policyStatus); $this->_encoder->endTag(); $this->_encoder->startTag(Horde_ActiveSync::PROVISION_POLICYKEY); $this->_encoder->content($policykey); $this->_encoder->endTag(); // Send security policies. if ($phase2 && $status == self::STATUS_SUCCESS && $policyStatus == self::STATUS_SUCCESS) { $this->_encoder->startTag(Horde_ActiveSync::PROVISION_DATA); if ($policytype == Horde_ActiveSync::POLICYTYPE_XML) { $policyHandler->toXml(); } else { $policyHandler->toWbxml(); } $this->_encoder->endTag(); //data } $this->_encoder->endTag(); //policy $this->_encoder->endTag(); //policies // Remote wipe if requested. $rwstatus = $this->_state->getDeviceRWStatus($this->_device->id); if ($rwstatus == Horde_ActiveSync::RWSTATUS_PENDING || $rwstatus == Horde_ActiveSync::RWSTATUS_WIPED) { $this->_encoder->startTag(Horde_ActiveSync::PROVISION_REMOTEWIPE, false, true); $this->_state->setDeviceRWStatus($this->_device->id, Horde_ActiveSync::RWSTATUS_WIPED); } $this->_encoder->endTag(); //provision return true; } /** * Send a WBXML response to the output stream indicating that no * provision requests are necessary. * * @param integer $status The status code to send along with the response. */ protected function _sendNoProvisionNeededResponse($status) { $this->_encoder->startWBXML(); $this->_encoder->startTag(Horde_ActiveSync::PROVISION_PROVISION); $this->_encoder->startTag(Horde_ActiveSync::PROVISION_STATUS); $this->_encoder->content($status); $this->_encoder->endTag(); $this->_encoder->startTag(Horde_ActiveSync::PROVISION_POLICIES); $this->_encoder->startTag(Horde_ActiveSync::PROVISION_POLICY); $this->_encoder->startTag(Horde_ActiveSync::PROVISION_STATUS); $this->_encoder->content(self::STATUS_NOTDEFINED); $this->_encoder->endTag(); $this->_encoder->endTag(); $this->_encoder->endTag(); $this->_encoder->endTag(); } /** * Handle global provision request errors, and send the output to the * output stream. * * @param integer $status The status code to send. */ protected function _globalError($status) { $this->_encoder->StartWBXML(); $this->_encoder->startTag(Horde_ActiveSync::PROVISION_PROVISION); $this->_encoder->startTag(Horde_ActiveSync::PROVISION_STATUS); $this->_encoder->content($status); $this->_encoder->endTag(); $this->_encoder->endTag(); return false; } /** * Handle the EAS 14.1 SETTINGS_DEVICEINFORMATION parsing. * * @return boolean|array An array of received device information or false * on any protocol error. */ protected function _handleSettings() { // EAS 14.1 REQUIRES SETTINGS_DEVICEINFORMATION in the PROVISION command. if (!$this->_decoder->getElementStartTag(Horde_ActiveSync_Request_Settings::SETTINGS_DEVICEINFORMATION)) { return false; } if (!$this->_decoder->getElementStartTag(Horde_ActiveSync_Request_Settings::SETTINGS_SET)) { return false; } $di = array(); $settings = Horde_ActiveSync::messageFactory('DeviceInformation'); $settings->decodeStream($this->_decoder); $di[Horde_ActiveSync_Request_Settings::SETTINGS_MODEL] = $settings->model; $di[Horde_ActiveSync_Request_Settings::SETTINGS_IMEI] = $settings->imei; $di[Horde_ActiveSync_Request_Settings::SETTINGS_FRIENDLYNAME] = $settings->friendlyname; $di[Horde_ActiveSync_Request_Settings::SETTINGS_OS] = $settings->os; $di[Horde_ActiveSync_Request_Settings::SETTINGS_OSLANGUAGE] = $settings->oslanguage; $di[Horde_ActiveSync_Request_Settings::SETTINGS_PHONENUMBER] = $settings->phonenumber; $di[Horde_ActiveSync_Request_Settings::SETTINGS_USERAGENT] = $settings->useragent; $di[Horde_ActiveSync_Request_Settings::SETTINGS_MOBILEOPERATOR] = $settings->mobileoperator; $di[Horde_ActiveSync_Request_Settings::SETTINGS_ENABLEOUTBOUNDSMS] = $settings->enableoutboundsms; $this->_decoder->getElementEndTag(); $this->_decoder->getElementEndTag(); return $di; } /** * Output status that indicates device does not support the required * policies. * */ protected function _handleVersionMismatch() { $this->_encoder->startTag(Horde_ActiveSync::PROVISION_PROVISION); $this->_encoder->startTag(Horde_ActiveSync::PROVISION_STATUS); $this->_encoder->content(Horde_ActiveSync_Status::DEVICE_NOT_FULLY_PROVISIONABLE); $this->_encoder->endTag(); $this->_encoder->endTag(); } } Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Request/ResolveRecipients.php0000664000076500000240000003337412654565405023422 0ustar * @package ActiveSync */ /** * ActiveSync Handler for resolving recipients. * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2012-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync * @internal */ class Horde_ActiveSync_Request_ResolveRecipients extends Horde_ActiveSync_Request_Base { const TAG_RESOLVERECIPIENTS = 'ResolveRecipients:ResolveRecipients'; const TAG_RESPONSE = 'ResolveRecipients:Response'; const TAG_STATUS = 'ResolveRecipients:Status'; const TAG_TYPE = 'ResolveRecipients:Type'; const TAG_RECIPIENT = 'ResolveRecipients:Recipient'; const TAG_DISPLAYNAME = 'ResolveRecipients:DisplayName'; const TAG_EMAILADDRESS = 'ResolveRecipients:EmailAddress'; const TAG_CERTIFICATES = 'ResolveRecipients:Certificates'; const TAG_CERTIFICATE = 'ResolveRecipients:Certificate'; const TAG_MINICERTIFICATE = 'ResolveRecipients:MiniCertificate'; const TAG_OPTIONS = 'ResolveRecipients:Options'; const TAG_TO = 'ResolveRecipients:To'; const TAG_CERTIFICATERETRIEVAL = 'ResolveRecipients:CertificateRetrieval'; const TAG_RECIPIENTCOUNT = 'ResolveRecipients:RecipientCount'; const TAG_MAXCERTIFICATES = 'ResolveRecipients:MaxCertificates'; const TAG_MAXAMBIGUOUSRECIPIENTS = 'ResolveRecipients:MaxAmbiguousRecipients'; const TAG_CERTIFICATECOUNT = 'ResolveRecipients:CertificateCount'; const TAG_MAXSIZE = 'ResolveRecipients:MaxSize'; const TAG_DATA = 'ResolveRecipients:Data'; const TAG_PICTURE = 'ResolveRecipients:Picture'; const TAG_MAXPICTURES = 'ResolveRecipients:MaxPictures'; // 14 const TAG_AVAILABILITY = 'ResolveRecipients:Availability'; const TAG_STARTTIME = 'ResolveRecipients:StartTime'; const TAG_ENDTIME = 'ResolveRecipients:EndTime'; const TAG_MERGEDFREEBUSY = 'ResolveRecipients:MergedFreeBusy'; /* Certificate Retrieval */ const CERT_RETRIEVAL_NONE = 1; const CERT_RETRIEVAL_FULL = 2; const CERT_RETRIEVAL_MINI = 3; /* Global Status */ const STATUS_SUCCESS = 1; const STATUS_PROTERR = 5; const STATUS_SERVERERR = 6; /* Response Status */ const STATUS_RESPONSE_SUCCESS = 1; const STATUS_RESPONSE_AMBSUGG = 2; const STATUS_RESPONSE_NONE = 4; /* Certificate Status */ const STATUS_CERT_SUCCESS = 1; const STATUS_CERT_NOCERT = 7; const STATUS_LIMIT = 8; /* Availability Status */ const STATUS_AVAIL_SUCCESS = 1; const STATUS_AVAIL_MAXRECIPIENTS = 160; const STATUS_AVAIL_MAXLIST = 161; const STATUS_AVAIL_TEMPFAILURE = 162; const STATUS_AVAIL_NOTFOUND = 163; /** * Handle the request * * @return boolean * @throws Horde_ActiveSync_Exception */ protected function _handle() { $this->_logger->info(sprintf( '[%s] Handling RESOLVERECIPIENTS command.', $this->_device->id)); if (!$this->_decoder->getElementStartTag(self::TAG_RESOLVERECIPIENTS)) { return false; } $status = self::STATUS_SUCCESS; while ($status == self::STATUS_SUCCESS && ($field = ($this->_decoder->getElementStartTag(self::TAG_TO) ? self::TAG_TO : ($this->_decoder->getElementStartTag(self::TAG_OPTIONS) ? self::TAG_OPTIONS : -1))) != -1) { if ($field == self::TAG_OPTIONS) { while ($status == self::STATUS_SUCCESS && ($option = ($this->_decoder->getElementStartTag(self::TAG_CERTIFICATERETRIEVAL) ? self::TAG_CERTIFICATERETRIEVAL : ($this->_decoder->getElementStartTag(self::TAG_MAXCERTIFICATES) ? self::TAG_MAXCERTIFICATES : ($this->_decoder->getElementStartTag(self::TAG_MAXAMBIGUOUSRECIPIENTS) ? self::TAG_MAXAMBIGUOUSRECIPIENTS : ($this->_decoder->getElementStartTag(self::TAG_AVAILABILITY) ? self::TAG_AVAILABILITY : -1))))) != -1) { if ($option == self::TAG_AVAILABILITY) { $options[self::TAG_AVAILABILITY] = true; while ($status == self::STATUS_SUCCESS && ($tag = ($this->_decoder->getElementStartTag(self::TAG_STARTTIME) ? self::TAG_STARTTIME : ($this->_decoder->getElementStartTag(self::TAG_ENDTIME) ? self::TAG_ENDTIME : -1))) != -1) { $options[$tag] = $this->_decoder->getElementContent(); if (!$this->_decoder->getElementEndTag()) { $status = self::STATUS_PROTERR; } } } else { $options[$option] = $this->_decoder->getElementContent(); } if ($option == self::TAG_PICTURE) { $options[self::TAG_PICTURE] = true; if ($this->_decoder->getElementStartTag(self::TAG_MAXSIZE)) { $options[self::TAG_MAXSIZE] = $this->_decoder->getElementContent(); } if (!$this->_decoder->getElementEndTag()) { $status = self::STATUS_PROTERR; } if ($this->_decoder->getElementStartTag(self::TAG_MAXPICTURES)) { $options[self::TAG_MAXPICTURES] = $this->_decoder->getElementContent(); } if (!$this->_decoder->getElementEndTag()) { $status = self::STATUS_PROTERR; } } if (!$this->_decoder->getElementEndTag()) { $status = self::STATUS_PROTERR; } } if (!$this->_decoder->getElementEndTag()) { $status = self::STATUS_PROTERR; } } elseif ($field == self::TAG_TO) { $content = $this->_decoder->getElementContent(); $to[] = $content; if (!$this->_decoder->getElementEndTag()) { $status = self::STATUS_PROTERR; } } } // Verify max isn't attempted. if (isset($options[self::TAG_AVAILABILITY]) && count($to) > 100) { // Specs say to send this, but it's defined as a child of the // self::TAG_AVAILABILITY response. If we have too many recipients, // we don't check the availability?? Not sure what to do with this. // For now, treat it as a protocol error. //$avail_status = self::STATUS_AVAIL_MAXRECIPIENTS; $status = self::STATUS_PROTERR; } $results = array(); if ($status == self::STATUS_SUCCESS) { foreach ($to as $item) { $driver_opts = array( 'maxcerts' => !empty($options[self::TAG_MAXCERTIFICATES]) ? $options[self::TAG_MAXCERTIFICATES] : false, 'maxambiguous' => !empty($options[self::TAG_MAXAMBIGUOUSRECIPIENTS]) ? $options[self::TAG_MAXAMBIGUOUSRECIPIENTS] : false, 'starttime' => !empty($options[self::TAG_STARTTIME]) ? new Horde_Date($options[self::TAG_STARTTIME], 'utc') : false, 'endtime' => !empty($options[self::TAG_ENDTIME]) ? new Horde_Date($options[self::TAG_ENDTIME], 'utc') : false, 'pictures' => !empty($options[self::TAG_PICTURE]), 'maxsize' => !empty($options[self::TAG_MAXSIZE]) ? $options[self::TAG_MAXSIZE] : false, 'maxpictures' => !empty($options[self::TAG_MAXPICTURES]) ? $options[self::TAG_MAXPICTURES] : false, ); $results[$item] = $this->_driver->resolveRecipient( isset($options[self::TAG_CERTIFICATERETRIEVAL]) ? 'certificate' : 'availability', $item, $driver_opts ); } } $this->_encoder->startWBXML(); $this->_encoder->startTag(self::TAG_RESOLVERECIPIENTS); $this->_encoder->startTag(self::TAG_STATUS); $this->_encoder->content($status); $this->_encoder->endTag(); foreach ($to as $item) { $this->_encoder->startTag(self::TAG_RESPONSE); $this->_encoder->startTag(self::TAG_TO); $this->_encoder->content($item); $this->_encoder->endTag(); $this->_encoder->startTag(self::TAG_STATUS); if (empty($results[$item])) { $responseStatus = self::STATUS_RESPONSE_NONE; } elseif (count($results[$item]) > 1) { $responseStatus = self::STATUS_RESPONSE_AMBSUGG; } else { $responseStatus = self::STATUS_RESPONSE_SUCCESS; } $this->_encoder->content($responseStatus); $this->_encoder->endTag(); $this->_encoder->startTag(self::TAG_RECIPIENTCOUNT); $this->_encoder->content(count($results[$item])); $this->_encoder->endTag(); foreach ($results[$item] as $value) { $this->_encoder->startTag(self::TAG_RECIPIENT); $this->_encoder->startTag(self::TAG_TYPE); $this->_encoder->content($value['type']); $this->_encoder->endTag(); $this->_encoder->startTag(self::TAG_DISPLAYNAME); $this->_encoder->content($value['displayname']); $this->_encoder->endTag(); $this->_encoder->startTag(self::TAG_EMAILADDRESS); $this->_encoder->content($value['emailaddress']); $this->_encoder->endTag(); if (isset($options[self::TAG_CERTIFICATERETRIEVAL]) && $options[self::TAG_CERTIFICATERETRIEVAL] > 1) { $this->_encoder->startTag(self::TAG_CERTIFICATES); $this->_encoder->startTag(self::TAG_STATUS); if (count($value['entries']) == 0) { $certStatus = self::STATUS_CERT_NOCERT; } else { $certStatus = self::STATUS_CERT_SUCCESS; } $this->_encoder->content($certStatus); $this->_encoder->endTag(); $this->_encoder->startTag(self::TAG_CERTIFICATECOUNT); $this->_encoder->content(count($value['entries'])); $this->_encoder->endTag(); switch ($options[self::TAG_CERTIFICATERETRIEVAL]) { case self::CERT_RETRIEVAL_FULL: foreach($value['entries'] as $cert) { $this->_encoder->startTag(self::TAG_CERTIFICATE); $this->_encoder->content($cert); $this->_encoder->endTag(); } break; case self::CERT_RETRIEVAL_MINI: foreach($value['entries'] as $cert) { $this->_encoder->startTag(self::TAG_MINICERTIFICATE); $this->_encoder->content($cert); $this->_encoder->endTag(); } } $this->_encoder->endTag(); } if (isset($options[self::TAG_AVAILABILITY])) { $this->_encoder->startTag(self::TAG_AVAILABILITY); $this->_encoder->startTag(self::TAG_STATUS); $this->_encoder->content(empty($value['availability']) ? self::STATUS_AVAIL_NOTFOUND : self::STATUS_AVAIL_SUCCESS); $this->_encoder->endTag(); if (!empty($value['availability'])) { $this->_encoder->startTag(self::TAG_MERGEDFREEBUSY); $this->_encoder->content($value['availability']); $this->_encoder->endTag(); } $this->_encoder->endTag(); } if ($this->_device->version >= Horde_ActiveSync::VERSION_FOURTEENONE && isset($options[self::TAG_PICTURE]) && !empty($value['picture'])) { $this->_encoder->startTag(self::TAG_PICTURE); $value['picture']->encodeStream($this->_encoder); $this->_encoder->endTag(); } $this->_encoder->endTag(); // end self::TAG_RECIPIENT } $this->_encoder->endTag(); // end self::TAG_RESPONSE } $this->_encoder->endTag(); // end self::TAG_RESOLVERECIPIENTS return true; } } Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Request/Search.php0000664000076500000240000005243112654565405021155 0ustar * @package ActiveSync */ /** * Handle Search requests. * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2012-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync * @internal */ class Horde_ActiveSync_Request_Search extends Horde_ActiveSync_Request_SyncBase { /** Search code page **/ const SEARCH_SEARCH = 'Search:Search'; const SEARCH_STORE = 'Search:Store'; const SEARCH_NAME = 'Search:Name'; const SEARCH_QUERY = 'Search:Query'; const SEARCH_OPTIONS = 'Search:Options'; const SEARCH_RANGE = 'Search:Range'; const SEARCH_STATUS = 'Search:Status'; const SEARCH_RESPONSE = 'Search:Response'; const SEARCH_RESULT = 'Search:Result'; const SEARCH_PROPERTIES = 'Search:Properties'; const SEARCH_TOTAL = 'Search:Total'; const SEARCH_EQUALTO = 'Search:EqualTo'; const SEARCH_VALUE = 'Search:Value'; const SEARCH_AND = 'Search:And'; const SEARCH_OR = 'Search:Or'; const SEARCH_FREETEXT = 'Search:FreeText'; const SEARCH_DEEPTRAVERSAL = 'Search:DeepTraversal'; const SEARCH_LONGID = 'Search:LongId'; const SEARCH_REBUILDRESULTS = 'Search:RebuildResults'; const SEARCH_LESSTHAN = 'Search:LessThan'; const SEARCH_GREATERTHAN = 'Search:GreaterThan'; const SEARCH_SCHEMA = 'Search:Schema'; const SEARCH_SUPPORTED = 'Search:Supported'; const SEARCH_USERNAME = 'Search:UserName'; const SEARCH_PASSWORD = 'Search:Password'; // 14 const SEARCH_CONVERSATIONID = 'Search:ConversationId'; // 14.1 const SEARCH_PICTURE = 'Search:Picture'; const SEARCH_MAXSIZE = 'Search:MaxSize'; const SEARCH_MAXPICTURES = 'Search:MaxPictures'; /** Search Status **/ const SEARCH_STATUS_SUCCESS = 1; const SEARCH_STATUS_ERROR = 3; /** Compat **/ const STATUS_PROTERROR = 3; /** Store Status **/ const STORE_STATUS_SUCCESS = 1; const STORE_STATUS_PROTERR = 2; const STORE_STATUS_SERVERERR = 3; const STORE_STATUS_BADLINK = 4; const STORE_STATUS_NOTFOUND = 6; const STORE_STATUS_CONNECTIONERR = 7; const STORE_STATUS_COMPLEX = 8; /** * @var Horde_ActiveSync_Collections */ protected $_collections; /** * Handle request * * @return boolean */ protected function _handle() { $this->_logger->info(sprintf( '[%s] Handling SEARCH command.', $this->_device->id)); $search_status = self::SEARCH_STATUS_SUCCESS; $store_status = self::STORE_STATUS_SUCCESS; $this->_collections = $this->_activeSync->getCollectionsObject(); if (!$this->_decoder->getElementStartTag(self::SEARCH_SEARCH) || !$this->_decoder->getElementStartTag(self::SEARCH_STORE) || !$this->_decoder->getElementStartTag(self::SEARCH_NAME)) { $search_status = self::SEARCH_STATUS_ERROR; } $search_name = $this->_decoder->getElementContent(); if (!$this->_decoder->getElementEndTag()) { $search_status = self::SEARCH_STATUS_ERROR; $store_status = self::STORE_STATUS_PROTERR; } if (!$this->_decoder->getElementStartTag(self::SEARCH_QUERY)) { $search_status = self::SEARCH_STATUS_ERROR; $store_status = self::STORE_STATUS_PROTERR; } $search_query = array(); switch (Horde_String::lower($search_name)) { case 'documentlibrary': case 'mailbox': $search_query['query'] = $this->_parseQuery(); break; case 'gal': $search_query['query'] = $this->_decoder->getElementContent(); } if (!$this->_decoder->getElementEndTag()) { $search_status = self::SEARCH_STATUS_ERROR; $store_status = self::STORE_STATUS_PROTERR; } $mime = Horde_ActiveSync::MIME_SUPPORT_NONE; if ($this->_decoder->getElementStartTag(self::SEARCH_OPTIONS)) { $searchbodypreference = array(); while(1) { if ($this->_decoder->getElementStartTag(self::SEARCH_RANGE)) { $search_query['range'] = $this->_decoder->getElementContent(); if (!$this->_decoder->getElementEndTag()) { $search_status = self::SEARCH_STATUS_ERROR; $store_status = self::STORE_STATUS_PROTERR; } } if ($this->_decoder->getElementStartTag(self::SEARCH_DEEPTRAVERSAL)) { if (!($search_query['deeptraversal'] = $this->_decoder->getElementContent())) { $search_query['deeptraversal'] = true; } elseif (!$this->_decoder->getElementEndTag()) { return false; } } if ($this->_decoder->getElementStartTag(self::SEARCH_REBUILDRESULTS)) { if (!($search_query['rebuildresults'] = $this->_decoder->getElementContent())) { $search_query['rebuildresults'] = true; } elseif (!$this->_decoder->getElementEndTag()) { return false; } } if ($this->_decoder->getElementStartTag(self::SEARCH_USERNAME)) { if (!($search_query['username'] = $this->_decoder->getElementContent())) { return false; } elseif (!$this->_decoder->getElementEndTag()) { return false; } } if ($this->_decoder->getElementStartTag(self::SEARCH_PASSWORD)) { if (!($search_query['password'] = $this->_decoder->getElementContent())) return false; else if(!$this->_decoder->getElementEndTag()) return false; } if ($this->_decoder->getElementStartTag(self::SEARCH_SCHEMA)) { if (!($search_query['schema'] = $this->_decoder->getElementContent())) { $search_query['schema'] = true; } elseif (!$this->_decoder->getElementEndTag()) { return false; } } // 14.1 Only if ($this->_decoder->getElementStartTag(self::SEARCH_PICTURE)) { $search_query[self::SEARCH_PICTURE] = true; if ($this->_decoder->getElementStartTag(self::SEARCH_MAXSIZE)) { $search_query[self::SEARCH_MAXSIZE] = $this->_decoder->getElementContent(); if (!$this->_decoder->getElementEndTag()) { return false; } } if ($this->_decoder->getElementStartTag(self::SEARCH_MAXPICTURES)) { $search_query[self::SEARCH_MAXPICTURES] = $this->_decoder->getElementContent(); if (!$this->_decoder->getElementEndTag()) { return false; } } } if ($this->_decoder->getElementStartTag(Horde_ActiveSync::AIRSYNCBASE_BODYPREFERENCE)) { $this->_bodyPrefs($searchbodypreference); $searchbodypreference = empty($searchbodypreference['bodyprefs']) ? array() : $searchbodypreference['bodyprefs']; } if ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_MIMESUPPORT)) { $this->_mimeSupport($searchbodypreference); } // EAS 14.1 if ($this->_device->version >= Horde_ActiveSync::VERSION_FOURTEENONE) { $rm = array(); if ($this->_decoder->getElementStartTag(Horde_ActiveSync::RM_SUPPORT)) { $this->_rightsManagement($rm); } if ($this->_decoder->getElementStartTag(Horde_ActiveSync::AIRSYNCBASE_BODYPARTPREFERENCE)) { $this->_bodyPartPrefs($search_query); } } $e = $this->_decoder->peek(); if ($e[Horde_ActiveSync_Wbxml::EN_TYPE] == Horde_ActiveSync_Wbxml::EN_TYPE_ENDTAG) { $this->_decoder->getElementEndTag(); break; } } } if (!$this->_decoder->getElementEndTag()) { //store $search_status = self::SEARCH_STATUS_ERROR; $store_status = self::STORE_STATUS_PROTERR; } if (!$this->_decoder->getElementEndTag()) { //search $search_status = self::SEARCH_STATUS_ERROR; $store_status = self::STORE_STATUS_PROTERR; } $search_query['range'] = empty($search_query['range']) ? '0-99' : $search_query['range']; switch(Horde_String::lower($search_name)) { case 'mailbox': $search_query['rebuildresults'] = !empty($search_query['rebuildresults']); $search_query['deeptraversal'] = !empty($search_query['deeptraversal']); break; } // Get search results from backend $search_result = $this->_driver->getSearchResults($search_name, $search_query); // @TODO: Remove for H6. Total should be returned from the search call, // if it's not, do the best we can an use the count of results from // this page. if (empty($search_result['total'])) { $search_result['total'] = count($search_result['rows']); } /* Send output */ $this->_encoder->startWBXML(); $this->_encoder->startTag(self::SEARCH_SEARCH); $this->_encoder->startTag(self::SEARCH_STATUS); $this->_encoder->content($search_status); $this->_encoder->endTag(); $this->_encoder->startTag(self::SEARCH_RESPONSE); $this->_encoder->startTag(self::SEARCH_STORE); $this->_encoder->startTag(self::SEARCH_STATUS); $this->_encoder->content($store_status); $this->_encoder->endTag(); if (is_array($search_result['rows']) && !empty($search_result['rows'])) { $count = 0; foreach ($search_result['rows'] as $u) { $count++; switch (Horde_String::lower($search_name)) { case 'documentlibrary': $this->_encoder->startTag(self::SEARCH_RESULT); $doc = Horde_ActiveSync::messageFactory('DocumentLibrary'); $doc->linkid = $u['linkid']; $doc->displayname = $u['name']; $doc->isfolder = $u['is_folder'] ? '1' : '0'; $doc->creationdate = $u['created']; $doc->lastmodifieddate = $u['modified']; $doc->ishidden = '0'; $doc->contentlength = $u['content-length']; if (!empty($u['content-type'])) { $doc->contenttype = $u['content-type']; } $this->_encoder->startTag(self::SEARCH_PROPERTIES); $doc->encodeStream($this->_encoder); $this->_encoder->endTag(); $this->_encoder->endTag(); continue; case 'gal': $this->_encoder->startTag(self::SEARCH_RESULT); $this->_encoder->startTag(self::SEARCH_PROPERTIES); $this->_encoder->startTag(Horde_ActiveSync::GAL_DISPLAYNAME); $this->_encoder->content($u[Horde_ActiveSync::GAL_DISPLAYNAME]); $this->_encoder->endTag(); $this->_encoder->startTag(Horde_ActiveSync::GAL_PHONE); $this->_encoder->content($u[Horde_ActiveSync::GAL_PHONE]); $this->_encoder->endTag(); $this->_encoder->startTag(Horde_ActiveSync::GAL_OFFICE); $this->_encoder->content($u[Horde_ActiveSync::GAL_OFFICE]); $this->_encoder->endTag(); $this->_encoder->startTag(Horde_ActiveSync::GAL_TITLE); $this->_encoder->content($u[Horde_ActiveSync::GAL_TITLE]); $this->_encoder->endTag(); $this->_encoder->startTag(Horde_ActiveSync::GAL_COMPANY); $this->_encoder->content($u[Horde_ActiveSync::GAL_COMPANY]); $this->_encoder->endTag(); $this->_encoder->startTag(Horde_ActiveSync::GAL_ALIAS); $this->_encoder->content($u[Horde_ActiveSync::GAL_ALIAS]); $this->_encoder->endTag(); $this->_encoder->startTag(Horde_ActiveSync::GAL_FIRSTNAME); $this->_encoder->content($u[Horde_ActiveSync::GAL_FIRSTNAME]); $this->_encoder->endTag(); $this->_encoder->startTag(Horde_ActiveSync::GAL_LASTNAME); $this->_encoder->content($u[Horde_ActiveSync::GAL_LASTNAME]); $this->_encoder->endTag(); $this->_encoder->startTag(Horde_ActiveSync::GAL_HOMEPHONE); $this->_encoder->content($u[Horde_ActiveSync::GAL_HOMEPHONE]); $this->_encoder->endTag(); $this->_encoder->startTag(Horde_ActiveSync::GAL_MOBILEPHONE); $this->_encoder->content($u[Horde_ActiveSync::GAL_MOBILEPHONE]); $this->_encoder->endTag(); $this->_encoder->startTag(Horde_ActiveSync::GAL_EMAILADDRESS); $this->_encoder->content($u[Horde_ActiveSync::GAL_EMAILADDRESS]); $this->_encoder->endTag(); if ($this->_device->version >= Horde_ActiveSync::VERSION_FOURTEENONE && !empty($u[Horde_ActiveSync::GAL_PICTURE])) { $this->_encoder->startTag(Horde_ActiveSync::GAL_PICTURE); $u[Horde_ActiveSync::GAL_PICTURE]->encodeStream($this->_encoder); $this->_encoder->endTag(); } $this->_encoder->endTag();//properties $this->_encoder->endTag();//result break; case 'mailbox': $this->_encoder->startTag(self::SEARCH_RESULT); $this->_encoder->startTag(Horde_ActiveSync::SYNC_FOLDERTYPE); $this->_encoder->content(Horde_ActiveSync::CLASS_EMAIL); $this->_encoder->endTag(); $this->_encoder->startTag(self::SEARCH_LONGID); $this->_encoder->content($u['uniqueid']); $this->_encoder->endTag(); $this->_encoder->startTag(Horde_ActiveSync::SYNC_FOLDERID); $this->_encoder->content($this->_collections->getFolderUidForBackendId($u['searchfolderid'])); $this->_encoder->endTag(); $this->_encoder->startTag(self::SEARCH_PROPERTIES); $msg = $this->_driver->ItemOperationsFetchMailbox($u['uniqueid'], $searchbodypreference, $mime); $msg->encodeStream($this->_encoder); $this->_encoder->endTag();//properties $this->_encoder->endTag();//result } } if (!empty($search_query['range'])) { $range = explode('-', $search_query['range']); // If total results are less than max range, // we have all results and must modify the returned range. if ($count < ($range[1] - $range[0] + 1)) { $search_range = $range[0] . '-' . ($count - 1); } else { $search_range = $search_query['range']; } } $this->_encoder->startTag(self::SEARCH_RANGE); $this->_encoder->content($search_range); $this->_encoder->endTag(); $this->_encoder->startTag(self::SEARCH_TOTAL); $this->_encoder->content($search_result['total']); $this->_encoder->endTag(); } $this->_encoder->endTag();//store $this->_encoder->endTag();//response $this->_encoder->endTag();//search return true; } /** * Receive, and parse, the incoming wbxml query. * * According to MS docs, OR is supported in the protocol, but will ALWAYS * return a searchToComplex status in Exchange 2007. Additionally, AND is * ONLY supported as the topmost element. No nested AND is allowed. All * such queries will return a searchToComplex status. * * @param boolean $subquery Parsing a subquery. * * @return array */ protected function _parseQuery($subquery = null) { $query = array(); while (($type = ($this->_decoder->getElementStartTag(self::SEARCH_AND) ? self::SEARCH_AND : ($this->_decoder->getElementStartTag(self::SEARCH_OR) ? self::SEARCH_OR : ($this->_decoder->getElementStartTag(self::SEARCH_EQUALTO) ? self::SEARCH_EQUALTO : ($this->_decoder->getElementStartTag(self::SEARCH_LESSTHAN) ? self::SEARCH_LESSTHAN : ($this->_decoder->getElementStartTag(self::SEARCH_GREATERTHAN) ? self::SEARCH_GREATERTHAN : ($this->_decoder->getElementStartTag(self::SEARCH_FREETEXT) ? self::SEARCH_FREETEXT : ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_FOLDERID) ? Horde_ActiveSync::SYNC_FOLDERID : ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_FOLDERTYPE) ? Horde_ActiveSync::SYNC_FOLDERTYPE : ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_DOCUMENTLIBRARY_LINKID) ? Horde_ActiveSync::SYNC_DOCUMENTLIBRARY_LINKID : ($this->_decoder->getElementStartTag(Horde_ActiveSync_Message_Mail::POOMMAIL_DATERECEIVED) ? Horde_ActiveSync_Message_Mail::POOMMAIL_DATERECEIVED : -1))))))))))) != -1) { switch ($type) { case self::SEARCH_AND: case self::SEARCH_OR: case self::SEARCH_EQUALTO: case self::SEARCH_LESSTHAN: case self::SEARCH_GREATERTHAN: $q = array( 'op' => $type, 'value' => $this->_parseQuery(true) ); if ($subquery) { $query['subquery'][] = $q; } else { $query[] = $q; } $this->_decoder->getElementEndTag(); break; default: if (($query[$type] = $this->_decoder->getElementContent())) { if ($type == Horde_ActiveSync::SYNC_FOLDERID) { $query['serverid'] = $this->_collections->getBackendIdForFolderUid($query[$type]); } $this->_decoder->getElementEndTag(); } else { $this->_decoder->getElementStartTag(self::SEARCH_VALUE); $query[$type] = $this->_decoder->getElementContent(); switch ($type) { case Horde_ActiveSync_Message_Mail::POOMMAIL_DATERECEIVED: $query[$type] = new Horde_Date($query[$type]); break; } $this->_decoder->getElementEndTag(); }; break; } } return $query; } protected function _handleError(array $data) { $this->_decoder->getElementEndTag(); // end SYNC_ITEMOPERATIONS_ITEMOPERATIONS $this->_encoder->startWBXML($this->_activeSync->multipart); $this->_encoder->startTag(self::ITEMOPERATIONS_ITEMOPERATIONS); $this->_encoder->startTag(self::SEARCH_STATUS); $this->_encoder->content($this->_statusCode); $this->_encoder->endTag(); $this->_encoder->endTag(); } } Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Request/SendMail.php0000664000076500000240000001455112654565405021445 0ustar * @package ActiveSync */ /** * Handle SendMail requests. * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2009-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync * @internal */ class Horde_ActiveSync_Request_SendMail extends Horde_ActiveSync_Request_Base { /** * Handle the request * * @return boolean */ protected function _handle() { // Check for wbxml vs RFC822 if (!$this->_decoder->isWbxml()) { $this->_logger->info(sprintf( '[%s] Handling SENDMAIL command with no Wbxml.', $this->_procid)); $stream = $this->_decoder->getFullInputStream(); try { $result = $this->_driver->sendMail($stream, false, false, false, true); fclose($stream); return $result; } catch (Horde_ActiveSync_Exception $e) { $this->_logger->err($e->getMessage()); $this->_handError( Horde_ActiveSync_Status::MAIL_SUBMISSION_FAILED, Horde_ActiveSync_Message_SendMail::COMPOSEMAIL_SENDMAIL); return true; } } else { return $this->_handleWbxmlRequest(); } } /** * Handle EAS 14+ SendMail/SmartReply/SmartForward requests. * * @return boolean */ protected function _handleWbxmlRequest() { $this->_logger->info(sprintf( '[%s] Handling SENDMAIL command with Wbxml.', $this->_procid)); // Get the first element and see what type of mail request we have. $e = $this->_decoder->getElement(); if ($e[Horde_ActiveSync_Wbxml::EN_TYPE] != Horde_ActiveSync_Wbxml::EN_TYPE_STARTTAG) { $this->_handleError( Horde_ActiveSync_Status::INVALID_WBXML, Horde_ActiveSync_Message_SendMail::COMPOSEMAIL_SENDMAIL); return true; } $sendmail = $smartreply = $smartforward = false; switch ($e[Horde_ActiveSync_Wbxml::EN_TAG]) { case Horde_ActiveSync_Message_SendMail::COMPOSEMAIL_SENDMAIL: $sendmail = true; break; case Horde_ActiveSync_Message_SendMail::COMPOSEMAIL_SMARTREPLY: $smartreply = true; break; case Horde_ActiveSync_Message_SendMail::COMPOSEMAIL_SMARTFORWARD: $smartforward = true; } if (!$sendmail && !$smartreply && !$smartforward) { return $this->_handleError( Horde_ActiveSync_Status::INVALID_CONTENT, Horde_ActiveSync_Message_SendMail::COMPOSEMAIL_SENDMAIL); } $mail = Horde_ActiveSync::messageFactory('SendMail'); $mail->decodeStream($this->_decoder); if ($smartreply || $smartforward) { $mail->source->folderid = $this->_activeSync->getCollectionsObject()->getBackendIdForFolderUid($mail->source->folderid); } try { // @TODO fix this ugly method call in H6 when we can break BC. return $this->_driver->sendMail(null, $smartforward, $smartreply, null, null, $mail); } catch (Horde_Exception_NotFound $ex) { $this->_logger->err($ex->getMessage()); $this->_handleError( Horde_ActiveSync_Status::ITEM_NOT_FOUND, $e[Horde_ActiveSync_Wbxml::EN_TAG]); } catch (Horde_ActiveSync_Exception_EmailFatalFailure $ex) { $this->_logger->err($ex->getMessage()); if ($this->_device->version < Horde_ActiveSync::VERSION_FOURTEEN) { // For now, return false, which causes a HTTP 500 to be returned // - the expected behavior prior to EAS 14.0. For 3.0, we will // rework this stuff into a Response object. $this->_logger->err(sprintf( '[%s] Returning HTTP 500, since EAS version < 14.0', $this->_procid) ); return false; } $this->_handleError( Horde_ActiveSync_Status::SERVER_ERROR, $e[Horde_ActiveSync_Wbxml::EN_TAG]); } catch (Horde_ActiveSync_Exception $ex) { $this->_logger->err($ex->getMessage()); if ($this->_device->version < Horde_ActiveSync::VERSION_FOURTEEN) { // For now, return false, which causes a HTTP 500 to be returned // - the expected behavior prior to EAS 14.0. For 3.0, we will // rework this stuff into a Response object. return false; } $this->_handleError( Horde_ActiveSync_Status::MAIL_SUBMISSION_FAILED, $e[Horde_ActiveSync_Wbxml::EN_TAG]); } return true; } /** * Helper to output a global error response. * * @param integer $status A Horde_ActiveSync_Status:: constant. * @param string $type The type of response tag. */ protected function _handleError($status, $type) { $this->_encoder->startWBXML(); $this->_encoder->startTag($type); $this->_encoder->startTag(Horde_ActiveSync_Message_SendMail::COMPOSEMAIL_STATUS); $this->_encoder->content($status); $this->_encoder->endTag(); $this->_encoder->endTag(); } } Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Request/Settings.php0000664000076500000240000004267712654565405021563 0ustar * @package ActiveSync */ /** * Handle Settings requests. * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2009-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync * @internal */ class Horde_ActiveSync_Request_Settings extends Horde_ActiveSync_Request_Base { /** Wbxml constants **/ const SETTINGS_SETTINGS = 'Settings:Settings'; const SETTINGS_STATUS = 'Settings:Status'; const SETTINGS_GET = 'Settings:Get'; const SETTINGS_SET = 'Settings:Set'; const SETTINGS_OOF = 'Settings:Oof'; const SETTINGS_OOFSTATE = 'Settings:OofState'; const SETTINGS_STARTTIME = 'Settings:StartTime'; const SETTINGS_ENDTIME = 'Settings:EndTime'; const SETTINGS_OOFMESSAGE = 'Settings:OofMessage'; const SETTINGS_APPLIESTOINTERNAL = 'Settings:AppliesToInternal'; const SETTINGS_APPLIESTOEXTERNALKNOWN = 'Settings:AppliesToExternalKnown'; const SETTINGS_APPLIESTOEXTERNALUNKNOWN = 'Settings:AppliesToExternalUnknown'; const SETTINGS_ENABLED = 'Settings:Enabled'; const SETTINGS_REPLYMESSAGE = 'Settings:ReplyMessage'; const SETTINGS_BODYTYPE = 'Settings:BodyType'; const SETTINGS_DEVICEPASSWORD = 'Settings:DevicePassword'; const SETTINGS_PASSWORD = 'Settings:Password'; const SETTINGS_DEVICEINFORMATION = 'Settings:DeviceInformation'; const SETTINGS_MODEL = 'Settings:Model'; const SETTINGS_IMEI = 'Settings:IMEI'; const SETTINGS_FRIENDLYNAME = 'Settings:FriendlyName'; const SETTINGS_OS = 'Settings:OS'; const SETTINGS_OSLANGUAGE = 'Settings:OSLanguage'; const SETTINGS_PHONENUMBER = 'Settings:PhoneNumber'; const SETTINGS_USERINFORMATION = 'Settings:UserInformation'; const SETTINGS_EMAILADDRESSES = 'Settings:EmailAddresses'; const SETTINGS_SMTPADDRESS = 'Settings:SmtpAddress'; const SETTINGS_USERAGENT = 'Settings:UserAgent'; /** EAS 14.0 **/ const SETTINGS_ENABLEOUTBOUNDSMS = 'Settings:EnableOutboundSMS'; const SETTINGS_MOBILEOPERATOR = 'Settings:MobileOperator'; /** EAS 14.1 **/ const SETTINGS_PRIMARYSMTPADDRESS = 'Settings:PrimarySmtpAddress'; const SETTINGS_ACCOUNTS = 'Settings:Accounts'; const SETTINGS_ACCOUNT = 'Settings:Account'; const SETTINGS_ACCOUNTID = 'Settings:AccountId'; const SETTINGS_USERDISPLAYNAME = 'Settings:UserDisplayName'; const SETTINGS_RIGHTSMANAGEMENTINFO = 'Settings:RightsManagementInformation'; /** Status codes **/ const STATUS_SUCCESS = 1; const STATUS_ERROR = 2; const STATUS_UNAVAILABLE = 4; /** Out of office constants **/ const OOF_STATE_TIMEBASED = 2; const OOF_STATE_ENABLED = 1; const OOF_STATE_DISABLED = 0; /** * Handle the request. * * @see Horde_ActiveSync_Request_Base::_handle() */ protected function _handle() { if (!$this->_decoder->getElementStartTag(self::SETTINGS_SETTINGS)) { throw new Horde_ActiveSync_Exception('Protocol Errror'); } $request = array(); while (($reqtype = ($this->_decoder->getElementStartTag(self::SETTINGS_OOF) ? self::SETTINGS_OOF : ($this->_decoder->getElementStartTag(self::SETTINGS_DEVICEINFORMATION) ? self::SETTINGS_DEVICEINFORMATION : ($this->_decoder->getElementStartTag(self::SETTINGS_USERINFORMATION) ? self::SETTINGS_USERINFORMATION : ($this->_decoder->getElementStartTag(self::SETTINGS_DEVICEPASSWORD) ? self::SETTINGS_DEVICEPASSWORD : ($this->_decoder->getElementStartTag(self::SETTINGS_RIGHTSMANAGEMENTINFO) ? self::SETTINGS_RIGHTSMANAGEMENTINFO : -1)))))) != -1) { while (($querytype = ($this->_decoder->getElementStartTag(self::SETTINGS_GET) ? self::SETTINGS_GET : ($this->_decoder->getElementStartTag(self::SETTINGS_SET) ? self::SETTINGS_SET : -1))) != -1) { switch ($querytype) { case self::SETTINGS_GET: switch ($reqtype) { case self::SETTINGS_OOF: $oof = Horde_ActiveSync::messageFactory('Oof'); $oof->decodeStream($this->_decoder); $request['get']['oof']['bodytype'] = $oof->bodytype; $this->_decoder->getElementEndTag(); // SETTINGS_GET break; case self::SETTINGS_USERINFORMATION: // These are empty tags. $request['get']['userinformation'] = array(); $this->_decoder->getElementContent(); break; case self::SETTINGS_RIGHTSMANAGEMENTINFO: // These are empty tags. $request['get']['rightsmanagementinfo'] = true; $this->_decoder->getElementContent(); break; } break; case self::SETTINGS_SET: switch ($reqtype) { case self::SETTINGS_OOF: $oof = Horde_ActiveSync::messageFactory('Oof'); $oof->decodeStream($this->_decoder); $request['set']['oof']['oofstate'] = $oof->state; $request['set']['oof']['starttime'] = $oof->starttime; $request['set']['oof']['endtime'] = $oof->endtime; $request['set']['oof']['oofmsgs'] = array(); foreach ($oof->messages as $msg) { $message = array(); $message['appliesto'] = !empty($msg->internal) ? Horde_ActiveSync_Request_Settings::SETTINGS_APPLIESTOINTERNAL : (!empty($msg->externalknown) ? Horde_ActiveSync_Request_Settings::SETTINGS_APPLIESTOEXTERNALKNOWN : Horde_ActiveSync_Request_Settings::SETTINGS_APPLIESTOEXTERNALUNKNOWN); $message['enabled'] = $msg->enabled; $message['replymessage'] = $msg->reply; $message['bodytype'] = $msg->bodytype; $request['set']['oof']['oofmsgs'][] = $message; } break; case self::SETTINGS_DEVICEINFORMATION : // @TODO Clean the return values up when we can break bc. $device_properties = $this->_device->properties; $settings = Horde_ActiveSync::messageFactory('DeviceInformation'); $settings->decodeStream($this->_decoder); $device_properties[self::SETTINGS_MODEL] = $settings->model; $device_properties[self::SETTINGS_IMEI] = $settings->imei; $device_properties[self::SETTINGS_FRIENDLYNAME] = $settings->friendlyname; $device_properties[self::SETTINGS_OS] = $settings->os; $device_properties[self::SETTINGS_OSLANGUAGE] = $settings->oslanguage; $device_properties[self::SETTINGS_PHONENUMBER] = $settings->phonenumber; $device_properties[self::SETTINGS_USERAGENT] = $settings->useragent; $device_properties[self::SETTINGS_MOBILEOPERATOR] = $settings->mobileoperator; $device_properties[self::SETTINGS_ENABLEOUTBOUNDSMS] = $settings->enableoutboundsms; try { $device_properties['version'] = $this->_device->version; $this->_device->setDeviceProperties($device_properties); $this->_device->save(); } catch (Horde_ActiveSync_Exception $e) { $this->_logger->err($e->getMessage()); unset($device_properties); } break; case self::SETTINGS_DEVICEPASSWORD : $this->_decoder->getElementStartTag(self::SETTINGS_PASSWORD); if (($password = $this->_decoder->getElementContent()) !== false) { $this->_decoder->getElementEndTag(); // end $field } $request['set']['devicepassword'] = $password; break; } $this->_decoder->getElementEndTag(); // SETTINGS_SET break; } } // SETTINGS_OOF || SETTINGS_DEVICEPW || SETTINGS_DEVICEINFORMATION || SETTINGS_USERINFORMATION $this->_decoder->getElementEndTag(); } $this->_decoder->getElementEndTag(); // SETTINGS // Tell the backend if (isset($request['set'])) { $result['set'] = $this->_driver->setSettings($request['set'], $this->_device->id); } if (isset($request['get'])) { $result['get'] = $this->_driver->getSettings($request['get'], $this->_device->id); } // Output response $this->_encoder->startWBXML(); $this->_encoder->startTag(self::SETTINGS_SETTINGS); $this->_encoder->startTag(self::SETTINGS_STATUS); $this->_encoder->content(self::STATUS_SUCCESS); $this->_encoder->endTag(); // end self::SETTINGS_STATUS if (isset($request['set']['oof'])) { $this->_encoder->startTag(self::SETTINGS_OOF); $this->_encoder->startTag(self::SETTINGS_STATUS); if (!isset($result['set']['oof'])) { $this->_encoder->content(self::OOF_STATE_DISABLED); } else { $this->_encoder->content($result['set']['oof']); } $this->_encoder->endTag(); // end self::SETTINGS_STATUS $this->_encoder->endTag(); // end self::SETTINGS_OOF } if (isset($device_properties)) { $this->_encoder->startTag(self::SETTINGS_DEVICEINFORMATION); $this->_encoder->startTag(self::SETTINGS_STATUS); $this->_encoder->content(Horde_ActiveSync_Request_Settings::STATUS_SUCCESS); $this->_encoder->endTag(); // end self::SETTINGS_STATUS $this->_encoder->endTag(); // end self::SETTINGS_DEVICEINFORMATION } if (isset($request['set']['devicepassword'])) { $this->_encoder->startTag(self::SETTINGS_DEVICEPASSWORD); $this->_encoder->startTag(self::SETTINGS_STATUS); if (!isset($result['set']['devicepassword'])) { $this->_encoder->content(0); } else { $this->_encoder->content($result['set']['devicepassword']); } $this->_encoder->endTag(); // end self::SETTINGS_STATUS $this->_encoder->endTag(); // end self::SETTINGS_DEVICEPASSWORD } if (isset($request['get']['userinformation']) && isset($result['get']['userinformation'])) { $this->_encoder->startTag(self::SETTINGS_USERINFORMATION); $this->_encoder->startTag(self::SETTINGS_STATUS); $this->_encoder->content($result['get']['userinformation']['status']); $this->_encoder->endTag(); // end self::SETTINGS_STATUS $this->_encoder->startTag(self::SETTINGS_GET); // @todo remove accounts existence check for H6. if ($this->_device->version >= Horde_ActiveSync::VERSION_FOURTEENONE && !empty($result['get']['userinformation']['accounts'])) { $this->_encoder->startTag(self::SETTINGS_ACCOUNTS); foreach ($result['get']['userinformation']['accounts'] as $account) { $this->_encoder->startTag(self::SETTINGS_ACCOUNT); if (!empty($account['fullname'])) { $this->_encoder->startTag(self::SETTINGS_USERDISPLAYNAME); $this->_encoder->content($account['fullname']); $this->_encoder->endTag(); } if (!empty($account['emailaddresses'])) { $this->_encoder->startTag(self::SETTINGS_EMAILADDRESSES); $this->_encoder->startTag(self::SETTINGS_PRIMARYSMTPADDRESS); $this->_encoder->content($account['emailaddresses'][0]); $this->_encoder->endTag(); if (!empty($account['id'])) { $this->_encoder->startTag(self::SETTINGS_ACCOUNTID); $this->_encoder->content(array_pop($account['id'])); $this->_encoder->endTag(); } foreach($account['emailaddresses'] as $value) { $this->_encoder->startTag(self::SETTINGS_SMTPADDRESS); $this->_encoder->content($value); $this->_encoder->endTag(); // end self::SETTINGS_SMTPADDRESS } $this->_encoder->endTag(); // SETTINGS_EMAILADDRESSES } $this->_encoder->endTag(); // SETTINGS_ACCOUNT } $this->_encoder->endTag(); // SETTINGS_ACCOUNTS } else { $this->_encoder->startTag(self::SETTINGS_EMAILADDRESSES); if (!empty($result['get']['userinformation']['emailaddresses'])) { foreach($result['get']['userinformation']['emailaddresses'] as $value) { $this->_encoder->startTag(self::SETTINGS_SMTPADDRESS); $this->_encoder->content($value); $this->_encoder->endTag(); // end self::SETTINGS_SMTPADDRESS } } $this->_encoder->endTag(); // end self::SETTINGS_EMAILADDRESSES } $this->_encoder->endTag(); // end self::SETTINGS_GET $this->_encoder->endTag(); // end self::SETTINGS_USERINFORMATION } if (isset($request['get']['oof'])) { $oof = $this->_getOofObject($result['get']['oof']); $this->_encoder->startTag(self::SETTINGS_OOF); $this->_encoder->startTag(self::SETTINGS_STATUS); $this->_encoder->content($result['get']['oof']['status']); $this->_encoder->endTag(); // end self::SETTINGS_STATUS if ($result['get']['oof']['status'] == self::STATUS_SUCCESS) { $this->_encoder->startTag(self::SETTINGS_GET); $oof->encodeStream($this->_encoder); $this->_encoder->endTag(); // end self::SETTINGS_GET } $this->_encoder->endTag(); $this->_encoder->endTag(); } if (isset($request['get']['rightsmanagementinfo'])) { $this->_encoder->startTag(self::SETTINGS_RIGHTSMANAGEMENTINFO); $this->_encoder->startTag(self::SETTINGS_STATUS); $this->_encoder->content(self::STATUS_SUCCESS); $this->_encoder->endTag(); $this->_encoder->endTag(); } $this->_encoder->endTag(); // end self::SETTINGS_SETTINGS return true; } /** * @todo remove for H6 when driver methods always return EAS objects. */ protected function _getOofObject($info) { $info = new Horde_Support_Array($info); $oof = Horde_ActiveSync::messageFactory('Oof'); $oof->state = $info['oofstate']; $oof->starttime = new Horde_Date($info['starttime']); $oof->endtime = new Horde_Date($info['endtime']); $msg = Horde_ActiveSync::messageFactory('OofMessage'); $msg->internal = ''; $msg->enabled = $info['oofmsgs'][0]['enabled']; $msg->reply = $info['oofmsgs'][0]['replymessage']; $msg->bodytype = 'text'; $oof->messages[] = $msg; return $oof; } }Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Request/SmartForward.php0000664000076500000240000000553012654565405022361 0ustar * @package ActiveSync */ /** * ActiveSync Handler for SmartForward requests. The device only sends the reply * text, along with the message uid and collection id (mailbox). The server is * responsible for appending the original text. * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2009-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync * @internal */ class Horde_ActiveSync_Request_SmartForward extends Horde_ActiveSync_Request_SendMail { /** * Handle request * * @return boolean */ protected function _handle() { if ($this->_decoder->isWbxml()) { return $this->_handleWbxmlRequest(); } $rfc822 = file_get_contents('php://input'); $get = $this->_activeSync->getGetVars(); if (empty($get['ItemId'])) { $orig = false; } else { $orig = $get['ItemId']; } if (empty($get['CollectionId'])) { $parent = false; } else { $parent = $this->_activeSync->getCollectionsObject() ->getBackendIdForFolderUid($get['CollectionId']); } try { return $this->_driver->sendMail($rfc822, $orig, false, $parent); } catch (Horde_Exception_NotFound $e) { $this->_logger->err($e->getMessage()); $this->_handleError( Horde_ActiveSync_Status::ITEM_NOT_FOUND, Horde_ActiveSync_Message_SendMail::COMPOSEMAIL_SMARTFORWARD); } catch (Horde_ActiveSync_Exception $e) { $this->_handleError( Horde_ActiveSync_Status::MAIL_SUBMISSION_FAILED, Horde_ActiveSync_Message_SendMail::COMPOSEMAIL_SMARTFORWARD); } return true; } }Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Request/SmartReply.php0000664000076500000240000000564012654565405022052 0ustar * @package ActiveSync */ /** * ActiveSync Handler for SmartReply requests. The device only sends the reply * text, along with the message uid and collection id (mailbox). The server is * responsible for appending the original text. * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2012-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync * @internal */ class Horde_ActiveSync_Request_SmartReply extends Horde_ActiveSync_Request_SendMail { /** * Handle request * * @return boolean */ protected function _handle() { if ($this->_decoder->isWbxml()) { return $this->_handleWbxmlRequest(); } // Smart reply should add the original message to the end of the message body $rfc822 = file_get_contents('php://input'); $get = $this->_activeSync->getGetVars(); if (empty($get['ItemId'])) { $orig = false; } else { $orig = $get['ItemId']; } if (empty($get['CollectionId'])) { $parent = false; } else { $parent = $this->_activeSync->getCollectionsObject() ->getBackendIdForFolderUid($get['CollectionId']); } try { return $this->_driver->sendMail($rfc822, false, $orig, $parent); } catch (Horde_Exception_NotFound $e) { $this->_logger->err($e->getMessage()); $this->_handleError( Horde_ActiveSync_Status::ITEM_NOT_FOUND, Horde_ActiveSync_Message_SendMail::COMPOSEMAIL_SMARTREPLY); } catch (Horde_ActiveSync_Exception $e) { $this->_handleError( Horde_ActiveSync_Status::MAIL_REPLY_FAILED, Horde_ActiveSync_Message_SendMail::COMPOSEMAIL_SMARTREPLY); } return true; } }Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Request/Sync.php0000664000076500000240000017645112654565405020675 0ustar * @package ActiveSync */ /** * Handle Sync requests * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2009-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync * @internal */ class Horde_ActiveSync_Request_Sync extends Horde_ActiveSync_Request_SyncBase { /* Status */ const STATUS_SUCCESS = 1; const STATUS_VERSIONMISM = 2; const STATUS_KEYMISM = 3; const STATUS_PROTERROR = 4; const STATUS_SERVERERROR = 5; const STATUS_INVALID = 6; const STATUS_CONFLICT = 7; const STATUS_NOTFOUND = 8; // 12.1 const STATUS_FOLDERSYNC_REQUIRED = 12; const STATUS_REQUEST_INCOMPLETE = 13; const STATUS_INVALID_WAIT_HEARTBEAT = 14; /* Maximum window size (12.1 only) */ const MAX_WINDOW_SIZE = 512; /* Maximum HEARTBEAT value (seconds) (12.1 only) */ const MAX_HEARTBEAT = 3540; /** * Collections manager. * * @var Horde_ActiveSync_Collections */ protected $_collections; /** * Handle the sync request * * @return boolean * @throws Horde_ActiveSync_Exception */ protected function _handle() { $this->_logger->info(sprintf( '[%s] Handling SYNC command.', $this->_procid) ); // Check policy if (!$this->checkPolicyKey($this->_activeSync->getPolicyKey(), Horde_ActiveSync::SYNC_SYNCHRONIZE)) { return true; } // Check global errors. if ($error = $this->_activeSync->checkGlobalError()) { $this->_statusCode = $error; $this->_handleGlobalSyncError(); return true; } // Defaults $this->_statusCode = self::STATUS_SUCCESS; $partial = false; try { $this->_collections = $this->_activeSync->getCollectionsObject(); } catch (Horde_ActiveSync_Exception $e) { $this->_statusCode = self::STATUS_SERVERERROR; $this->_handleGlobalSyncError(); return true; } // Sanity check if ($this->_device->version >= Horde_ActiveSync::VERSION_TWELVEONE) { // We don't have a previous FOLDERSYNC. if (!$this->_collections->haveHierarchy()) { $this->_logger->notice(sprintf( '[%s] No HIERARCHY SYNCKEY in sync_cache, invalidating.', $this->_procid)); $this->_statusCode = self::STATUS_FOLDERSYNC_REQUIRED; $this->_handleGlobalSyncError(); return true; } } // Start decoding request if (!$this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_SYNCHRONIZE)) { if ($this->_device->version >= Horde_ActiveSync::VERSION_TWELVEONE) { $this->_logger->info(sprintf( '[%s] Empty Sync request, taking info from SyncCache.', $this->_procid)); if ($this->_collections->cachedCollectionCount() == 0) { $this->_logger->warn(sprintf( '[%s] Empty SYNC request but no SyncCache or SyncCache with no collections.', $this->_procid)); $this->_statusCode = self::STATUS_REQUEST_INCOMPLETE; $this->_handleGlobalSyncError(); return true; } else { if (!$this->_collections->initEmptySync()) { $this->_statusCode = self::STATUS_REQUEST_INCOMPLETE; $this->_handleGlobalSyncError(); return true; } } } else { $this->_statusCode = self::STATUS_REQUEST_INCOMPLETE; $this->_handleGlobalSyncError(); $this->_logger->err('Empty Sync request and protocolversion < 12.1'); return true; } } else { // Start decoding request. $this->_collections->hangingSync = false; while (($sync_tag = ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_WINDOWSIZE) ? Horde_ActiveSync::SYNC_WINDOWSIZE : ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_FOLDERS) ? Horde_ActiveSync::SYNC_FOLDERS : ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_PARTIAL) ? Horde_ActiveSync::SYNC_PARTIAL : ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_WAIT) ? Horde_ActiveSync::SYNC_WAIT : ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_HEARTBEATINTERVAL) ? Horde_ActiveSync::SYNC_HEARTBEATINTERVAL : -1)))))) != -1 ) { switch($sync_tag) { case Horde_ActiveSync::SYNC_HEARTBEATINTERVAL: if ($hbinterval = $this->_decoder->getElementContent()) { $this->_collections->setHeartbeat(array('hbinterval' => $hbinterval)); $this->_collections->hangingSync = true; $this->_decoder->getElementEndTag(); if ($hbinterval > (self::MAX_HEARTBEAT)) { $this->_logger->err(sprintf( '[%s] HeartbeatInterval outside of allowed range.', $this->_procid) ); $this->_statusCode = self::STATUS_INVALID_WAIT_HEARTBEATINTERVAL; $this->_handleGlobalSyncError(self::MAX_HEARTBEAT); return true; } } break; case Horde_ActiveSync::SYNC_WAIT: if ($wait = $this->_decoder->getElementContent()) { $this->_collections->setHeartbeat(array('wait' => $wait)); $this->_collections->hangingSync = true; $this->_decoder->getElementEndTag(); if ($wait > (self::MAX_HEARTBEAT / 60)) { $this->_logger->err(sprintf( '[%s] Wait value outside of allowed range.', $this->_procid) ); $this->_statusCode = self::STATUS_INVALID_WAIT_HEARTBEATINTERVAL; $this->_handleGlobalSyncError(self::MAX_HEARBEAT / 60); return true; } } break; case Horde_ActiveSync::SYNC_PARTIAL: if ($this->_decoder->getElementContent(Horde_ActiveSync::SYNC_PARTIAL)) { $this->_decoder->getElementEndTag(); } $partial = true; break; case Horde_ActiveSync::SYNC_WINDOWSIZE: $this->_collections->setDefaultWindowSize($this->_decoder->getElementContent()); if (!$this->_decoder->getElementEndTag()) { $this->_logger->err('PROTOCOL ERROR'); return false; } break; case Horde_ActiveSync::SYNC_FOLDERS: if (!$this->_parseSyncFolders()) { // Any errors are handled in _parseSyncFolders() and // appropriate error codes sent to device. return true; } } } if ($this->_device->version >= Horde_ActiveSync::VERSION_TWELVEONE) { // These are not allowed in the same request. if ($this->_collections->hbinterval !== false && $this->_collections->wait !== false) { $this->_logger->err(sprintf( '[%s] Received both HBINTERVAL and WAIT interval in same request.', $this->_procid)); $this->_statusCode = Horde_ActiveSync_Status::INVALID_XML; $this->_handleGlobalSyncError(); return true; } // Fill in missing sticky data from cache. $this->_collections->validateFromCache(); } // Ensure we have OPTIONS values. $this->_collections->ensureOptions(); // Full or partial sync request? if ($partial === true) { $this->_logger->info(sprintf( '[%s] Executing a PARTIAL SYNC.', $this->_procid)); if (!$this->_collections->initPartialSync()) { $this->_statusCode = self::STATUS_REQUEST_INCOMPLETE; $this->_handleGlobalSyncError(); return true; } // Fill in any missing collections that were already sent. // @TODO: Can we move this to initPartialSync()? $this->_collections->getMissingCollectionsFromCache(); } else { // Full request. $this->_collections->initFullSync(); } // End SYNC tag. if (!$this->_decoder->getElementEndTag()) { $this->_statusCode = self::STATUS_PROTERROR; $this->_handleGlobalSyncError(); $this->_logger->err('PROTOCOL ERROR: Missing closing SYNC tag'); return false; } // We MUST have syncable collections by now. if (!$this->_collections->haveSyncableCollections($this->_device->version)) { $this->_statusCode = self::STATUS_KEYMISM; $this->_handleGlobalSyncError(); return true; } // Update the syncCache with the new collection data. $this->_collections->updateCache(); // Save. $this->_collections->save(true); $this->_logger->info(sprintf( '[%s] All synckeys confirmed. Continuing with SYNC', $this->_procid)); } $pingSettings = $this->_driver->getHeartbeatConfig(); // Override the total, per-request, WINDOWSIZE? if (!empty($pingSettings['maximumrequestwindowsize'])) { $this->_collections->setDefaultWindowSize($pingSettings['maximumrequestwindowsize'], true); } // If this is >= 12.1, see if we want a looping SYNC. if ($this->_collections->canDoLoopingSync() && $this->_device->version >= Horde_ActiveSync::VERSION_TWELVEONE && $this->_statusCode == self::STATUS_SUCCESS) { // Calculate the heartbeat if (!$heartbeat = $this->_collections->getHeartbeat()) { $heartbeat = !empty($pingSettings['heartbeatdefault']) ? $pingSettings['heartbeatdefault'] : 10; } // Wait for changes. $changes = $this->_collections->pollForChanges($heartbeat, $pingSettings['waitinterval']); if ($changes !== true && $changes !== false) { switch ($changes) { case Horde_ActiveSync_Collections::COLLECTION_ERR_STALE: $this->_logger->notice(sprintf( '[%s] Changes in cache detected during looping SYNC exiting here.', $this->_procid)); return true; case Horde_ActiveSync_Collections::COLLECTION_ERR_FOLDERSYNC_REQUIRED; $this->_statusCode = self::STATUS_FOLDERSYNC_REQUIRED; $this->_handleGlobalSyncError(); return true; case Horde_ActiveSync_Collections::COLLECTION_ERR_SYNC_REQUIRED; $this->_statusCode = self::STATUS_REQUEST_INCOMPLETE; $this->_handleGlobalSyncError(); return true; default: $this->_statusCode = self::STATUS_SERVERERROR; $this->_handleGlobalSyncError(); return true; } } } // See if we can do an empty response if ($this->_device->version >= Horde_ActiveSync::VERSION_TWELVEONE && $this->_statusCode == self::STATUS_SUCCESS && empty($changes) && $this->_collections->canSendEmptyResponse()) { $this->_logger->info(sprintf( '[%s] Sending an empty SYNC response.', $this->_procid)); $this->_collections->lastsyncendnormal = time(); $this->_collections->save(true); return true; } $this->_logger->info(sprintf( '[%s] Completed parsing incoming request. Peak memory usage: %d.', $this->_procid, memory_get_peak_usage(true))); // Start output to client $this->_encoder->startWBXML(); $this->_encoder->startTag(Horde_ActiveSync::SYNC_SYNCHRONIZE); $this->_encoder->startTag(Horde_ActiveSync::SYNC_STATUS); $this->_encoder->content(self::STATUS_SUCCESS); $this->_encoder->endTag(); $this->_encoder->startTag(Horde_ActiveSync::SYNC_FOLDERS); $exporter = new Horde_ActiveSync_Connector_Exporter( $this->_activeSync, $this->_encoder); $cnt_global = 0; $over_window = false; foreach ($this->_collections as $id => $collection) { $statusCode = self::STATUS_SUCCESS; $changecount = 0; if ($over_window || $cnt_global > $this->_collections->getDefaultWindowSize()) { $this->_sendOverWindowResponse($collection); continue; } try { $this->_collections->initCollectionState($collection); } catch (Horde_ActiveSync_Exception_StateGone $e) { $this->_logger->notice(sprintf( '[%s] SYNC terminating, state not found', $this->_procid) ); $statusCode = self::STATUS_KEYMISM; } catch (Horde_ActiveSync_Exception_FolderGone $e) { // This isn't strictly correct, but at least some versions of // iOS need this in order to catch missing state. $this->_logger->err($e->getMessage()); $statusCode = self::STATUS_FOLDERSYNC_REQUIRED; } catch (Horde_ActiveSync_Exception_StaleState $e) { $this->_logger->notice($e->getMessage()); return false; } catch (Horde_ActiveSync_Exception $e) { $this->_logger->err($e->getMessage()); return false; } // Outlook explicitly tells the server to NOT check for server // changes when importing client changes, unlike EVERY OTHER client // out there. This completely screws up many things like conflict // detection since we can't update the sync_ts until we actually // check for changes. So, we need to FORCE a change detection cycle // to be sure we don't screw up state. Any detected changes will be // ignored until the next cycle, utilizing the existing mechanism // for sending MOREITEMS when we will return the previously // selected changes. getchanges defaults to true if it is missing // and the synckey != 0, defaults to true if it is present as an // empty tag. If it is present, but '0' or not present but synckey // is 0 then it defaults to false. if (!isset($collection['getchanges']) && $collection['synckey'] != '0') { $collection['getchanges'] = true; } if (!empty($collection['importedchanges']) && (empty($collection['getchanges']))) { $forceChanges = true; $collection['getchanges'] = true; $this->_logger->notice(sprintf( '[%s] Force a GETCHANGES due to incoming changes.', $this->_procid)); } if ($statusCode == self::STATUS_SUCCESS && (!empty($collection['getchanges']) || (!isset($collection['getchanges']) && $collection['synckey'] != '0'))) { try { $changecount = $this->_collections->getCollectionChangeCount(); } catch (Horde_ActiveSync_Exception_StaleState $e) { $this->_logger->err(sprintf( '[%s] Force restting of state for %s: %s', $this->_procid, $id, $e->getMessage())); $this->_state->loadState( array(), null, Horde_ActiveSync::REQUEST_TYPE_SYNC, $id); $statusCode = self::STATUS_KEYMISM; } catch (Horde_ActiveSync_Exception_StateGone $e) { $this->_logger->warn(sprintf( '[%s] SYNCKEY not found. Reset required.', $this->_procid)); $statusCode = self::STATUS_KEYMISM; } catch (Horde_ActiveSync_Exception_FolderGone $e) { $this->_logger->warn(sprintf( '[%s] FOLDERSYNC required, collection gone.', $this->_procid)); $statusCode = self::STATUS_FOLDERSYNC_REQUIRED; } } // Get new synckey if needed. We need a new synckey if there were // any changes (incoming or outgoing), if this is during the // initial sync pairing of the collection, or if we received a // SYNC due to changes found during a PING (since those changes // may be changes to items that never even made it to the client in // the first place (See Bug: 12075). if ($statusCode == self::STATUS_SUCCESS && (!empty($collection['importedchanges']) || !empty($changecount) || $collection['synckey'] == '0' || $this->_state->getSyncKeyCounter($collection['synckey']) == 1 || !empty($collection['fetchids']) || $this->_collections->hasPingChangeFlag($id))) { try { $collection['newsynckey'] = $this->_state->getNewSyncKey($collection['synckey']); $this->_logger->info(sprintf( '[%s] Old SYNCKEY: %s, New SYNCKEY: %s', $this->_procid, $collection['synckey'], $collection['newsynckey']) ); } catch (Horde_ActiveSync_Exception $e) { $statusCode = self::STATUS_KEYMISM; } } $this->_encoder->startTag(Horde_ActiveSync::SYNC_FOLDER); // Not sent in > 12.0 if ($this->_device->version <= Horde_ActiveSync::VERSION_TWELVE) { $this->_encoder->startTag(Horde_ActiveSync::SYNC_FOLDERTYPE); $this->_encoder->content($collection['class']); $this->_encoder->endTag(); } $this->_encoder->startTag(Horde_ActiveSync::SYNC_SYNCKEY); if (!empty($collection['newsynckey'])) { $this->_encoder->content($collection['newsynckey']); } else { $this->_encoder->content($collection['synckey']); } $this->_encoder->endTag(); $this->_encoder->startTag(Horde_ActiveSync::SYNC_FOLDERID); $this->_encoder->content($collection['id']); $this->_encoder->endTag(); $this->_encoder->startTag(Horde_ActiveSync::SYNC_STATUS); $this->_encoder->content($statusCode); $this->_encoder->endTag(); if ($statusCode == self::STATUS_SUCCESS) { // Send server changes to client if ($statusCode == self::STATUS_SUCCESS && empty($forceChanges) && (!empty($collection['getchanges']) || (!isset($collection['getchanges']) && !empty($collection['synckey'])))) { $max_windowsize = !empty($pingSettings['maximumwindowsize']) ? min($collection['windowsize'], $pingSettings['maximumwindowsize']) : $collection['windowsize']; if (!empty($changecount) && (($changecount > $max_windowsize) || $cnt_global + $max_windowsize > $this->_collections->getDefaultWindowSize())) { $this->_logger->info(sprintf( '[%s] Sending MOREAVAILABLE. WINDOWSIZE = %d, $changecount = %d, MAX_REQUEST_WINDOWSIZE = %d, $cnt_global = %d', $this->_procid, $max_windowsize, $changecount, $this->_collections->getDefaultWindowSize(), $cnt_global)); $this->_encoder->startTag(Horde_ActiveSync::SYNC_MOREAVAILABLE, false, true); $over_window = ($cnt_global + $max_windowsize > $this->_collections->getDefaultWindowSize()); } if (!empty($changecount)) { $exporter->setChanges($this->_collections->getCollectionChanges(false), $collection); $this->_encoder->startTag(Horde_ActiveSync::SYNC_COMMANDS); $cnt_collection = 0; while ($cnt_collection < $max_windowsize && $cnt_global < $this->_collections->getDefaultWindowSize() && $progress = $exporter->sendNextChange()) { $this->_logger->info(sprintf( '[%s] Peak memory usage after message: %d', $this->_procid, memory_get_peak_usage(true))); if ($progress === true) { ++$cnt_collection; ++$cnt_global; } } $this->_encoder->endTag(); } } if (!empty($collection['clientids']) || !empty($collection['fetchids']) || !empty($collection['missing']) || !empty($collection['importfailures'])) { $this->_encoder->startTag(Horde_ActiveSync::SYNC_REPLIES); // Output any SYNC_MODIFY failures if (!empty($collection['importfailures'])) { foreach ($collection['importfailures'] as $id => $reason) { $this->_encoder->startTag(Horde_ActiveSync::SYNC_MODIFY); $this->_encoder->startTag(Horde_ActiveSync::SYNC_FOLDERTYPE); $this->_encoder->content($collection['class']); $this->_encoder->endTag(); $this->_encoder->startTag(Horde_ActiveSync::SYNC_SERVERENTRYID); $this->_encoder->content($id); $this->_encoder->endTag(); $this->_encoder->startTag(Horde_ActiveSync::SYNC_STATUS); $this->_encoder->content($reason); $this->_encoder->endTag(); $this->_encoder->endTag(); } } // Output server IDs for new items we received and added from client if (!empty($collection['clientids'])) { foreach ($collection['clientids'] as $clientid => $serverid) { if ($serverid) { $status = self::STATUS_SUCCESS; } else { $status = self::STATUS_INVALID; } $this->_encoder->startTag(Horde_ActiveSync::SYNC_ADD); // If we have clientids and a CLASS_EMAIL, this is // a SMS response. if ($collection['class'] == Horde_ActiveSync::CLASS_EMAIL) { $this->_encoder->startTag(Horde_ActiveSync::SYNC_FOLDERTYPE); $this->_encoder->content(Horde_ActiveSync::CLASS_SMS); $this->_encoder->endTag(); } $this->_encoder->startTag(Horde_ActiveSync::SYNC_CLIENTENTRYID); $this->_encoder->content($clientid); $this->_encoder->endTag(); if ($status == self::STATUS_SUCCESS) { $this->_encoder->startTag(Horde_ActiveSync::SYNC_SERVERENTRYID); $this->_encoder->content($serverid); $this->_encoder->endTag(); } $this->_encoder->startTag(Horde_ActiveSync::SYNC_STATUS); $this->_encoder->content($status); $this->_encoder->endTag(); $this->_encoder->endTag(); } } // Output any errors from missing messages in REMOVE requests. if (!empty($collection['missing'])) { foreach ($collection['missing'] as $uid) { $this->_encoder->startTag(Horde_ActiveSync::SYNC_REMOVE); $this->_encoder->startTag(Horde_ActiveSync::SYNC_CLIENTENTRYID); $this->_encoder->content($uid); $this->_encoder->endTag(); $this->_encoder->startTag(Horde_ActiveSync::SYNC_STATUS); $this->_encoder->content(self::STATUS_NOTFOUND); $this->_encoder->endTag(); $this->_encoder->endTag(); } } if (!empty($collection['fetchids'])) { // Output any FETCH requests foreach ($collection['fetchids'] as $fetch_id) { try { $data = $this->_driver->fetch($collection['serverid'], $fetch_id, $collection); $this->_encoder->startTag(Horde_ActiveSync::SYNC_FETCH); $this->_encoder->startTag(Horde_ActiveSync::SYNC_SERVERENTRYID); $this->_encoder->content($fetch_id); $this->_encoder->endTag(); $this->_encoder->startTag(Horde_ActiveSync::SYNC_STATUS); $this->_encoder->content(self::STATUS_SUCCESS); $this->_encoder->endTag(); $this->_encoder->startTag(Horde_ActiveSync::SYNC_DATA); $data->encodeStream($this->_encoder); $this->_encoder->endTag(); $this->_encoder->endTag(); } catch (Horde_Exception_NotFound $e) { $this->_logger->err(sprintf( '[%s] Unable to fetch %s', $this->_procid, $fetch_id) ); $this->_encoder->startTag(Horde_ActiveSync::SYNC_FETCH); $this->_encoder->startTag(Horde_ActiveSync::SYNC_SERVERENTRYID); $this->_encoder->content($fetch_id); $this->_encoder->endTag(); $this->_encoder->startTag(Horde_ActiveSync::SYNC_STATUS); $this->_encoder->content(self::STATUS_NOTFOUND); $this->_encoder->endTag(); $this->_encoder->endTag(); } } } $this->_encoder->endTag(); } // Save the sync state for the next time if (!empty($collection['newsynckey'])) { if (!empty($sync) || !empty($importer) || $collection['synckey'] == 0) { $this->_state->setNewSyncKey($collection['newsynckey']); $this->_state->save(); } else { $this->_logger->err(sprintf( '[%s] Error saving %s - no state information available.', $this->_procid, $collection['newsynckey']) ); } // Do we need to add the new synckey to the syncCache? if ($collection['newsynckey'] != $collection['synckey']) { $this->_collections->addConfirmedKey($collection['newsynckey']); } $this->_collections->updateCollection( $collection, array('newsynckey' => true, 'unsetChanges' => true, 'unsetPingChangeFlag' => true) ); } } $this->_encoder->endTag(); $this->_logger->info(sprintf( '[%s] Collection output peak memory usage: %d', $this->_procid, memory_get_peak_usage(true))); } $this->_encoder->endTag(); $this->_encoder->endTag(); if ($this->_device->version >= Horde_ActiveSync::VERSION_TWELVEONE) { if ($this->_collections->checkStaleRequest()) { $this->_logger->info(sprintf( '[%s] Changes detected in sync_cache during wait interval, exiting without updating cache.', $this->_procid)); return true; } else { $this->_collections->lastsyncendnormal = time(); $this->_collections->save(true); } } else { $this->_collections->save(true); } return true; } protected function _sendOverWindowResponse($collection) { $this->_logger->info(sprintf( '[%s] Over window maximum, skip polling for this request.', $this->_procid)); $this->_encoder->startTag(Horde_ActiveSync::SYNC_FOLDER); // Not sent in > 12.0 if ($this->_device->version <= Horde_ActiveSync::VERSION_TWELVE) { $this->_encoder->startTag(Horde_ActiveSync::SYNC_FOLDERTYPE); $this->_encoder->content($collection['class']); $this->_encoder->endTag(); } $this->_encoder->startTag(Horde_ActiveSync::SYNC_SYNCKEY); $this->_encoder->content($collection['synckey']); $this->_encoder->endTag(); $this->_encoder->startTag(Horde_ActiveSync::SYNC_FOLDERID); $this->_encoder->content($collection['id']); $this->_encoder->endTag(); $this->_encoder->startTag(Horde_ActiveSync::SYNC_STATUS); $this->_encoder->content(self::STATUS_SUCCESS); //?? $this->_encoder->endTag(); $this->_encoder->startTag(Horde_ActiveSync::SYNC_MOREAVAILABLE, false, true); $this->_encoder->endTag(); return; } /** * Helper method for parsing incoming SYNC_FOLDERS nodes. * */ protected function _parseSyncFolders() { while ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_FOLDER)) { $collection = $this->_collections->getNewCollection(); while (($folder_tag = ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_FOLDERTYPE) ? Horde_ActiveSync::SYNC_FOLDERTYPE : ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_SYNCKEY) ? Horde_ActiveSync::SYNC_SYNCKEY : ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_FOLDERID) ? Horde_ActiveSync::SYNC_FOLDERID : ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_SUPPORTED) ? Horde_ActiveSync::SYNC_SUPPORTED : ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_DELETESASMOVES) ? Horde_ActiveSync::SYNC_DELETESASMOVES : ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_GETCHANGES) ? Horde_ActiveSync::SYNC_GETCHANGES : ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_WINDOWSIZE) ? Horde_ActiveSync::SYNC_WINDOWSIZE : ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_CONVERSATIONMODE) ? Horde_ActiveSync::SYNC_CONVERSATIONMODE : ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_OPTIONS) ? Horde_ActiveSync::SYNC_OPTIONS : ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_COMMANDS) ? Horde_ActiveSync::SYNC_COMMANDS : -1))))))))))) != -1) { switch ($folder_tag) { case Horde_ActiveSync::SYNC_FOLDERTYPE: // According to docs, in 12.1 this is sent here, in > 12.1 // it is NOT sent here, it is sent in the ADD command ONLY. // BUT, I haven't seen any 12.1 client actually send this. // Only < 12.1 - leave version sniffing out in this case. $collection['class'] = $this->_decoder->getElementContent(); if (!$this->_decoder->getElementEndTag()) { throw new Horde_ActiveSync_Exception('Protocol error'); } break; case Horde_ActiveSync::SYNC_SYNCKEY: $collection['synckey'] = $this->_decoder->getElementContent(); if (!$this->_decoder->getElementEndTag()) { throw new Horde_ActiveSync_Exception('Protocol error'); } break; case Horde_ActiveSync::SYNC_FOLDERID: $collection['id'] = $this->_decoder->getElementContent(); if ($collection['id'] === false) { // Log this case explicitly since we can't send back // a protocol error status (the response requires a // collection id and we obviously don't have one). $this->_logger->err(sprintf( '[%s] PROTOCOL ERROR. Client sent an empty SYNC_FOLDERID value.', $this->_procid)); throw new Horde_ActiveSync_Exception('Protocol error'); } if (!$this->_decoder->getElementEndTag()) { throw new Horde_ActiveSync_Exception('Protocol error'); } break; case Horde_ActiveSync::SYNC_WINDOWSIZE: $collection['windowsize'] = $this->_decoder->getElementContent(); if (!$this->_decoder->getElementEndTag()) { $this->_statusCode = self::STATUS_PROTERROR; $this->_handleError($collection); return false; } if ($collection['windowsize'] < 1 || $collection['windowsize'] > self::MAX_WINDOW_SIZE) { $this->_logger->err(sprintf( '[%s] Bad windowsize sent, defaulting to 512', $this->_procid)); $collection['windowsize'] = self::MAX_WINDOW_SIZE; } break; case Horde_ActiveSync::SYNC_CONVERSATIONMODE: // Optional element, but if it's present with an empty value // it defaults to true. $collection['conversationmode'] = $this->_decoder->getElementContent(); if ($collection['conversationmode'] !== false && !$this->_decoder->getElementEndTag()) { throw new Horde_ActiveSync_Exception('Protocol Error'); } elseif ($collection['conversationmode'] === false) { $collection['conversationmode'] = true; } break; case Horde_ActiveSync::SYNC_SUPPORTED: // Only allowed on initial sync request if ($collection['synckey'] != '0') { $this->_statusCode = self::STATUS_PROTERROR; $this->_handleError($collection); return false; } while (1) { $el = $this->_decoder->getElement(); if ($this->_decoder->isEmptyElement($this->_decoder->getLastStartElement())) { // MS-ASCMD 2.2.3.168 An empty SUPPORTED tag // indicates that ALL elements able to be ghosted // ARE ghosted. $collection['supported'] = array(Horde_ActiveSync::ALL_GHOSTED); break; } if ($el[Horde_ActiveSync_Wbxml::EN_TYPE] == Horde_ActiveSync_Wbxml::EN_TYPE_ENDTAG) { break; } $collection['supported'][] = $el[2]; } if (!empty($collection['supported'])) { // Initial sync and we have SUPPORTED data - save it if (empty($this->_device->supported)) { $this->_device->supported = array(); } // Not all clients send the $collection['class'] in more // recent EAS versions. Grab it from the collection // handler if needed. if (empty($collection['class'])) { $collection['class'] = $this->_collections->getCollectionClass($collection['id']); } $this->_device->supported[$collection['class']] = $collection['supported']; $this->_device->save(); } break; case Horde_ActiveSync::SYNC_DELETESASMOVES: // Optional element, but if it's present with an empty value // it defaults to true. $collection['deletesasmoves'] = $this->_decoder->getElementContent(); if ($collection['deletesasmoves'] !== false && !$this->_decoder->getElementEndTag()) { throw new Horde_ActiveSync_Exception('Protocol Error'); } elseif ($collection['deletesasmoves'] === false) { $collection['deletesasmoves'] = true; } break; case Horde_ActiveSync::SYNC_GETCHANGES: // Optional element, but if it's present with an empty value // it defaults to true. Also, not sent by EAS 14 $collection['getchanges'] = $this->_decoder->getElementContent(); if ($collection['getchanges'] !== false && !$this->_decoder->getElementEndTag()) { throw new Horde_ActiveSync_Exception('Protocol Error'); } elseif ($collection['getchanges'] === false) { $collection['getchanges'] = true; } break; case Horde_ActiveSync::SYNC_OPTIONS: if (!$this->_decoder->isEmptyElement($this->_decoder->getLastStartElement())) { $this->_parseSyncOptions($collection); } break; case Horde_ActiveSync::SYNC_COMMANDS: if (!$this->_parseSyncCommands($collection)) { return false; } } } if (!$this->_decoder->getElementEndTag()) { $this->_statusCode = self::STATUS_PROTERROR; $this->_handleError($collection); return false; } if (isset($collection['filtertype']) && !$this->_collections->checkFilterType($collection['id'], $collection['filtertype'])) { $this->_logger->info(sprintf( '[%s] Found updated filtertype, will force a SOFTDELETE.', $this->_procid)); $collection['forcerefresh'] = true; } try { $this->_collections->addCollection($collection); } catch (Horde_ActiveSync_Exception_StateGone $e) { $this->_statusCode = self::STATUS_FOLDERSYNC_REQUIRED; $this->_handleError($collection); return false; } catch (Horde_ActiveSync_Exception $e) { $this->_statusCode = self::STATUS_SERVERERROR; $this->_handleError($collection); return false; } if (!empty($collection['importedchanges'])) { $this->_collections->importedChanges = true; } if ($this->_collections->collectionExists($collection['id']) && !empty($collection['windowsize'])) { $this->_collections->updateWindowSize($collection['id'], $collection['windowsize']); } } if (!$this->_decoder->getElementEndTag()) { $this->_logger->err('Parsing Error'); return false; } return true; } /** * Handle incoming SYNC nodes * * @param array $collection The current collection array. * * @return boolean */ protected function _parseSyncCommands(&$collection) { // Some broken clients send SYNC_COMMANDS with a synckey of 0. // This is a violation of the spec, and could lead to all kinds // of data integrity issues. if (empty($collection['synckey'])) { $this->_logger->warn(sprintf( '[%s] Attempting a SYNC_COMMANDS, but device failed to send synckey. Ignoring.', $this->_procid)); } try { $this->_collections->initCollectionState($collection); } catch (Horde_ActiveSync_Exception_StateGone $e) { $this->_logger->warn(sprintf( '[%s] State not found sending STATUS_KEYMISM', $this->_procid)); $this->_statusCode = self::STATUS_KEYMISM; $this->_handleError($collection); return false; } catch (Horde_ActiveSync_Exception_StaleState $e) { $this->_logger->notice($e->getMessage()); $this->_statusCode = self::STATUS_SERVERERROR; $this->_handleGlobalSyncError(); return false; } catch (Horde_ActiveSync_Exception $e) { $this->_statusCode = self::STATUS_FOLDERSYNC_REQUIRED; $this->_handleError($colleciton); return false; } catch (Horde_ActiveSync_Exception $e) { $this->_logger->err($e->getMessage()); $this->_statusCode = self::STATUS_SERVERERROR; $this->_handleGlobalSyncError(); return false; } // Configure importer with last state if (!empty($collection['synckey'])) { $importer = $this->_activeSync->getImporter(); $importer->init($this->_state, $collection['id'], $collection['conflict']); } $nchanges = 0; while (1) { // SYNC_MODIFY, SYNC_REMOVE, SYNC_ADD or SYNC_FETCH $element = $this->_decoder->getElement(); if ($element[Horde_ActiveSync_Wbxml::EN_TYPE] != Horde_ActiveSync_Wbxml::EN_TYPE_STARTTAG) { $this->_decoder->_ungetElement($element); break; } $nchanges++; $commandType = $element[Horde_ActiveSync_Wbxml::EN_TAG]; $instanceid = false; // Only sent during SYNC_MODIFY/SYNC_REMOVE/SYNC_FETCH if (($commandType == Horde_ActiveSync::SYNC_MODIFY || $commandType == Horde_ActiveSync::SYNC_REMOVE || $commandType == Horde_ActiveSync::SYNC_FETCH) && $this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_SERVERENTRYID)) { $serverid = $this->_decoder->getElementContent(); // Work around broken clients (Blackberry) that can send empty // $serverid values as a single empty tag. if ($serverid !== false && !$this->_decoder->getElementEndTag()) { $this->_statusCode = self::STATUS_PROTERROR; $this->_handleGlobalSyncError(); $this->_logger->err('Parsing Error - expecting '); return false; } if ($this->_activeSync->device->version >= Horde_ActiveSync::VERSION_SIXTEEN) { if ($this->_decoder->getElementStartTag(Horde_ActiveSync::AIRSYNCBASE_INSTANCEID)) { $instanceid = $this->_decoder->getElementContent(); if ($instanceid !== false && !$this->_decoder->getElementEndTag()) { $this->_statusCode = self::STATUS_PROTERROR; $this->_handleGlobalSyncError(); $this->_logger->err('Parsing Error - expecting '); return false; } } } } else { $serverid = false; } // This tag is only sent here during > 12.1 and SYNC_ADD requests... // and it's not even sent by all clients. Parse it if it's there, // ignore it if not. if ($this->_activeSync->device->version > Horde_ActiveSync::VERSION_TWELVEONE && $commandType == Horde_ActiveSync::SYNC_ADD && $this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_FOLDERTYPE)) { $collection['class'] = $this->_decoder->getElementContent(); if (!$this->_decoder->getElementEndTag()) { $this->_statusCode = self::STATUS_PROTERROR; $this->_handleGlobalSyncError(); $this->_logger->err('Parsing Error - expecting '); return false; } } // Only sent during SYNC_ADD if ($commandType == Horde_ActiveSync::SYNC_ADD && $this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_CLIENTENTRYID)) { $clientid = $this->_decoder->getElementContent(); if (!$this->_decoder->getElementEndTag()) { $this->_statusCode = self::STATUS_PROTERROR; $this->_handleGlobalSyncError(); $this->_logger->err('Parsing Error - expecting '); return false; } } else { $clientid = false; } // Create Message object from messages passed from client. // Only passed during SYNC_ADD or SYNC_MODIFY if (($commandType == Horde_ActiveSync::SYNC_ADD || $commandType == Horde_ActiveSync::SYNC_MODIFY) && $this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_DATA)) { switch ($collection['class']) { case Horde_ActiveSync::CLASS_EMAIL: $appdata = Horde_ActiveSync::messageFactory('Mail'); $appdata->decodeStream($this->_decoder); break; case Horde_ActiveSync::CLASS_CONTACTS: $appdata = Horde_ActiveSync::messageFactory('Contact'); $appdata->decodeStream($this->_decoder); break; case Horde_ActiveSync::CLASS_CALENDAR: $appdata = Horde_ActiveSync::messageFactory('Appointment'); $appdata->decodeStream($this->_decoder); if (!empty($instanceid) && $commandType == Horde_ActiveSync::SYNC_MODIFY) { // EAS 16.0 sends instanceid/serverid for exceptions. $appdata->instanceid = $instanceid; } break; case Horde_ActiveSync::CLASS_TASKS: $appdata = Horde_ActiveSync::messageFactory('Task'); $appdata->decodeStream($this->_decoder); break; case Horde_ActiveSync::CLASS_NOTES: $appdata = Horde_ActiveSync::messageFactory('Note'); $appdata->decodeStream($this->_decoder); break; case Horde_ActiveSync::CLASS_SMS: $appdata = Horde_ActiveSync::messageFactory('Mail'); $appdata->decodeStream($this->_decoder); break; } if (!$this->_decoder->getElementEndTag()) { // End application data $this->_statusCode = self::STATUS_PROTERROR; $this->_handleGlobalSyncError(); return false; } } $appdata->commandType = $commandType; if (!empty($collection['synckey'])) { switch ($commandType) { case Horde_ActiveSync::SYNC_MODIFY: if (isset($appdata)) { $id = $importer->importMessageChange( $serverid, $appdata, $this->_device, false, $collection['class'], $collection['synckey'] ); if ($id && !is_array($id)) { $collection['importedchanges'] = true; } elseif (is_array($id)) { $collection['importfailures'][$id[0]] = $id[1]; } } break; case Horde_ActiveSync::SYNC_ADD: if (isset($appdata)) { $id = $importer->importMessageChange( false, $appdata, $this->_device, $clientid, $collection['class']); if ($clientid && $id && !is_array($id)) { $collection['clientids'][$clientid] = $id; $collection['importedchanges'] = true; } elseif (!$id || is_array($id)) { $collection['clientids'][$clientid] = false; } } break; case Horde_ActiveSync::SYNC_REMOVE: if ($instanceid) { $collection['instanceid_removes'][$serverid] = $instanceid; } elseif ($serverid) { // Work around broken clients that send empty $serverid. $collection['removes'][] = $serverid; } break; case Horde_ActiveSync::SYNC_FETCH: $collection['fetchids'][] = $serverid; break; } } if (!$this->_decoder->getElementEndTag()) { $this->_statusCode = self::STATUS_PROTERROR; $this->_handleGlobalSyncError(); $this->_logger->err('Parsing error'); return false; } } // Do all the SYNC_REMOVE requests at once if (!empty($collection['removes']) && !empty($collection['synckey'])) { if (!empty($collection['deletesasmoves']) && $folderid = $this->_driver->getWasteBasket($collection['class'])) { $results = $importer->importMessageMove($collection['removes'], $folderid); } else { $results = $importer->importMessageDeletion($collection['removes'], $collection['class']); if (is_array($results)) { $results['results'] = $results; $results['missing'] = array_diff($collection['removes'], $results['results']); } } if (!empty($results['missing'])) { $collection['missing'] = $results['missing']; } unset($collection['removes']); $collection['importedchanges'] = true; } // EAS 16.0 instance deletions. if (!empty($collection['instanceid_removes']) && !empty($collection['synckey'])) { foreach ($collection['instanceid_removes'] as $uid => $instanceid) { $importer->importMessageDeletion(array($uid => $instanceid), $collection['class'], true); } unset($collection['instanceid_removes']); } $this->_logger->info(sprintf( '[%s] Processed %d incoming changes', $this->_procid, $nchanges)); if (!$this->_decoder->getElementEndTag()) { // end commands $this->_statusCode = self::STATUS_PROTERROR; $this->_handleGlobalSyncError(); $this->_logger->err('PARSING ERROR'); return false; } return true; } /** * Helper method to handle incoming OPTIONS nodes. * * @param array $collection The current collection array. */ public function _parseSyncOptions(&$collection) { $options = array(); $haveElement = false; // These can be sent in any order. while(1) { if ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_FILTERTYPE)) { $options['filtertype'] = $this->_decoder->getElementContent(); $haveElement = true; if (!$this->_decoder->getElementEndTag()) { $this->_statusCode = self::STATUS_PROTERROR; $this->_handleError($collection); exit; } } // EAS > 12.1 the Collection Class can be part of OPTIONS. if ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_FOLDERTYPE)) { $haveElement = true; $options['class'] = $this->_decoder->getElementContent(); if (!$this->_decoder->getElementEndTag()) { $this->_statusCode = self::STATUS_PROTERROR; $this->_handleError($collection); exit; } } if ($this->_decoder->getElementStartTag(Horde_ActiveSync::AIRSYNCBASE_BODYPREFERENCE)) { $this->_bodyPrefs($options); } if ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_CONFLICT)) { $haveElement = true; $options['conflict'] = $this->_decoder->getElementContent(); if (!$this->_decoder->getElementEndTag()) { $this->_statusCode = self::STATUS_PROTERROR; $this->_handleError; exit; } } if ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_MIMESUPPORT)) { $haveElement = true; $this->_mimeSupport($options); } // SYNC_MIMETRUNCATION is used when no SYNC_BODYPREFS element is sent. if ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_MIMETRUNCATION)) { $haveElement = true; $options['mimetruncation'] = Horde_ActiveSync::getMIMETruncSize($this->_decoder->getElementContent()); if (!$this->_decoder->getElementEndTag()) { $this->_statusCode = self::STATUS_PROTERROR; $this->_handleError($collection); exit; } } // SYNC_TRUNCATION only applies to the body of non-email collections // or the BODY element of an Email in EAS 2.5. if ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_TRUNCATION)) { $haveElement = true; $options['truncation'] = Horde_ActiveSync::getTruncSize($this->_decoder->getElementContent()); if (!$this->_decoder->getElementEndTag()) { $this->_statusCode = self::STATUS_PROTERROR; $this->_handleError($collection); exit; } } // @todo This seems to no longer be supported by the specs? Probably // a leftover from EAS 1 or 2.0. Remove in H6. if ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_RTFTRUNCATION)) { $haveElement = true; $options['rtftruncation'] = $this->_decoder->getElementContent(); if (!$this->_decoder->getElementEndTag()) { $this->_statusCode = self::STATUS_PROTERROR; $this->_handleError($collection); exit; } } if ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_MAXITEMS)) { $haveElement = true; $options['maxitems'] = $this->_decoder->getElementContent(); if (!$this->_decoder->getElementEndTag()) { $this->_statusCode = self::STATUS_PROTERROR; $this->_handleError($collection); exit; } } // EAS 14.1 if ($this->_device->version >= Horde_ActiveSync::VERSION_FOURTEENONE) { if ($this->_decoder->getElementStartTag(Horde_ActiveSync::RM_SUPPORT)) { $haveElement = true; $this->_rightsManagement($options); } if ($this->_decoder->getElementStartTag(Horde_ActiveSync::AIRSYNCBASE_BODYPARTPREFERENCE)) { $haveElement = true; $this->_bodyPartPrefs($options); } } $e = $this->_decoder->peek(); if ($e[Horde_ActiveSync_Wbxml::EN_TYPE] == Horde_ActiveSync_Wbxml::EN_TYPE_ENDTAG) { $this->_decoder->getElementEndTag(); break; } elseif (!$haveElement) { $depth = 0; while (1) { $e = $this->_decoder->getElement(); if ($e === false) { $this->_logger->err(sprintf('[%s] Unexpected end of stream.', $this->_procid)); $this->_statusCode = self::STATUS_PROTERROR; $this->_handleError($collection); exit; } elseif ($e[Horde_ActiveSync_Wbxml::EN_TYPE] == Horde_ActiveSync_Wbxml::EN_TYPE_STARTTAG) { $depth = $this->_decoder->isEmptyElement($e) ? $depth : $depth + 1; } elseif ($e[Horde_ActiveSync_Wbxml::EN_TYPE] == Horde_ActiveSync_Wbxml::EN_TYPE_ENDTAG) { $depth--; } if ($depth == 0) { break; } } } } // Default to no filter as per the specs. if (!isset($options['filtertype'])) { $options['filtertype'] = '0'; } if (!empty($options['class']) && $options['class'] == 'SMS') { return; } $collection = array_merge($collection, $options); } /** * Helper for sending error status results. * * @param boolean $limit Send the SYNC_LIMIT error if true. */ protected function _handleGlobalSyncError($limit = false) { $this->_encoder->StartWBXML(); $this->_encoder->startTag(Horde_ActiveSync::SYNC_SYNCHRONIZE); $this->_encoder->startTag(Horde_ActiveSync::SYNC_STATUS); $this->_encoder->content($this->_statusCode); $this->_encoder->endTag(); if ($limit !== false) { $this->_encoder->startTag(Horde_ActiveSync::SYNC_LIMIT); $this->_encoder->content($limit); $this->_encoder->endTag(); } $this->_encoder->endTag(); } /** * Helper for handling sync errors * * @param array $collection */ protected function _handleError(array $collection) { $this->_encoder->startWBXML(); $this->_encoder->startTag(Horde_ActiveSync::SYNC_SYNCHRONIZE); $this->_encoder->startTag(Horde_ActiveSync::SYNC_FOLDERS); // Get new synckey if needed if ($this->_statusCode == self::STATUS_KEYMISM || !empty($collection['importedchanges']) || !empty($collection['getchanges']) || $collection['synckey'] == '0') { $collection['newsynckey'] = Horde_ActiveSync_State_Base::getNewSyncKey(($this->_statusCode == self::STATUS_KEYMISM) ? 0 : $collection['synckey']); if ($collection['synckey'] != '0') { $this->_state->removeState(array('synckey' => $collection['synckey'])); } } $this->_encoder->startTag(Horde_ActiveSync::SYNC_FOLDER); $this->_encoder->startTag(Horde_ActiveSync::SYNC_FOLDERTYPE); $this->_encoder->content($collection['class']); $this->_encoder->endTag(); $this->_encoder->startTag(Horde_ActiveSync::SYNC_SYNCKEY); if (!empty($collection['newsynckey'])) { $this->_encoder->content($collection['newsynckey']); } else { $this->_encoder->content($collection['synckey']); } $this->_encoder->endTag(); $this->_encoder->startTag(Horde_ActiveSync::SYNC_FOLDERID); $this->_encoder->content($collection['id']); $this->_encoder->endTag(); $this->_encoder->startTag(Horde_ActiveSync::SYNC_STATUS); $this->_encoder->content($this->_statusCode); $this->_encoder->endTag(); $this->_encoder->endTag(); // Horde_ActiveSync::SYNC_FOLDER $this->_encoder->endTag(); $this->_encoder->endTag(); } } Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Request/SyncBase.php0000664000076500000240000001506212654565405021456 0ustar * @package ActiveSync */ /** * Base class for handling ActiveSync Sync-type requests. * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2010-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync * @internal */ abstract class Horde_ActiveSync_Request_SyncBase extends Horde_ActiveSync_Request_Base { /** * Parse incoming BODYPARTPREFERENCE options. * * @param array $options An array structure to parse the data into. */ protected function _bodyPartPrefs(&$options) { $options['bodypartprefs'] = array(); if ($this->_decoder->getElementStartTag(Horde_ActiveSync::AIRSYNCBASE_TYPE)) { $options['bodypartprefs']['type'] = $this->_decoder->getElementContent(); // MS-ASAIRS 2.2.2.22.3 type MUST be BODYPREF_TYPE_HTML if (!$this->_decoder->getElementEndTag() || $options['bodypartprefs']['type'] != Horde_ActiveSync::BODYPREF_TYPE_HTML) { $this->_statusCode = self::STATUS_PROTERROR; $this->_handleError($options); exit; } } if ($this->_decoder->getElementStartTag(Horde_ActiveSync::AIRSYNCBASE_TRUNCATIONSIZE)) { $options['bodypartprefs']['truncationsize'] = $this->_decoder->getElementContent(); if (!$this->_decoder->getElementEndTag()) { $this->_statusCode = self::STATUS_PROTERROR; $this->_handleError($options); exit; } } if ($this->_decoder->getElementStartTag(Horde_ActiveSync::AIRSYNCBASE_ALLORNONE)) { $options['bodypartprefs']['allornone'] = $this->_decoder->getElementContent(); // MS-ASAIRS 2.2.2.1.1 - MUST be ignored if no trunction // size is set. Note we still must read it if it sent // so reading the wbxml stream does not break. if (empty($options['bodypartprefs']['truncationsize'])) { unset($options['bodypartprefs']['allornone']); } if (!$this->_decoder->getElementEndTag()) { $this->_statusCode = self::STATUS_PROTERROR; $this->_handleError($options); exit; } } if ($this->_decoder->getElementStartTag(Horde_ActiveSync::AIRSYNCBASE_PREVIEW)) { $options['bodypartprefs']['preview'] = $this->_decoder->getElementContent(); // MS-ASAIRS 2.2.2.18.3 - Max size of preview is 255. if (!$this->_decoder->getElementEndTag() || $options['bodypartprefs']['preview'] > 255) { $this->_statusCode = self::STATUS_PROTERROR; $this->_handleError($options); exit; } } if (!$this->_decoder->getElementEndTag()) { $this->_statusCode = self::STATUS_PROTERROR; $this->_handleError($options); exit; } } /** * Parse incoming BODYPREFERENCE options. * * @param array An array structure to parse the values into. */ protected function _bodyPrefs(&$options) { $body_pref = array(); if (empty($options['bodyprefs'])) { $options['bodyprefs'] = array(); } while (1) { if ($this->_decoder->getElementStartTag(Horde_ActiveSync::AIRSYNCBASE_TYPE)) { $body_pref['type'] = $this->_decoder->getElementContent(); if (!$this->_decoder->getElementEndTag()) { $this->_statusCode = self::STATUS_PROTERROR; $this->_handleError($options); exit; } } if ($this->_decoder->getElementStartTag(Horde_ActiveSync::AIRSYNCBASE_TRUNCATIONSIZE)) { $body_pref['truncationsize'] = $this->_decoder->getElementContent(); if (!$this->_decoder->getElementEndTag()) { $this->_statusCode = self::STATUS_PROTERROR; $this->_handleError($options); exit; } } if ($this->_decoder->getElementStartTag(Horde_ActiveSync::AIRSYNCBASE_ALLORNONE)) { $body_pref['allornone'] = $this->_decoder->getElementContent(); if (!$this->_decoder->getElementEndTag()) { $this->_statusCode = self::STATUS_PROTERROR; $this->_handleError($options); exit; } } if ($this->_decoder->getElementStartTag(Horde_ActiveSync::AIRSYNCBASE_PREVIEW)) { $body_pref['preview'] = $this->_decoder->getElementContent(); if (!$this->_decoder->getElementEndTag()) { $this->_statusCode = self::STATUS_PROTERROR; $this->_handleError($options); exit; } } $e = $this->_decoder->peek(); if ($e[Horde_ActiveSync_Wbxml::EN_TYPE] == Horde_ActiveSync_Wbxml::EN_TYPE_ENDTAG) { $this->_decoder->getElementEndTag(); $options['bodyprefs'][$body_pref['type']] = $body_pref; break; } } } protected function _rightsManagement(&$collection) { $collection['rightsmanagement'] = $this->_decoder->getElementContent(); if (!$this->_decoder->getElementEndTag()) { $this->_statusCode = self::STATUS_PROTERROR; $this->_handleError($collection); exit; } } protected function _mimeSupport(&$collection) { $collection['mimesupport'] = $this->_decoder->getElementContent(); if (!$this->_decoder->getElementEndTag()) { $this->_statusCode = self::STATUS_PROTERROR; $this->_handleError($collection); exit; } } } Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Request/ValidateCert.php0000664000076500000240000001603112654565405022313 0ustar * @package ActiveSync */ /** * ActiveSync Handler for ValidateCertificate requests. Responsible for * determining if the presented smime certificate is valid. * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2012-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync * @internal */ class Horde_ActiveSync_Request_ValidateCert extends Horde_ActiveSync_Request_Base { const VALIDATECERT_VALIDATECERT = 'ValidateCert:ValidateCert'; const VALIDATECERT_CERTIFICATES = 'ValidateCert:Certificates'; const VALIDATECERT_CERTIFICATE = 'ValidateCert:Certificate'; const VALIDATECERT_CERTIFICATECHAIN = 'ValidateCert:CertificateChain'; const VALIDATECERT_CHECKCRL = 'ValidateCert:CheckCRL'; const VALIDATECERT_STATUS = 'ValidateCert:Status'; const STATUS_SUCCESS = 1; const STATUS_PROTERR = 2; const STATUS_SIGERR = 3; const STATUS_UNTRUSTED = 4; const STATUS_EXPIRED = 7; const STATUS_PURPOSE_INVALID = 9; const STATUS_MISSING_INFO = 10; const STATUS_UNKNOWN = 17; /** * Handle request * * @return boolean * @throws Horde_ActiveSync_Exception */ protected function _handle() { $this->_logger->info(sprintf( '[%s] Handling ValidateCertificate command.', $this->_device->id) ); if (!$this->_decoder->getElementStartTag(self::VALIDATECERT_VALIDATECERT)) { throw new Horde_ActiveSync_Exception('Protocol Error'); } $certificates = array(); $chain_certificates = array(); while (($field = ($this->_decoder->getElementStartTag(self::VALIDATECERT_CERTIFICATES) ? self::VALIDATECERT_CERTIFICATES : ($this->_decoder->getElementStartTag(self::VALIDATECERT_CERTIFICATECHAIN) ? self::VALIDATECERT_CERTIFICATECHAIN : ($this->_decoder->getElementStartTag(self::VALIDATECERT_CHECKCRL) ? self::VALIDATECERT_CHECKCRL : -1)))) != -1) { if ($field == self::VALIDATECERT_CERTIFICATES) { while ($this->_decoder->getElementStartTag(self::VALIDATECERT_CERTIFICATE)) { $certificates[] = $this->_decoder->getElementContent(); $this->_logger->info('VALIDATE CERT: ' . $certificates[count($certificates) - 1]); if (!$this->_decoder->getElementEndTag()) { throw new Horde_ActiveSync_Exception('Protocol Error'); } } if (!$this->_decoder->getElementEndTag()) { throw new Horde_ActiveSync_Exception('Protocol Error'); } } elseif ($field == self::VALIDATECERT_CERTIFICATECHAIN) { while ($this->_decoder->getElementStartTag(self::VALIDATECERT_CERTIFICATE)) { $chain_certificates[] = $this->_decoder->getElementContent(); $this->_logger->info('CHAIN CERT: ' . $chain_certificates[count($chain_certificates) - 1]); if (!$this->_decoder->getElementEndTag()) { throw new Horde_ActiveSync_Exception('Protocol Error'); } } if (!$this->_decoder->getElementEndTag()) { throw new Horde_ActiveSync_Exception('Protocol Error'); } } elseif ($field == self::VALIDATECERT_CHECKCRL) { $checkcrl = $this->_decoder->getElementContent(); if (!$this->_decoder->getElementEndTag()) { throw new Horde_ActiveSync_Exception('Protocol Error'); } } } $cert_status = array(); foreach ($certificates as $key => $certificate) { $cert_der = base64_decode($certificate); $cert_pem = "-----BEGIN CERTIFICATE-----\n" . chunk_split(base64_encode($cert_der), 64, "\n") . "-----END CERTIFICATE-----\n"; // Parsable? if (!$parsed = openssl_x509_parse($cert_pem, false)) { $cert_status[$key] = self::STATUS_MISSING_INFO; continue; } // Valid times? $now = time(); if ($parsed['validFrom_time_t'] >= $now || $parsed['validTo_time_t'] <= $now) { $cert_status[$key] = self::STATUS_EXPIRED; continue; } // Valid purpose/trusted? // @TODO: CRL support, CHAIN support $result = openssl_x509_checkpurpose($cert_pem, X509_PURPOSE_SMIME_SIGN, array($this->_activeSync->certPath)); if ($result === false) { // @TODO: // checkpurpose returns false if either the purpose is invalid OR // the certificate is untrusted, so we should validate the // trust before we send back any errors. $cert_status[$key] = self::STATUS_PURPOSE_INVALID; } elseif ($results == -1) { // Unspecified error. $cert_status[$key] = self::STATUS_UNKNOWN; } else { // If checkpurpose passes, it's valid AND trusted. $cert_status[$key] = self::STATUS_SUCCESS; } } $this->_encoder->startWBXML(); $this->_encoder->startTag(self::VALIDATECERT_VALIDATECERT); $this->_encoder->startTag(self::VALIDATECERT_STATUS); $this->_encoder->content(1); $this->_encoder->endTag(); foreach ($certificates as $key => $certificate) { $this->_encoder->startTag(self::VALIDATECERT_CERTIFICATE); $this->_encoder->startTag(self::VALIDATECERT_STATUS); $this->_encoder->content($cert_status[$key]); $this->_encoder->endTag(); $this->_encoder->endTag(); } $this->_encoder->endTag(); return true; } } Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/State/Base.php0000664000076500000240000011041612654565405020250 0ustar * @package ActiveSync */ /** * Base class for managing everything related to device state * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2009-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync */ abstract class Horde_ActiveSync_State_Base { /** * Configuration parameters * * @var array */ protected $_params; /** * Caches the current state(s) in memory * * @var mixed Horde_ActiveSync_Folder_Base if request is not a FOLDERSYNC * otherwise an array containing all FOLDERSYNC state. */ protected $_folder; /** * The syncKey for the current request. * * @var string */ protected $_syncKey; /** * The backend driver * * @param Horde_ActiveSync_Driver_Base */ protected $_backend; /** * The process id (used for logging). * * @var integer */ protected $_procid; /** * The timestamp for the last syncKey * * @var timestamp */ protected $_lastSyncStamp = 0; /** * The current sync timestamp * * @var timestamp */ protected $_thisSyncStamp = 0; /** * The collection array for the collection we are currently syncing. * Keys include: * - class: The collection class Contacts, Calendar etc... * - synckey: The current synckey * - newsynckey: The new synckey sent back to the client * - id: Server folder id * - filtertype: Filter * - conflict: Conflicts * - truncation: Truncation * * @var array */ protected $_collection; /** * Logger instance * * @var Horde_Log_Logger */ protected $_logger; /** * Device object. * * @var Horde_ActiveSync_Device */ protected $_deviceInfo; /** * Local cache for changes to *send* to client. * (Will remain null until getChanges() is called) * * @var array */ protected $_changes; /** * The type of request we are handling. * * @var string */ protected $_type; /** * A map of backend folderids to UIDs * * @var array */ protected $_folderUidMap; /** * Const'r * * @param array $params All configuration parameters. */ public function __construct(array $params = array()) { $this->_params = $params; if (empty($params['logger'])) { $this->_logger = new Horde_Log_Logger(new Horde_Log_Handler_Null()); } else { $this->_logger = $params['logger']; } $this->_procid = getmypid(); } /** * Update the $oldKey syncState to $newKey. * * @param string $newKey * * @return void */ public function setNewSyncKey($newKey) { $this->_syncKey = $newKey; } /** * Get the current synckey * * @return string The synkey we last retrieved state for */ public function getCurrentSyncKey() { return $this->_syncKey; } /** * Generate a random 10 digit policy key * * @return unknown */ public function generatePolicyKey() { return mt_rand(1000000000, 9999999999); } /** * Obtain the current policy key, if it exists. * * @param string $devId The device id to obtain policy key for. * * @return integer The current policy key for this device, or 0 if none * exists. */ public function getPolicyKey($devId) { /* See if we have it already */ if (empty($this->_deviceInfo) || $this->_deviceInfo->id != $devId) { throw new Horde_ActiveSync_Exception('Device not loaded.'); } return $this->_deviceInfo->policykey; } /** * Return a device wipe status * * @param string $devId * @param boolean $refresh If true, reload the device's rwstatus flag. * @since 2.31.0 * * @return integer */ public function getDeviceRWStatus($devId, $refresh = false) { /* See if we have it already */ if (empty($this->_deviceInfo) || $this->_deviceInfo->id != $devId) { throw new Horde_ActiveSync_Exception('Device not loaded.'); } /* Should we refresh? */ if ($refresh) { $this->loadDeviceInfo( $this->_deviceInfo->id, $this->_deviceInfo->user, array('force' => true) ); } return $this->_deviceInfo->rwstatus; } /** * Set the backend driver * (should really only be called by a backend object when passing this * object to client code) * * @param Horde_ActiveSync_Driver_Base $backend The backend driver * * @return void */ public function setBackend(Horde_ActiveSync_Driver_Base $backend) { $this->_backend = $backend; } /** * Set the logger instance for this object. * * @param Horde_Log_Logger $logger */ public function setLogger($logger) { $this->_logger = $logger; } /** * Get the number of server changes. * * @return integer */ public function getChangeCount() { if (!isset($this->_changes)) { $this->getChanges(); } return count($this->_changes); } /** * Determines if the server version of the message represented by $stat * conflicts with the client version of the message. For this driver, this is * true whenever $lastSyncTime is older then $stat['mod']. Method is only * called from the Importer during an import of a non-new change from the * client. * * @param array $stat A message stat array * @param string $type The type of change (change, delete, add) * * @return boolean */ public function isConflict($stat, $type) { // $stat == server's message information if ($stat['mod'] > $this->_lastSyncStamp && ($type == Horde_ActiveSync::CHANGE_TYPE_DELETE || $type == Horde_ActiveSync::CHANGE_TYPE_CHANGE)) { // changed here - deleted there // changed here - changed there return true; } return false; } /** * Return an array of known folders. This is essentially the state for a * FOLDERSYNC request. AS uses a seperate synckey for FOLDERSYNC requests * also, so need to treat it as any other collection. * * @return array An array of folder uids. */ public function getKnownFolders() { if (!isset($this->_folder)) { throw new Horde_ActiveSync_Exception('Sync state not loaded'); } $folders = array(); foreach ($this->_folder as $folder) { $folders[] = $folder['id']; } return $folders; } /** * Return the mapping of folder uids to backend folderids. * * @return array An array of backend folderids -> uids. * @since 2.9.0 */ public function getFolderUidToBackendIdMap() { if (!isset($this->_folderUidMap)) { $this->_folderUidMap = array(); $cache = $this->getSyncCache( $this->_deviceInfo->id, $this->_deviceInfo->user, array('folders')); foreach ($cache['folders'] as $id => $folder) { $this->_folderUidMap[$folder['serverid']] = $id; } } return $this->_folderUidMap; } /** * Get a EAS Folder Uid for the given backend server id. * * @param string $serverid The backend server id. E.g., 'INBOX'. * * @return string|boolean The EAS UID for the requested serverid, or false * if it is not found. * @since 2.4.0 * @deprecated Use self::getFolderUidToBackendIdMap() instead. */ public function getFolderUidForBackendId($serverid) { $cache = $this->getSyncCache( $this->_deviceInfo->id, $this->_deviceInfo->user); $folders = $cache['folders']; foreach ($folders as $id => $folder) { if ($folder['serverid'] == $serverid) { $this->_logger->info(sprintf( '[%s] Found serverid for %s: %s', $this->_procid, $serverid, $id)); return $id; } } $this->_logger->info(sprintf( '[%s] No folderid found for %s', $this->_procid, $serverid)); return false; } /** * Get all items that have changed since the last sync time * * @param array $options An options array: * - ping: (boolean) Only detect if there is a change, do not build * any messages. * DEFAULT: false (Build full change array). * * @return array An array of hashes describing each change: * - id: The id of the item being changed. * - type: The type of change. a Horde_ActiveSync::CHANGE_TYPE_* * constant. * - flags: Used to transport email message flags when type is * Horde_ActiveSync::CHANGE_TYPE_FLAGS or set to * Horde_ActiveSync::FLAG_NEWMESSAGE when type is * Horde_ActiveSync::CHANGE_TYPE_CHANGE and the message * represents a new message, as opposed to a change in an * existing message. * - ignore: Set to true when the change should be ignored, and not sent * to the client by the exporter. Usually due to the change * being the result of a client originated change. * * @throws Horde_ActiveSync_Exception_StaleState */ public function getChanges(array $options = array()) { if (!empty($this->_collection['id'])) { // How far back to sync (for those collections that use this) $cutoffdate = self::_getCutOffDate(!empty($this->_collection['filtertype']) ? $this->_collection['filtertype'] : 0); $this->_logger->info(sprintf( '[%s] Initializing message diff engine for %s (%s)', $this->_procid, $this->_collection['id'], $this->_folder->serverid() )); if (!empty($this->_changes)) { $this->_logger->info(sprintf( '[%s] Returning previously found changes.', $this->_procid)); return $this->_changes; } // Get the current syncStamp from the backend. $this->_thisSyncStamp = $this->_backend->getSyncStamp( $this->_folder->serverid(), $this->_lastSyncStamp); if ($this->_thisSyncStamp === false) { throw new Horde_ActiveSync_Exception_StaleState( 'Detecting a change in timestamp or modification sequence. Reseting state.'); } $this->_logger->info(sprintf( '[%s] Using SYNCSTAMP %s for %s.', $this->_procid, $this->_thisSyncStamp, $this->_collection['id'])); // No existing changes, poll the backend $changes = $this->_backend->getServerChanges( $this->_folder, (int)$this->_lastSyncStamp, (int)$this->_thisSyncStamp, $cutoffdate, !empty($options['ping']), $this->_folder->haveInitialSync, !empty($options['maxitems']) ? $options['maxitems'] : 100, !empty($this->_collection['forcerefresh']) ); // Only update the folderstate if we are not PINGing. if (empty($options['ping'])) { $this->_folder->updateState(); } $this->_logger->info(sprintf( '[%s] Found %d message changes in %s.', $this->_procid, count($changes), $this->_collection['id'])); // Check changes for mirrored client chagnes, but only if we KNOW // we have some client changes. $this->_changes = array(); if (count($changes) && $this->_havePIMChanges()) { $this->_logger->info(sprintf( '[%s] Checking for client initiated changes.', $this->_procid)); switch ($this->_collection['class']) { case Horde_ActiveSync::CLASS_EMAIL: // @todo Fix me with a changes object that transparently // deals with different data structure for initial sync. // ...or come up with better solution for dealing with // memory usage. if (!empty($changes) && !is_array($changes[0])) { $this->_changes = $changes; break; } $mailmap = $this->_getMailMapChanges($changes); $flag_map = array( Horde_ActiveSync::CHANGE_TYPE_FLAGS => 'flag change', Horde_ActiveSync::CHANGE_TYPE_DELETE => 'deletion', Horde_ActiveSync::CHANGE_TYPE_CHANGE => 'move' ); $cnt = count($changes); for ($i = 0; $i < $cnt; $i++) { if (!empty($mailmap[$changes[$i]['id']][$changes[$i]['type']])) { // @todo For 3.0, create a Changes and // ChangeFilter classes to abstract out a bunch of // this stuff. (Needs BC breaking changes in // storage/state classes). // // OL2013 is broken and duplicates the destination // email during MOVEITEMS requests (instead it // reassigns the existing email the new UID). Don't // send the ADD command for these changes. if ($changes[$i]['type'] == Horde_ActiveSync::CHANGE_TYPE_CHANGE && $changes[$i]['flags'] == Horde_ActiveSync::FLAG_NEWMESSAGE && $this->_deviceInfo->deviceType != 'WindowsOutlook15') { $this->_changes[] = $changes[$i]; continue; } $this->_logger->info(sprintf( '[%s] Ignoring client initiated %s for %s', $this->_procid, $flag_map[$changes[$i]['type']], $changes[$i]['id'])); $changes[$i]['ignore'] = true; } $this->_changes[] = $changes[$i]; } break; default: $client_timestamps = $this->_getPIMChangeTS($changes); $cnt = count($changes); for ($i = 0; $i < $cnt; $i++) { if (empty($client_timestamps[$changes[$i]['id']])) { $this->_changes[] = $changes[$i]; continue; } if ($changes[$i]['type'] == Horde_ActiveSync::CHANGE_TYPE_DELETE) { // If we have a delete, don't bother stating the message, // the entry should already be deleted on the client. $stat['mod'] = 0; } else { // stat only returns MODIFY times, not deletion times, // so will return (int)0 for ADD or DELETE. $stat = $this->_backend->statMessage($this->_folder->serverid(), $changes[$i]['id']); } if ($client_timestamps[$changes[$i]['id']] >= $stat['mod']) { $this->_logger->info(sprintf( '[%s] Ignoring client initiated change for %s (client TS: %s Stat TS: %s)', $this->_procid, $changes[$i]['id'], $client_timestamps[$changes[$i]['id']], $stat['mod'])); } else { $this->_changes[] = $changes[$i]; } } } } elseif (count($changes)) { $this->_logger->info(sprintf( '[%s] No client changes present, returning all messages.', $this->_procid)); $this->_changes = $changes; } } else { $this->_getFolderChanges(); } return $this->_changes; } /** * Get folder changes. Populates $this->_changes with an array of change * entries each containing 'type', 'id' and possibly 'flags'. */ protected function _getFolderChanges() { $this->_logger->info(sprintf( '[%s] Initializing folder diff engine', $this->_procid)); $folderlist = $this->_backend->getFolderList(); if ($folderlist === false) { return false; } // @TODO Remove in H6. We need to ensure we have 'serverid' in the // returned stat data. foreach ($folderlist as &$folder) { // Either the backend populates this or not. So, if we have it, we // can stop checking. if (!empty($folder['serverid'])) { break; } else { $folder['serverid'] = $folder['id']; } } $this->_changes = $this->_getDiff( (empty($this->_folder) ? array() : $this->_folder), $folderlist); if (!count($this->_changes)) { $this->_logger->info(sprintf( '[%s] No folder changes found.', $this->_procid)); } else { $this->_logger->info(sprintf( '[%s] Found %d folder changes.', $this->_procid, count($this->_changes))); } } /** * Gets the new sync key for a specified sync key. You must save the new * sync state under this sync key when done sync'ing by calling * setNewSyncKey(), then save(). * * @param string $syncKey The old syncKey * * @return string The new synckey * @throws Horde_ActiveSync_Exception */ public static function getNewSyncKey($syncKey) { if (empty($syncKey)) { return '{' . new Horde_Support_Uuid() . '}' . '1'; } else { if (preg_match('/^\{([a-fA-F0-9-]+)\}([0-9]+)$/', $syncKey, $matches)) { $n = $matches[2]; $n++; return '{' . $matches[1] . '}' . $n; } throw new Horde_ActiveSync_Exception('Invalid SyncKey format passed to getNewSyncKey()'); } } /** * Return the counter for the specified syncKey. * * @param string $syncKey The synckey to obtain the counter for. * * @return mixed integer|boolean The increment counter or false if failed. */ public static function getSyncKeyCounter($syncKey) { if (preg_match('/^\{([a-fA-F0-9-]+)\}([0-9]+)$/', $syncKey, $matches)) { $n = $matches[2]; return $n; } return false; } /** * Return the UID portion of a synckey. * * @param string $syncKey The synckey * * @return string The UID. */ public static function getSyncKeyUid($syncKey) { if (preg_match('/^(\{[a-fA-F0-9-]+\})([0-9]+)$/', $syncKey, $matches)) { return $matches[1]; } } /** * Returns the timestamp of the earliest modification time to consider * * @param integer $restrict The time period to restrict to * * @return integer */ protected static function _getCutOffDate($restrict) { // @todo Should just pass the filtertype to the driver instead // of parsing it here, let the driver figure out what to do with it. if ($restrict == Horde_ActiveSync::FILTERTYPE_INCOMPLETETASKS) { return $restrict; } switch($restrict) { case Horde_ActiveSync::FILTERTYPE_1DAY: $back = 86400; break; case Horde_ActiveSync::FILTERTYPE_3DAYS: $back = 259200; break; case Horde_ActiveSync::FILTERTYPE_1WEEK: $back = 604800; break; case Horde_ActiveSync::FILTERTYPE_2WEEKS: $back = 1209600; break; case Horde_ActiveSync::FILTERTYPE_1MONTH: $back = 2419200; break; case Horde_ActiveSync::FILTERTYPE_3MONTHS: $back = 7257600; break; case Horde_ActiveSync::FILTERTYPE_6MONTHS: $back = 14515200; break; default: break; } if (isset($back)) { $date = time() - $back; return $date; } else { return 0; // unlimited } } /** * Helper function that performs the actual diff between client state and * server state FOLDERSYNC arrays. * * @param array $old The client state * @param array $new The current server state * * @return unknown_type */ protected function _getDiff($old, $new) { $changes = array(); // Sort both arrays in the same way by ID usort($old, array(__CLASS__, 'RowCmp')); usort($new, array(__CLASS__, 'RowCmp')); $inew = 0; $iold = 0; // Get changes by comparing our list of folders with // our previous state while (1) { $change = array(); if ($iold >= count($old) || $inew >= count($new)) { break; } // If ids are the same, but mod is different, a folder was // renamed on the client, but the server keeps it's id. if ($old[$iold]['id'] == $new[$inew]['id']) { // Both folders are still available compare mod if ($old[$iold]['mod'] != $new[$inew]['mod']) { $change['type'] = Horde_ActiveSync::CHANGE_TYPE_CHANGE; $change['id'] = $new[$inew]['id']; $change['serverid'] = $new[$inew]['serverid']; $changes[] = $change; } $inew++; $iold++; } else { if ($old[$iold]['id'] > $new[$inew]['id']) { // Messesge in device state has disappeared $change['type'] = Horde_ActiveSync::CHANGE_TYPE_DELETE; $change['id'] = $old[$iold]['id']; $change['serverid'] = $old[$iold]['serverid']; $changes[] = $change; $iold++; } else { // Message in $new is new $change['type'] = Horde_ActiveSync::CHANGE_TYPE_CHANGE; $change['id'] = $new[$inew]['id']; $change['serverid'] = $new[$inew]['serverid']; $changes[] = $change; $inew++; } } } while ($iold < count($old)) { // All data left in _syncstate have been deleted $change['type'] = Horde_ActiveSync::CHANGE_TYPE_DELETE; $change['id'] = $old[$iold]['id']; $change['serverid'] = $old[$iold]['serverid']; $changes[] = $change; $iold++; } // New folders added on server. while ($inew < count($new)) { // All data left in new have been added $change['type'] = Horde_ActiveSync::CHANGE_TYPE_CHANGE; $change['flags'] = Horde_ActiveSync::FLAG_NEWMESSAGE; $change['id'] = $new[$inew]['id']; $change['serverid'] = $new[$inew]['serverid']; $changes[] = $change; $inew++; } return $changes; } /** * Helper function for the _diff method * * @param $a * @param $b * @return unknown_type */ public static function RowCmp($a, $b) { return $a['id'] < $b['id'] ? 1 : -1; } /** * Load and initialize the sync state * * @param array $collection The collection array for the collection, if * a FOLDERSYNC, pass an empty array. * @param string $syncKey The synckey of the state to load. If empty will * force a reset of the state for the class * specified in $id * @param string $type The type of state a * Horde_ActiveSync::REQUEST_TYPE constant. * @param string $id The folder id this state represents. If empty * assumed to be a foldersync state. * * @throws Horde_ActiveSync_Exception, Horde_ActiveSync_Exception_StateGone */ public function loadState(array $collection, $syncKey, $type = null, $id = null) { // Initialize the local members. $this->_collection = $collection; $this->_changes = null; $this->_type = $type; // If this is a FOLDERSYNC, mock the device id. if ($type == Horde_ActiveSync::REQUEST_TYPE_FOLDERSYNC && empty($id)) { $id = Horde_ActiveSync::REQUEST_TYPE_FOLDERSYNC; } // synckey == 0 is an initial sync or reset. if (empty($syncKey)) { $this->_logger->notice(sprintf( '[%s] %s::loadState: clearing folder state.', $this->_procid, __CLASS__)); if ($type == Horde_ActiveSync::REQUEST_TYPE_FOLDERSYNC) { $this->_folder = array(); } else { // Create a new folder object. $this->_folder = ($this->_collection['class'] == Horde_ActiveSync::CLASS_EMAIL) ? new Horde_ActiveSync_Folder_Imap($this->_collection['serverid'], Horde_ActiveSync::CLASS_EMAIL) : ($this->_collection['serverid'] == 'RI' ? new Horde_ActiveSync_Folder_RI('RI', 'RI') : new Horde_ActiveSync_Folder_Collection($this->_collection['serverid'], $this->_collection['class'])); } $this->_syncKey = '0'; $this->_resetDeviceState($id); return; } $this->_logger->info( sprintf('[%s] Loading state for synckey %s', $this->_procid, $syncKey) ); // Check if synckey is allowed if (!preg_match('/^\{([0-9A-Za-z-]+)\}([0-9]+)$/', $syncKey, $matches)) { throw new Horde_ActiveSync_Exception('Invalid sync key'); } $this->_syncKey = $syncKey; // Cleanup older syncstates $this->_gc($syncKey); // Load the state $this->_loadState($type); } /** * Return the most recently seen synckey for the given collection. * * @param string $collection_id The activesync collection id. * * @return string|integer The synckey or 0 if none found. */ public function getLatestSynckeyForCollection($collection_id) { // For now, pull in the raw cache_data. Will change when each bit of // data gets it's own field. $data = $this->getSyncCache($this->_deviceInfo->id, $this->_deviceInfo->user); return !empty($data['collections'][$collection_id]['lastsynckey']) ? $data['collections'][$collection_id]['lastsynckey'] : 0; } /** * Load the state represented by $syncKey from storage. * * @param string $type The type of state a * Horde_ActiveSync::REQUEST_TYPE constant. * * @throws Horde_ActiveSync_Exception, Horde_ActiveSync_Exception_StateGone */ protected function _loadState($type) { throw new Horde_ActiveSync_Exception('Must be implemented in concrete class.'); } /** * Check for the existence of ANY entries in the map table for this device * and user. * * An extra database query for each sync, but the payoff is that we avoid * having to stat every message change we send to the client if there are no * client generated changes for this sync period. * * @return boolean * @throws Horde_ActiveSync_Exception */ protected function _havePIMChanges() { throw new Horde_ActiveSync_Exception('Must be implemented in concrete class.'); } /** * Return all available mailMap changes for the current folder. * * @param array $changes The chagnes array * * @return array An array of hashes, each in the form of * {uid} => array( * Horde_ActiveSync::CHANGE_TYPE_FLAGS => true|false, * Horde_ActiveSync::CHANGE_TYPE_DELETE => true|false * ) */ protected function _getMailMapChanges(array $changes) { throw new Horde_ActiveSync_Exception('Must be implemented in concrete class.'); } /** * Save the current syncstate to storage */ abstract public function save(); /** * Update the state to reflect changes * * @param string $type The type of change (change, delete, flags or * foldersync) * @param array $change A stat/change hash describing the change. * Contains: * - id: The message uid the change applies to * - parent: The parent of the message, normally the folder id. * - flags: If this is a flag change, the state of the read flag. * - mod: The modtime of this change for collections that use it. * * @param integer $origin Flag to indicate the origin of the change: * Horde_ActiveSync::CHANGE_ORIGIN_NA - Not applicapble/not important * Horde_ActiveSync::CHANGE_ORIGIN_PIM - Change originated from client * * @param string $user The current sync user, only needed if change * origin is CHANGE_ORIGIN_PIM * @param string $clientid client clientid sent when adding a new message */ abstract public function updateState( $type, array $change, $origin = Horde_ActiveSync::CHANGE_ORIGIN_NA, $user = null, $clientid = ''); /** * Save a new device policy key to storage. * * @param string $devId The device id * @param integer $key The new policy key */ abstract public function setPolicyKey($devId, $key); /** * Reset ALL device policy keys. Used when server policies have changed * and you want to force ALL devices to pick up the changes. This will * cause all devices that support provisioning to be reprovisioned. * * @throws Horde_ActiveSync_Exception */ abstract public function resetAllPolicyKeys(); /** * Set a new remotewipe status for the device * * @param string $devId The device id. * @param string $status A Horde_ActiveSync::RWSTATUS_* constant. * * @throws Horde_ActiveSync_Exception */ abstract public function setDeviceRWStatus($devId, $status); /** * Obtain the device object. * * @param object $device * @param string $user * @param array $params Additional parameters: * - force: (boolean) If true, reload the device info even if it's * already loaded. Used to refresh values such as device_rwstatus that * may have changed during a long running PING/SYNC. DEFAULT: false. * @since 2.31.0 * * @return Horde_ActiveSync_Device */ abstract public function loadDeviceInfo($device, $user = null, $params = array()); /** * Check that a given device id is known to the server. This is regardless * of Provisioning status. * * @param string $devId The device id to check * @param string $user The device should be owned by this user. * * @return boolean */ abstract public function deviceExists($devId, $user = null); /** * Set new device info * * @param object $data The device information * @param array $dirty Array of dirty properties. @since 2.9.0 */ abstract public function setDeviceInfo(Horde_ActiveSync_Device $data, array $dirty = array()); /** * Set the device's properties as sent by a SETTINGS request. * * @param array $data The device settings * @param string $deviceId The device id. * * @throws Horde_ActiveSync_Exception */ abstract public function setDeviceProperties(array $data, $deviceId); /** * Explicitly remove a state from storage. * * @param array $options An options array containing: * - synckey: (string) Remove only the state associated with this synckey. * - devId: (string) Remove all information for this device. * - user: (string) When removing device info, restrict to removing data * for this user only. * - id: (string) When removing device state, restrict ro removing data * only for this collection. * * @throws Horde_ActiveSyncException */ abstract public function removeState(array $options); /** * List all devices that we know about. * * @return array An array of device hashes * @throws Horde_ActiveSync_Exception */ abstract public function listDevices(); /** * Get the last time the currently loaded device issued a SYNC request. * * @return integer The timestamp of the last sync, regardless of collection * @throws Horde_ActiveSync_Exception */ abstract public function getLastSyncTimestamp(); /** * Return the sync cache. * * @param string $devid The device id. * @param string $user The user id. * @param array $fields An array of fields to return. Default is to return * the full cache. @since 2.9.0 * * @return array The current sync cache for the user/device combination. * @throws Horde_ActiveSync_Exception */ abstract public function getSyncCache($devid, $user, array $fields = null); /** * Save the provided sync_cache. * * @param array $cache The cache to save. * @param string $devid The device id. * @param string $user The userid. * @param array $dirty An array of dirty properties. @since 2.9.0 * * @throws Horde_ActiveSync_Exception */ abstract public function saveSyncCache(array $cache, $devid, $user, array $dirty = null); /** * Delete a complete sync cache * * @param string $devid The device id * @param string $user The user name. * * @throws Horde_ActiveSync_Exception */ abstract public function deleteSyncCache($devid, $user); /** * Check and see that we didn't already see the incoming change from the client. * This would happen e.g., if the client failed to receive the server response * after successfully importing new messages. * * @param string $id The client id sent during message addition. * * @return string The UID for the given clientid, null if none found. * @throws Horde_ActiveSync_Exception */ abstract public function isDuplicatePIMAddition($id); } Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/State/Mongo.php0000664000076500000240000016604012654565405020461 0ustar * @package ActiveSync */ /** * NoSQL based state management. * * Collections used: * - HAS_state: Holds sync state documents. * * - HAS_device: Holds device and device_user info. * - _id: * - device_type: The device's device_type. * - device_agent: The client's user agent string. * - device_rwstatus: The current RW status. * - device_supported: An array of SUPPORTED properties. * - device_properties: An array of device properties. * - device_users: An array of each user with a known account on device * with each entry containing: * - device_user: * - device_policykey: * * - HAS_map: Holds the incoming change (non-mail) map. * - message_uid: The message's server uid. * - sync_modtime: The modtime. * - sync_key: The sync_key in effect when the change was imported. * - sync_devid: The device_id sending the change. * - sync_folderid: The folderid of the collection the change belongs to. * - sync_user: The username. * - sync_clientid: The client's clientid of incoming new items. * - sync_deleted: Flag to indicate change was a deletion. * * - HAS_mailmap: Holds the incoming mail change map. * - message_uid: The message's UID. * - sync_key: The sync_key in effect when the change was imported. * - sync_devid: The device_id sending the change. * - sync_folderid: The folderid of the collection the change belongs to. * - sync_user: The username. * - sync_read: Flag to indicate change is a change in the /seen flag. * - sync_flagged: Flag to indicate change is a change to the flagged status. * - sync_deleted: Flag to indicate change is a message deletion. * * - HAS_cache: Holds the sync cache. * - cache_user: * - cache_devid: * - cache_data: An object containing: * - confirmed_synckeys: Array to hold synckeys for confirmation. * - lasthbsyncstarted: Timestamp of the start of last heartbeat sync. * - lastsyncendnormal: Timestamp of the last successfully ended sync. * - timestamp: Timestamp of cache. * - wait: Current wait interval. * - hbinterval: Current heartbeat interval. * - folders: Array of known folders. * - hierarchy: Current hierarchy key. * - collections: * - pingheartbeat: * * @license http://www.horde.org/licenses/gpl GPLv2 * @copyright 2010-2016 Horde LLC (http://www.horde.org/) * @author Michael J Rubinsky * @link http://pear.horde.org/index.php?package=ActiveSync * @package ActiveSync */ class Horde_ActiveSync_State_Mongo extends Horde_ActiveSync_State_Base implements Horde_Mongo_Collection_Index { /** Collection names **/ const COLLECTION_CACHE = 'HAS_cache'; const COLLECTION_MAILMAP = 'HAS_mailmap'; const COLLECTION_MAP = 'HAS_map'; const COLLECTION_DEVICE = 'HAS_device'; const COLLECTION_STATE = 'HAS_state'; /** Field names **/ const MONGO_ID = '_id'; const CACHE_USER = 'cache_user'; const CACHE_DEVID = 'cache_devid'; const CACHE_DATA = 'cache_data'; const MESSAGE_UID = 'message_uid'; const SYNC_KEY = 'sync_key'; const SYNC_DEVID = 'sync_devid'; const SYNC_FOLDERID = 'sync_folderid'; const SYNC_USER = 'sync_user'; const SYNC_READ = 'sync_read'; const SYNC_FLAGGED = 'sync_flagged'; const SYNC_DELETED = 'sync_deleted'; const SYNC_CHANGED = 'sync_changed'; const SYNC_MODTIME = 'sync_modtime'; const SYNC_CLIENTID = 'sync_clientid'; const SYNC_DATA = 'sync_data'; const SYNC_MOD = 'sync_mod'; const SYNC_PENDING = 'sync_pending'; const SYNC_TIMESTAMP = 'sync_timestamp'; const DEVICE_ID = 'device_id'; const DEVICE_TYPE = 'device_type'; const DEVICE_AGENT = 'device_agent'; const DEVICE_RWSTATUS = 'device_rwstatus'; const DEVICE_SUPPORTED = 'device_supported'; const DEVICE_PROPERTIES = 'device_properties'; const DEVICE_USERS = 'device_users'; const DEVICE_USER = 'device_user'; const DEVICE_USERS_USER = 'users.device_user'; const DEVICE_USERS_POLICYKEY = 'users.device_policykey'; const DEVICE_POLICYKEY = 'device_policykey'; /** * Mongo connection * * @var MongoClient */ protected $_mongo; /** * Mongo database * * @var MongoDB */ protected $_db; /** * Mongo Indexes * * @var array */ protected $_indexes = array( self::COLLECTION_DEVICE => array( 'index_id_user' => array( self::MONGO_ID => 1, self::DEVICE_USERS_USER => 1 ) ), self::COLLECTION_STATE => array( 'index_devid_folderid' => array( self::SYNC_DEVID => 1, self::SYNC_FOLDERID => 1 ) ), self::COLLECTION_MAP => array( 'index_folder_dev_uid_user' => array( self::SYNC_DEVID => 1, self::SYNC_USER => 1, self::SYNC_FOLDERID => 1, self::MESSAGE_UID => 1 ), 'index_dev_user_uid_key' => array( self::SYNC_DEVID => 1, self::SYNC_USER => 1, self::MESSAGE_UID => 1, self::SYNC_KEY => 1, self::SYNC_DELETED => 1, ), 'index_client_user_dev' => array( self::SYNC_CLIENTID => 1, self::SYNC_USER => 1, self::SYNC_DEVID => 1 ) ), self::COLLECTION_MAILMAP => array( 'index_folder_dev_uid_user' => array( self::SYNC_DEVID => 1, self::SYNC_USER => 1, self::SYNC_FOLDERID => 1, self::MESSAGE_UID => 1 ) ), self::COLLECTION_CACHE => array( 'index_dev_user' => array( self::CACHE_DEVID => 1, self::CACHE_USER => 1 ) ) ); protected $_propertyMap = array( 'deviceType' => self::DEVICE_TYPE, 'userAgent' => self::DEVICE_AGENT, 'rwstatus' => self::DEVICE_RWSTATUS, 'supported' => self::DEVICE_SUPPORTED, 'properties' => self::DEVICE_PROPERTIES, 'id' => self::DEVICE_ID ); /** * Const'r * * @param array $params Must contain: * - connection: (Horde_Mongo_Client The Horde_Mongo instance. * * @return Horde_ActiveSync_State_Sql */ public function __construct(array $params = array()) { parent::__construct($params); if (empty($this->_params['connection']) || !($this->_params['connection'] instanceof MongoClient)) { throw new InvalidArgumentException('Missing or invalid connection parameter.'); } $this->_mongo = $params['connection']; $this->_db = $this->_mongo->selectDb(null); } /** * Update the serverid for a given folder uid in the folder's state object. * Needed when a folder is renamed on a client, but the UID must remain the * same. * * @param string $uid The folder UID. * @param string $serverid The new serverid for this uid. * * @throws Horde_ActiveSync_Exception * @since 2.4.0 */ public function updateServerIdInState($uid, $serverid) { $this->_logger->info(sprintf( '[%s] Updating serverid in folder state. Setting %s for %s.', $this->_procid, $serverid, $uid)); $query = array( self::SYNC_DEVID => $this->_deviceInfo->id, self::SYNC_USER => $this->_deviceInfo->user, self::SYNC_FOLDERID => $uid ); try { $cursor = $this->_db->selectCollection(self::COLLECTION_STATE) ->find($query, array(self::SYNC_DATA)); } catch (Exception $e) { $this->_logger->err(sprintf( '[%s] %s', $this->_procid, $e->getMessage()) ); throw new Horde_ActiveSync_Exception($e); } foreach ($cursor as $folder) { $folder = unserialize($folder[self::SYNC_DATA]); $folder->setServerId($serverid); $folder = serialize($folder); try { $this->_db->selectCollection(self::COLLECTION_STATE)->update( $query, array('$set' => array(self::SYNC_DATA => $folder)), array('multiple' => true) ); } catch (Exception $e) { $this->_logger->err(sprintf( '[%s] %s', $this->_procid, $e->getMessage()) ); throw new Horde_ActiveSync_Exception($e); } } } /** * Load the state represented by $syncKey from storage. * * @param string $type The type of state a * Horde_ActiveSync::REQUEST_TYPE constant. * * @throws Horde_ActiveSync_Exception, Horde_ActiveSync_Exception_StateGone */ protected function _loadState($type) { try { $results = $this->_db->selectCollection(self::COLLECTION_STATE)->findOne( array(self::MONGO_ID => $this->_syncKey), array(self::SYNC_DATA, self::SYNC_DEVID, self::SYNC_MOD, self::SYNC_PENDING)); } catch (Exception $e) { $this->_logger->err('Error in loading state from DB: ' . $e->getMessage()); throw new Horde_ActiveSync_Exception($e); } if (empty($results)) { $this->_logger->warn(sprintf( '[%s] Could not find state for synckey %s.', $this->_procid, $this->_syncKey)); throw new Horde_ActiveSync_Exception_StateGone(); } $this->_loadStateFromResults($results, $type); } /** * Actually load the state data into the object from the query results. * * @param array $results The results array from the state query. * @param string $type The type of request we are handling. * * @throws Horde_ActiveSync_Exception_StateGone */ protected function _loadStateFromResults(array $results, $type = Horde_ActiveSync::REQUEST_TYPE_SYNC) { // Load the last known sync time for this collection $this->_lastSyncStamp = !empty($results[self::SYNC_MOD]) ? $results[self::SYNC_MOD] : 0; // Pre-Populate the current sync timestamp in case this is only a // Client -> Server sync. $this->_thisSyncStamp = $this->_lastSyncStamp; // Restore any state or pending changes $data = unserialize($results[self::SYNC_DATA]); $pending = $results[self::SYNC_PENDING]; if ($type == Horde_ActiveSync::REQUEST_TYPE_FOLDERSYNC) { $this->_folder = ($data !== false) ? $data : array(); $this->_logger->info( sprintf('[%s] Loading FOLDERSYNC state containing %d folders.', $this->_procid, count($this->_folder))); } elseif ($type == Horde_ActiveSync::REQUEST_TYPE_SYNC) { $this->_folder = $data; $this->_changes = ($pending !== false) ? $pending : null; if ($this->_changes) { $this->_logger->info( sprintf('[%s] Found %d changes remaining from previous SYNC.', $this->_procid, count($this->_changes))); } } } /** * Save the current state to storage * * @throws Horde_ActiveSync_Exception */ public function save() { // Prepare state and pending data if ($this->_type == Horde_ActiveSync::REQUEST_TYPE_FOLDERSYNC) { $data = (isset($this->_folder) ? serialize($this->_folder) : ''); $pending = ''; } elseif ($this->_type == Horde_ActiveSync::REQUEST_TYPE_SYNC) { $pending = (isset($this->_changes) ? array_values($this->_changes) : ''); $data = (isset($this->_folder) ? serialize($this->_folder) : ''); } else { $pending = ''; $data = ''; } // If we are setting the first synckey iteration, do not save the // timestamp, otherwise we will never get the initial set of data. $document = array( self::MONGO_ID => $this->_syncKey, self::SYNC_KEY => $this->_syncKey, self::SYNC_DATA => $data, self::SYNC_DEVID => $this->_deviceInfo->id, self::SYNC_MOD => (self::getSyncKeyCounter($this->_syncKey) == 1 ? 0 : $this->_thisSyncStamp), self::SYNC_FOLDERID => (!empty($this->_collection['id']) ? $this->_collection['id'] : Horde_ActiveSync::REQUEST_TYPE_FOLDERSYNC), self::SYNC_USER => $this->_deviceInfo->user, self::SYNC_PENDING => $pending, self::SYNC_TIMESTAMP => time() ); $this->_logger->info( sprintf('[%s] Saving state for sync_key %s: %s', $this->_procid, $this->_syncKey, serialize($document)) ); try { $this->_db->selectCollection(self::COLLECTION_STATE)->insert($document); } catch (Exception $e) { // Might exist already if the last sync attempt failed. $this->_logger->notice( sprintf('[%s] Previous request processing for synckey %s failed to be accepted by the client, removing previous state and trying again.', $this->_procid, $this->_syncKey) ); try { $this->_db->selectCollection(self::COLLECTION_STATE)->remove(array(self::MONGO_ID => $this->_syncKey)); $this->_db->selectCollection(self::COLLECTION_STATE)->insert($document); } catch (Exception $e) { throw new Horde_ActiveSync_Exception('Error saving state.'); } } } /** * Update the state to reflect changes * * Notes: If we are importing client changes, need to update the syncMapTable * so we don't mirror back the changes on next sync. If we are exporting * server changes, we need to track which changes have been sent (by * removing them from $this->_changes) so we know which items to send on the * next sync if a MOREAVAILBLE response was needed. If this is being called * from a FOLDERSYNC command, update state accordingly. * * @param string $type The type of change (change, delete, flags or * foldersync) * @param array $change A stat/change hash describing the change. * Contains: * - id: (mixed) The message uid the change applies to. * - serverid: (string) The backend server id for the folder. * - folderuid: (string) The EAS folder UID for the folder. * - parent: (string) The parent of the current folder, if any. * - flags: (array) If this is a flag change, the state of the flags. * - mod: (integer) The modtime of this change. * * @param integer $origin Flag to indicate the origin of the change: * Horde_ActiveSync::CHANGE_ORIGIN_NA - Not applicapble/not important * Horde_ActiveSync::CHANGE_ORIGIN_PIM - Change originated from client * * @param string $user The current sync user, only needed if change * origin is CHANGE_ORIGIN_PIM * @param string $clientid client clientid sent when adding a new message. * * @throws Horde_ActiveSync_Exception */ public function updateState( $type, array $change, $origin = Horde_ActiveSync::CHANGE_ORIGIN_NA, $user = null, $clientid = '') { $this->_logger->info(sprintf( '[%s] Horde_ActiveSync_State_Mongo::updateState(%s, %s, %d, %s, %s)', $this->_procid, $type, serialize($change), $origin, $user, $clientid)); if ($origin == Horde_ActiveSync::CHANGE_ORIGIN_PIM) { if ($this->_type == Horde_ActiveSync::REQUEST_TYPE_FOLDERSYNC) { foreach ($this->_folder as $fi => $state) { if ($state['id'] == $change['id']) { unset($this->_folder[$fi]); break; } } if ($type != Horde_ActiveSync::CHANGE_TYPE_DELETE) { $this->_folder[] = $change; } $this->_folder = array_values($this->_folder); return; } // Some requests like e.g., MOVEITEMS do not include the state // information since there is no SYNCKEY. Attempt to map this from // the $change array. if (empty($this->_collection)) { $this->_collection = array( 'class' => $change['class'], 'id' => $change['folderuid']); } $syncKey = empty($this->_syncKey) ? $this->getLatestSynckeyForCollection($this->_collection['id']) : $this->_syncKey; // This is an incoming change from the client, store it so we // don't mirror it back to device. switch ($this->_collection['class']) { case Horde_ActiveSync::CLASS_EMAIL: if ($type == Horde_ActiveSync::CHANGE_TYPE_CHANGE && isset($change['flags']) && is_array($change['flags']) && !empty($change['flags'])) { $type = Horde_ActiveSync::CHANGE_TYPE_FLAGS; } $document = array( self::MESSAGE_UID => (string)$change['id'], self::SYNC_KEY => $syncKey, self::SYNC_DEVID => $this->_deviceInfo->id, self::SYNC_FOLDERID => $change['serverid'], self::SYNC_USER => $user ); switch ($type) { case Horde_ActiveSync::CHANGE_TYPE_FLAGS: if (isset($change['flags']['read'])) { $document[self::SYNC_READ] = !empty($change['flags']['read']); } else { $document[self::SYNC_FLAGGED] = $flag_value = !empty($change['flags']['flagged']); } break; case Horde_ActiveSync::CHANGE_TYPE_DELETE: $document[self::SYNC_DELETED] = true; break; case Horde_ActiveSync::CHANGE_TYPE_CHANGE: $document[self::SYNC_CHANGED] = true; break; } try { $this->_db->selectCollection(self::COLLECTION_MAILMAP)->insert($document); } catch (Exception $e) { throw Horde_ActiveSync_Exception($e); } break; default: $document = array( self::MESSAGE_UID => $change['id'], self::SYNC_MODTIME => $change['mod'], self::SYNC_KEY => $syncKey, self::SYNC_DEVID => $this->_deviceInfo->id, self::SYNC_FOLDERID => $change['serverid'], self::SYNC_USER => $user, self::SYNC_CLIENTID => $clientid, self::SYNC_DELETED => $type == Horde_ActiveSync::CHANGE_TYPE_DELETE ); try { $this->_db->selectCollection(self::COLLECTION_MAP)->insert($document); } catch (Exception $e) { throw new Horde_ActiveSync_Exception($e); } break; } } else { // We are sending server changes; $this->_changes will contain all // changes so we need to track which ones are sent since not all // may be sent. We need to store the leftovers for sending next // request. foreach ($this->_changes as $key => $value) { if ($value['id'] == $change['id']) { if ($this->_type == Horde_ActiveSync::REQUEST_TYPE_FOLDERSYNC) { foreach ($this->_folder as $fi => $state) { if ($state['id'] == $value['id']) { unset($this->_folder[$fi]); break; } } // Only save what we need. Note that 'mod' is eq to the // folder id, since that is the only thing that can // change in a folder. if ($type != Horde_ActiveSync::CHANGE_TYPE_DELETE) { $folder = $this->_backend->getFolder($value['serverid']); $stat = $this->_backend->statFolder( $value['id'], (empty($value['parent']) ? '0' : $value['parent']), $folder->displayname, $folder->_serverid, $folder->type); $this->_folder[] = $stat; $this->_folder = array_values($this->_folder); } } unset($this->_changes[$key]); break; } } } } /** * Load the device object. * * @param string $devId The device id to obtain * @param string $user The user to retrieve user-specific device info for * @param array $params Additional parameters: * - force: (boolean) If true, reload the device info even if it's * already loaded. Used to refresh values such as device_rwstatus that * may have changed during a long running PING/SYNC. DEFAULT: false. * @since 2.31.0 * * @return Horde_ActiveSync_Device The device object * @throws Horde_ActiveSync_Exception */ public function loadDeviceInfo($devId, $user = null, $params = array()) { // See if we already have this device, for this user loaded if (empty($params['force']) && !empty($this->_deviceInfo) && $this->_deviceInfo->id == $devId && !empty($this->_deviceInfo) && $user == $this->_deviceInfo->user) { return $this->_deviceInfo; } $query = array(self::MONGO_ID => $devId); if (!empty($user)) { $query[self::DEVICE_USERS_USER] = $user; } try { $device_data = $this->_db->selectCollection(self::COLLECTION_DEVICE)->findOne($query); } catch (Exception $e) { $this->_logger->err($e->getMessage()); throw new Horde_ActiveSync_Exception($e); } if (empty($device_data)) { throw new Horde_ActiveSync_Exception('Device not found.'); } $map = array_flip($this->_propertyMap); $device = array(); foreach ($device_data as $field => $data) { if (!empty($map[$field])) { $device[$map[$field]] = $data; } } $device['id'] = $devId; $device['user'] = $user; foreach ($device_data['users'] as $user_entry) { if ($user_entry[self::DEVICE_USER] == $user) { $device['policykey'] = $user_entry[self::DEVICE_POLICYKEY]; break; } } $this->_deviceInfo = new Horde_ActiveSync_Device($this, $device); return $this->_deviceInfo; } /** * Set new device info * * @param Horde_ActiveSync_Device $data The device information * @param array $dirty Array of dirty properties. * @since 2.9.0 * * @throws Horde_ActiveSync_Exception */ public function setDeviceInfo(Horde_ActiveSync_Device $data, array $dirty = array()) { if (count($dirty)) { $device = array(); foreach (array_keys($dirty) as $property) { if (!empty($this->_propertyMap[$property])) { $device[$this->_propertyMap[$property]] = $data->$property; } } $this->_logger->info(sprintf( '[%s] setDeviceInfo saving properties: %s', $this->_procid, serialize($dirty)) ); if (count($device)) { try { $this->_db->selectCollection(self::COLLECTION_DEVICE)->update( array(self::MONGO_ID => $data->id), array('$set' => $device), array('upsert' => true) ); } catch (Exception $e) { $this->_logger->err($e->getMessage()); throw new Horde_ActiveSync_Exception($e); } } if (!empty($dirty['user']) || !empty($dirty['policykey'])) { $user_data = array( self::DEVICE_USER => $data->user, self::DEVICE_POLICYKEY => (string)$data->policykey ); try { $this->_db->selectCollection(self::COLLECTION_DEVICE)->update( array(self::MONGO_ID => $data->id), array('$pull' => array('users' => array(self::DEVICE_USER => $data->user))) ); $this->_db->selectCollection(self::COLLECTION_DEVICE)->update( array(self::MONGO_ID => $data->id), array('$addToSet' => array('users' => $user_data)) ); } catch (Exception $e) { $this->_logger->err($e->getMessage()); throw new Horde_ActiveSync_Exception($e); } } $this->_deviceInfo = $data; } } /** * Set the device's properties as sent by a SETTINGS request. * * @param array $data The device settings * @param string $deviceId The device id. * * @throws Horde_ActiveSync_Exception */ public function setDeviceProperties(array $data, $deviceId) { $query = array(self::MONGO_ID => $deviceId); $update = array( '$set' => array( self::DEVICE_PROPERTIES => $data ) ); try { $this->_db->selectCollection(self::COLLECTION_DEVICE)->update($query, $update, array('upsert' => true)); } catch (Exception $e) { $this->_logger->err($e->getMessage()); throw new Horde_ActiveSync_Exception($e); } } /** * Check that a given device id is known to the server. This is regardless * of Provisioning status. If $user is provided, checks that the device * is attached to the provided username. * * @param string $devId The device id to check. * @param string $user The device should be owned by this user. * * @return integer The numer of device entries found for the give devId, * user combination. I.e., 0 == no device exists. */ public function deviceExists($devId, $user = null) { $query = array(self::MONGO_ID => $devId); if (!empty($user)) { $query[self::DEVICE_USERS_USER] = $user; } try { return $this->_db->selectCollection(self::COLLECTION_DEVICE)->find($query)->limit(1)->count(); } catch (Exception $e) { $this->_logger->err($e->getMessage()); throw new Horde_ActiveSync_Exception($e); } } /** * List all devices that we know about. * * @param string $user The username to list devices for. If empty, will * return all devices. * @param array $filter An array of optional filters where the keys are * field names and the values are values to * prefix-match. * * @return array An array of device hashes * @throws Horde_ActiveSync_Exception */ public function listDevices($user = null, $filter = array()) { $query = array(); if (!empty($user)) { $query[self::DEVICE_USERS_USER] = $user; } $explicit_fields = array(self::DEVICE_ID, self::DEVICE_TYPE, self::DEVICE_AGENT, self::DEVICE_USER); foreach ($filter as $key => $value) { if (in_array($key, $explicit_fields)) { $query[$key] = new MongoRegex("/^$value*/"); } else { $query['device_properties.' . $key] = new MongoRegex("/^$value*/"); } } try { $cursor = $this->_db->selectCollection(self::COLLECTION_DEVICE)->find($query); } catch (Exception $e) { $this->_logger->err($e->getMessage()); throw new Horde_ActiveSync_Exception($e); } $results = array(); foreach ($cursor as $item) { if (!empty($item['users'])) { foreach ($item['users'] as $user) { $device = $item; $device = array_merge ($device, $user); unset($device['users']); $results[] = $device; } } else { $results[] = $item; } } return $results; } /** * Reset ALL device policy keys. Used when server policies have changed * and you want to force ALL devices to pick up the changes. This will * cause all devices that support provisioning to be reprovisioned. * * @throws Horde_ActiveSync_Exception */ public function resetAllPolicyKeys() { // Holy cr*p. Can't believe this can't be done in MongoDB, but // we can't update a field in all subdocuments?! This can be // a very expensive operation in MongoDB with lots of devices. // See https://jira.mongodb.org/browse/SERVER-1243 // try { // $this->_db->selectCollection(self::COLLECTION_DEVICE)->update( // array(), // array('$set' => array(self::DEVICE_USERS_POLICYKEY => 0)), // array('multiple' => true) // ); // } catch (Exception $e) { // $this->_logger->err($e->getMessage()); // throw new Horde_ActiveSync_Exception($e); // } $cursor = $this->_db->selectCollection(self::COLLECTION_DEVICE)->find(array(), array('users')); foreach ($cursor as $row) { foreach ($row['users'] as $user) { $this->_db->selectCollection(self::COLLECTION_DEVICE)->update( array(self::DEVICE_USERS_USER => $user[self::DEVICE_USER]), array('$set' => array('users.$.device_policykey' => 0)), array('multiple' => true) ); } } } /** * Set a new remotewipe status for the device * * @param string $devId The device id. * @param string $status A Horde_ActiveSync::RWSTATUS_* constant. * * @throws Horde_ActiveSync_Exception */ public function setDeviceRWStatus($devId, $status) { $query = array(self::MONGO_ID => $devId); $new_data = array(self::DEVICE_RWSTATUS => $status); $update = array('$set' => $new_data); try { $this->_db->selectCollection(self::COLLECTION_DEVICE)->update($query, $update); } catch (Exception $e) { $this->_logger->err($e->getMessage()); throw new Horde_ActiveSync_Exception($e); } if ($status == Horde_ActiveSync::RWSTATUS_PENDING) { $new_data[self::DEVICE_USERS_POLICYKEY] = 0; $cursor = $this->_db->selectCollection(self::COLLECTION_DEVICE)->find($query, array('users')); try { foreach ($cursor as $row) { foreach ($row['users'] as $user) { $this->_db->selectCollection(self::COLLECTION_DEVICE)->update( array(self::DEVICE_USERS_USER => $user[self::DEVICE_USER]), array('$set' => array('users.$.device_policykey' => 0)), array('multiple' => true) ); } } } catch (Exception $e) { $this->_logger->err($e->getMessage()); throw new Horde_ActiveSync_Exception($e); } } } /** * Reset the sync state for this device, for the specified collection. * * @param string $id The collection to reset. * * @return void * @throws Horde_ActiveSync_Exception */ protected function _resetDeviceState($id) { $this->_logger->info(sprintf( '[%s] Resetting device state for device: %s, user: %s, and collection: %s.', $this->_procid, $this->_deviceInfo->id, $this->_deviceInfo->user, $id)); $query = array( self::SYNC_DEVID => $this->_deviceInfo->id, self::SYNC_FOLDERID => $id, self::SYNC_USER => $this->_deviceInfo->user ); try { $this->_db->selectCollection(self::COLLECTION_STATE)->remove($query); $this->_db->selectCollection(self::COLLECTION_MAP)->remove($query); $this->_db->selectCollection(self::COLLECTION_MAILMAP)->remove($query); } catch (Exception $e) { $this->_logger->err($e->getMessage()); throw new Horde_ActiveSync_Exception($e); } // Remove the collection data from the synccache as well. $cache = new Horde_ActiveSync_SyncCache($this, $this->_deviceInfo->id, $this->_deviceInfo->user, $this->_logger); if ($id != Horde_ActiveSync::REQUEST_TYPE_FOLDERSYNC) { $cache->removeCollection($id, false); } else { $this->_logger->notice(sprintf( '[%s] Clearing foldersync state from synccache.', $this->_procid)); $cache->clearFolders(); $cache->clearCollections(); $cache->hierarchy = '0'; } $cache->save(); } /** * Get the last time the loaded device issued a SYNC request. * * @param string $id The (optional) devivce id. If empty will use the * currently loaded device. * @param string $user The (optional) user id. If empty wil use the * currently loaded device. * * @return integer The timestamp of the last sync, regardless of collection * @throws Horde_ActiveSync_Exception */ public function getLastSyncTimestamp($id = null, $user = null) { if (empty($id) && empty($this->_deviceInfo)) { throw new Horde_ActiveSync_Exception('Device not loaded.'); } $id = empty($id) ? $this->_deviceInfo->id : $id; $user = empty($user) ? $this->_deviceInfo->user : $user; $match = array(self::SYNC_DEVID => $id); if (!empty($user)) { $match[self::SYNC_USER] = $user; } try { $results = $this->_db->selectCollection(self::COLLECTION_STATE)->aggregate( array('$match' => $match), array('$group' => array(self::MONGO_ID => '$sync_dev', 'max' => array('$max' => '$sync_timestamp'))) ); } catch (Exception $e) { $this->_logger->err(sprintf( '[%s] %s', $this->_procid, $e->getMessage()) ); throw new Horde_ActiveSync_Exception($e); } if (empty($results) || empty($results['ok'])) { throw new Horde_ActiveSync_Exception(empty($results['errmsg']) ? 'Error running aggregation.' : $results['errmsg']); } $results = current($results['result']); return $results['max']; } /** * Save a new device policy key to storage for the current user. * * @param string $devId The device id * @param integer $key The new policy key * * @throws Horde_ActiveSync_Exception */ public function setPolicyKey($devId, $key) { if (empty($this->_deviceInfo) || $devId != $this->_deviceInfo->id) { $this->_logger->err(sprintf( '[%s] Device not loaded', $this->_procid) ); throw new Horde_ActiveSync_Exception('Device not loaded'); } $this->_logger->info(sprintf( '[%s] Setting policykey: %s, %s, %s', $this->_procid, $devId, $this->_backend->getUser(), $key)); $this->_deviceInfo->policykey = $key; $this->_deviceInfo->save(); } /** * Explicitly remove a state from storage. * * @param array $options An options array containing at least one of: * - synckey: (string) Remove only the state associated with this synckey. * DEFAULT: All synckeys are removed for the specified device. * - devId: (string) Remove all information for this device. * DEFAULT: None. If no device, a synckey is required. * - user: (string) Restrict to removing data for this user only. * DEFAULT: None - all users for the specified device are removed. * - id: (string) When removing device state, restrict ro removing data * only for this collection. * * @throws Horde_ActiveSyncException */ public function removeState(array $options) { // If the device is flagged as wiped, and we are removing the state, // we MUST NOT restrict to user since it will not remove the device's // device table entry, and the device will continue to be wiped each // time it connects. if (!empty($options['devId']) && !empty($options['user'])) { $query = array( self::MONGO_ID => $options['devId'], '$or' => array(array(self::DEVICE_RWSTATUS => Horde_ActiveSync::RWSTATUS_PENDING), array(self::DEVICE_RWSTATUS => Horde_ActiveSync::RWSTATUS_WIPED)) ); try { $results = $this->_db->selectCollection(self::COLLECTION_DEVICE)->findOne($query, array(self::MONGO_ID)); } catch (Exception $e) { $this->_logger->err(sprintf( '[%s] %s', $this->_procid, $e->getMessage()) ); throw new Horde_ActiveSync_Exception($e); } if (!empty($results)) { unset($options['user']); return $this->removeState($options); } // Query for state and map tables. $query = array( self::SYNC_DEVID => $options['devId'], self::SYNC_USER => $options['user'] ); if (!empty($options['id'])) { $query[self::SYNC_FOLDERID] = $options['id']; $this->_logger->info(sprintf( '[%s] Removing device state for user %s and collection %s.', $options['devId'], $options['user'], $options['id']) ); } else { $this->_logger->info(sprintf( '[%s] Removing device %s state for user %s.', $this->_procid, $options['devId'], $options['user']) ); $this->deleteSyncCache($options['devId'], $options['user']); } // Remove device data for user try { $this->_db->selectCollection(self::COLLECTION_DEVICE)->update( array(self::MONGO_ID => $options['devId'], self::DEVICE_USERS_USER => $options['user']), array('$pull' => array('users' => array(self::DEVICE_USER=> $options['user']))) ); } catch (Exception $e) { $this->_logger->err(sprintf( '[%s] %s', $this->_procid, $e->getMessage()) ); throw new Horde_ActiveSync_Exception($e); } } elseif (!empty($options['devId'])) { // Query for state and map tables. $query = array(self::SYNC_DEVID => $options['devId']); $this->_logger->info(sprintf( '[%s] Removing all device state for device %s.', $this->_procid, $options['devId']) ); $this->deleteSyncCache($options['devId']); // Remove device data. try { $this->_db->selectCollection(self::COLLECTION_DEVICE)->remove(array(self::MONGO_ID => $options['devId'])); } catch (Exception $e) { $this->_logger->err(sprintf( '[%s] %s', $this->_procid, $e->getMessage()) ); throw new Horde_ActiveSync_Exception($e); } } elseif (!empty($options['user'])) { // Query for state and map tables. $query = array(self::SYNC_USER => $options['user']); $this->_logger->info(sprintf( '[%s] Removing all device state for user %s.', $this->_procid, $options['user']) ); $this->deleteSyncCache(null, $options['user']); // Delete all user's device info. try { $this->_db->selectCollection(self::COLLECTION_DEVICE)->update( array(self::DEVICE_USERS_USER), array('$pull' => array('users' => array(self::DEVICE_USER => $options['user']))) ); } catch (Exception $e) { $this->_logger->err($e->getMessage()); throw new Horde_ActiveSync_Exception($e); } } elseif (!empty($options['synckey'])) { $query = array(self::SYNC_KEY => $options['synckey']); $this->_logger->info(sprintf( '[%s] Removing device state for sync_key %s only.', $this->_procid, $options['synckey']) ); } else { return; } // Do the state/map deletions and GC the device collection. try { $this->_db->selectCollection(self::COLLECTION_STATE)->remove($query); $this->_db->selectCollection(self::COLLECTION_MAP)->remove($query); $this->_db->selectCollection(self::COLLECTION_MAILMAP)->remove($query); $this->_db->selectCollection(self::COLLECTION_DEVICE)->remove(array('users' => array('$size' => 0))); } catch (Exception $e) { $this->_logger->err(sprintf( '[%s] %s', $this->_procid, $e->getMessage()) ); throw new Horde_ActiveSync_Exception($e); } } /** * Check and see that we didn't already see the incoming change from the client. * This would happen e.g., if the client failed to receive the server response * after successfully importing new messages. * * @param string $id The client id sent during message addition. * * @return string The UID for the given clientid, null if none found. * @throws Horde_ActiveSync_Exception */ public function isDuplicatePIMAddition($id) { $query = array( self::SYNC_CLIENTID => $id, self::SYNC_USER => $this->_deviceInfo->user, self::SYNC_DEVID => $this->_deviceInfo->id ); try { $result = $this->_db->selectCollection(self::COLLECTION_MAP)->findOne( $query, array(self::MESSAGE_UID) ); } catch (Exception $e) { $this->_logger->err(sprintf( '[%s] %s', $this->_procid, $e->getMessage()) ); throw new Horde_ActiveSync_Exception($e); } if (empty($result)) { return null; } return $result[self::MESSAGE_UID]; } /** * Return the sync cache. * * @param string $devid The device id. * @param string $user The user id. * @param array $fields An array of fields to return. Default is to return * the full cache. @since 2.9.0 * * @return array The current sync cache for the user/device combination. * @throws Horde_ActiveSync_Exception */ public function getSyncCache($devid, $user, array $fields = null) { $this->_logger->info(sprintf( '[%s] Loading SyncCache from storage: %s', $this->_procid, serialize($fields))); $query = array( self::CACHE_DEVID => $devid, self::CACHE_USER => $user ); $projection = array(); if (!is_null($fields)) { foreach ($fields as $field) { $projection[] = 'cache_data.' . $field; } } else { $projection = array(self::CACHE_DATA); } try { $data = $this->_db->selectCollection(self::COLLECTION_CACHE)->findOne( $query, $projection ); } catch (Exception $e) { $this->_logger->err(sprintf( '[%s] %s', $this->_procid, $e->getMessage()) ); throw new Horde_ActiveSync_Exception($e); } if (is_null($fields) && (empty($data) || empty($data[self::CACHE_DATA]))) { return array( 'confirmed_synckeys' => array(), 'lasthbsyncstarted' => false, 'lastsyncendnormal' => false, 'timestamp' => false, 'wait' => false, 'hbinterval' => false, 'folders' => array(), 'hierarchy' => false, 'collections' => array(), 'pingheartbeat' => false); } else { return $data[self::CACHE_DATA]; } } /** * Save the provided sync_cache. * * @param array $cache The cache to save. * @param string $devid The device id. * @param string $user The user id. * @param array $dirty An array of dirty properties. @since 2.9.0 * * @throws Horde_ActiveSync_Exception */ public function saveSyncCache(array $cache, $devid, $user, array $dirty = array()) { $this->_logger->info( sprintf('[%s] Saving SYNC_CACHE entry fields %s for user %s and device %s.', $this->_procid, serialize($dirty), $user, $devid)); $cache['timestamp'] = strval($cache['timestamp']); $query = array( self::CACHE_DEVID => $devid, self::CACHE_USER => $user ); $update = array(); // Ensure the initial object is written for the collection data. if (empty($cache['collections'])) { $cache['collections'] = new stdClass(); $update['cache.data.collections'] = $cache['collections']; } foreach ($dirty as $property => $value) { if ($property == 'collections' && is_array($value) && !empty($cache['collections'])) { foreach (array_keys($value) as $collection) { $update['cache_data.collections.' . $collection] = $cache['collections'][$collection]; } } else { $update['cache_data.' . $property] = $cache[$property]; } } try { $this->_db->selectCollection(self::COLLECTION_CACHE)->update( $query, array('$set' => $update), array('upsert' => true) ); } catch (Exception $e) { $this->_logger->err(sprintf( '[%s] %s', $this->_procid, $e->getMessage()) ); throw new Horde_ActiveSync_Exception($e); } } /** * Delete a complete sync cache * * @param string $devid The device id * @param string $user The user name. * * @throws Horde_ActiveSync_Exception */ public function deleteSyncCache($devid, $user = null) { $this->_logger->info(sprintf( 'Horde_ActiveSync_State_Mongo::deleteSyncCache(%s, %s)', $devid, $user)); $params = array(); if (!empty($devid)) { $params[self::CACHE_DEVID] = $devid; } if (!empty($user)) { $params[self::CACHE_USER] = $user; } try { $this->_db->selectCollection(self::COLLECTION_CACHE)->remove($params); } catch (Exception $e) { $this->_logger->err(sprintf( '[%s] %s', $this->_procid, $e->getMessage()) ); throw new Horde_ActiveSync_Exception($e); } } /** * Return an array of timestamps from the map table for the last * client-initiated change for the provided uid. Used to avoid mirroring back * changes to the client that it sent to the server. * * @param array $changes The changes array, containing 'id' and 'type'. * * @return array An array of UID -> timestamp of the last client-initiated * change for the specified uid, or null if none found. */ protected function _getPIMChangeTS(array $changes) { // Get the allowed synckeys to include. $uuid = self::getSyncKeyUid($this->_syncKey); $cnt = self::getSyncKeyCounter($this->_syncKey); $keys = array(); foreach (array($uuid . $cnt, $uuid . ($cnt - 1)) as $v) { $keys[] = $v; } $match = array( self::SYNC_DEVID => $this->_deviceInfo->id, self::SYNC_USER => $this->_deviceInfo->user, self::SYNC_KEY => array('$in' => $keys) ); $uids = array(); $match['$or'] = array(); foreach ($changes as $change) { $match['$or'][] = array( '$and' => array( array(self::MESSAGE_UID => $change['id']), array(self::SYNC_DELETED => $change['type'] == Horde_ActiveSync::CHANGE_TYPE_DELETE) ) ); } try { $rows = $this->_db->selectCollection(self::COLLECTION_MAP)->aggregate( array('$match' => $match), array('$group' => array(self::MONGO_ID => '$message_uid', 'max' => array('$max' => '$sync_modtime'))) ); } catch (Exception $e) { $this->_logger->err(sprintf( '[%s] %s', $this->_procid, $e->getMessage()) ); throw new Horde_ActiveSync_Exception($e); } if (empty($rows) || empty($rows['ok'])) { throw new Horde_ActiveSync_Exception(sprintf( 'Error running aggregation: %s', empty($rows['errmsg']) ? '' : $rows['errmsg'])); } $results = array(); foreach ($rows['result'] as $row) { $results[$row[self::MONGO_ID]] = $row['max']; } return $results; } /** * Check for the existence of ANY entries in the map table for this device * and user. * * An extra database query for each sync, but the payoff is that we avoid * having to stat every message change we send to the client if there are no * client generated changes for this sync period. * * @return boolean * @throws Horde_ActiveSync_Exception */ protected function _havePIMChanges() { if ($this->_collection['class'] == Horde_ActiveSync::CLASS_EMAIL) { return true; } $this->_logger->info(sprintf( '[%s] Horde_ActiveSync_State_Mongo::_havePIMChanges() for %s', $this->_procid, $this->_collection['serverid'])); $c = $this->_db->selectCollection(self::COLLECTION_MAP); $query = array( self::SYNC_DEVID => $this->_deviceInfo->id, self::SYNC_USER => $this->_deviceInfo->user, self::SYNC_FOLDERID => $this->_collection['serverid'] ); try { return (bool)$c->find($query, array(self::MONGO_ID))->count(); } catch (Exception $e) { $this->_logger->err(sprintf( '[%s] %s', $this->_procid, $e->getMessage()) ); throw new Horde_ActiveSync_Exception($e); } } /** * Return all available mailMap changes for the current folder. * * @param array $changes The chagnes array * * @return array An array of hashes, each in the form of * {uid} => array( * Horde_ActiveSync::CHANGE_TYPE_FLAGS => true|false, * Horde_ActiveSync::CHANGE_TYPE_DELETE => true|false * ) */ protected function _getMailMapChanges(array $changes) { $ids = array(); foreach ($changes as $change) { $ids[] = strval($change['id']); } $query = array( self::SYNC_FOLDERID => $this->_collection['serverid'], self::SYNC_DEVID => $this->_deviceInfo->id, self::SYNC_USER => $this->_deviceInfo->user, self::MESSAGE_UID => array('$in' => $ids) ); $rows = $this->_db->selectCollection(self::COLLECTION_MAILMAP)->find( $query, array(self::MESSAGE_UID, self::SYNC_READ, self::SYNC_FLAGGED, self::SYNC_DELETED, self::SYNC_CHANGED) ); $results = array(); foreach ($rows as $row) { foreach ($changes as $change) { if ($change['id'] == $row[self::MESSAGE_UID]) { switch ($change['type']) { case Horde_ActiveSync::CHANGE_TYPE_FLAGS: $results[$row[self::MESSAGE_UID]][$change['type']] = (!is_null($row[self::SYNC_READ]) && $row[self::SYNC_READ] == $change['flags']['read']) || (!is_null($row[self::SYNC_FLAGGED] && $row[self::SYNC_FLAGGED] == $change['flags']['flagged'])); continue 3; case Horde_ActiveSync::CHANGE_TYPE_DELETE: $results[$row[self::MESSAGE_UID]][$change['type']] = !is_null($row[self::SYNC_DELETED]) && $row[self::SYNC_DELETED] == true; continue 3; case Horde_ActiveSync::CHANGE_TYPE_CHANGE: $results[$row[self::MESSAGE_UID]][$change['type']] = !is_null($row[self::SYNC_CHANGED]) && $row[self::SYNC_CHANGED] == true; } } } } return $results; } /** * Garbage collector - clean up from previous sync requests. * * @param string $syncKey The sync key * * @throws Horde_ActiveSync_Exception */ protected function _gc($syncKey) { if (!preg_match('/^\{([0-9A-Za-z-]+)\}([0-9]+)$/', $syncKey, $matches)) { return; } $guid = $matches[1]; $n = $matches[2] - 1; // Clean up all but the last 2 syncs for any given sync series, this // ensures that we can still respond to SYNC requests for the previous // key if the client never received the new key in a SYNC response. $js = << $this->_deviceInfo->id, self::SYNC_FOLDERID => !empty($this->_collection['id']) ? $this->_collection['id'] : Horde_ActiveSync::CHANGE_TYPE_FOLDERSYNC, '$where' => $js ); try { $this->_db->selectCollection(self::COLLECTION_STATE)->remove($query); } catch (Exception $e) { $this->_logger->err(sprintf( '[%s] %s', $this->_procid, $e->getMessage()) ); throw new Horde_ActiveSync_Exception($e); } // Also clean up the map table since this data is only needed for one // SYNC cycle. Keep the same number of old keys for the same reasons as // above. $js = <<_db->selectCollection(self::COLLECTION_MAP), $this->_db->selectCollection(self::COLLECTION_MAILMAP)) as $c) { $query = array( self::SYNC_DEVID => $this->_deviceInfo->id, self::SYNC_USER => $this->_deviceInfo->user, '$where' => $js ); try { $c->remove($query); } catch (Exception $e) { $this->_logger->err(sprintf( '[%s] %s', $this->_procid, $e->getMessage()) ); throw new Horde_ActiveSync_Exception($e); } } } /* Horde_Mongo_Collection_Index methods. */ /** */ public function checkMongoIndices() { foreach ($this->_indexes as $collection => $indices) { if (!$this->_mongo->checkIndices($collection, $indices)) { return false; } } return true; } /** */ public function createMongoIndices() { foreach ($this->_indexes as $collection => $indices) { $this->_mongo->createIndices($collection, $indices); } } } Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/State/Sql.php0000664000076500000240000016630612654565405020146 0ustar * @package ActiveSync */ /** * SQL based state management. Responsible for maintaining device state * information such as last sync time, provisioning status, client-sent changes, * and for calculating deltas between server and client. * * Needs a number of SQL tables present: * syncStateTable (horde_activesync_state): * sync_timestamp: - The timestamp of last sync * sync_key: - The syncKey for the last sync * sync_pending: - If the last sync resulted in a MOREAVAILABLE, this * contains a list of UIDs that still need to be sent to * the client. * sync_data: - Any state data that we need to track for the specific * syncKey. Data such as current folder list on the client * (for a FOLDERSYNC) and IMAP email UIDs (for Email * collections during a SYNC). * sync_devid: - The device id. * sync_folderid:- The folder id for this sync. * sync_user: - The user for this synckey. * sync_mod: - The last modification stamp. * * syncMapTable (horde_activesync_map): * message_uid - The server uid for the object * sync_modtime - The time the change was received from the client and * applied to the server data store. * sync_key - The syncKey that was current at the time the change * was received. * sync_devid - The device id this change was done on. * sync_user - The user that initiated the change. * * syncDeviceTable (horde_activesync_device): * device_id - The unique id for this device * device_type - The device type the client identifies itself with * device_agent - The user agent string sent by the device * device_policykey - The current policykey for this device * device_rwstatus - The current remote wipe status for this device * * syncUsersTable (horde_activesync_device_users): * device_user - A username attached to the device * device_id - The device id * device_policykey - The provisioned policykey for this device/user * combination. * * @license http://www.horde.org/licenses/gpl GPLv2 * @copyright 2010-2016 Horde LLC (http://www.horde.org/) * @author Michael J Rubinsky * @link http://pear.horde.org/index.php?package=ActiveSync * @package ActiveSync */ class Horde_ActiveSync_State_Sql extends Horde_ActiveSync_State_Base { /** * DB handle * * @var Horde_Db_Adapter */ protected $_db; /** * State table name. This table holds the device's current state. * * @var string */ protected $_syncStateTable; /** * The Sync Map table. This table temporarily holds information about * changes received FROM the client and is used to prevent mirroring back * changes to the client that originated there. * * @var string */ protected $_syncMapTable; /** * The Sync Mail Map table. Same principle as self::_syncMapTable, but for * email collection data. * * @var string */ protected $_syncMailMapTable; /** * Device information table. Holds information about each client. * * @var string */ protected $_syncDeviceTable; /** * Users table. Holds information specific to a user. * * @var string */ protected $_syncUsersTable; /** * The Synccache table. Holds the sync cache and is used to cache info * about SYNC and PING request that are only sent a single time. Also stores * data supported looping SYNC requests. * * @var string */ protected $_syncCacheTable; /** * Const'r * * @param array $params Must contain: * - db: (Horde_Db_Adapter_Base) The Horde_Db instance. * * @return Horde_ActiveSync_State_Sql */ public function __construct(array $params = array()) { parent::__construct($params); if (empty($this->_params['db']) || !($this->_params['db'] instanceof Horde_Db_Adapter)) { throw new InvalidArgumentException('Missing or invalid Horde_Db parameter.'); } $this->_syncStateTable = 'horde_activesync_state'; $this->_syncMapTable = 'horde_activesync_map'; $this->_syncDeviceTable = 'horde_activesync_device'; $this->_syncUsersTable = 'horde_activesync_device_users'; $this->_syncMailMapTable = 'horde_activesync_mailmap'; $this->_syncCacheTable = 'horde_activesync_cache'; $this->_db = $params['db']; } /** * Update the serverid for a given folder uid in the folder's state object. * Needed when a folder is renamed on a client, but the UID must remain the * same. * * @param string $uid The folder UID. * @param string $serverid The new serverid for this uid. * @since 2.4.0 */ public function updateServerIdInState($uid, $serverid) { $this->_logger->info(sprintf( '[%s] Updating serverid in folder state. Setting %s for %s.', $this->_procid, $serverid, $uid)); $sql = 'SELECT sync_key, sync_data FROM ' . $this->_syncStateTable . ' WHERE ' . 'sync_devid = ? AND sync_user = ? AND sync_folderid = ?'; try { $results = $this->_db->selectAll($sql, array($this->_deviceInfo->id, $this->_deviceInfo->user, $uid)); } catch (Horde_Db_Exception $e) { $this->_logger->err(sprintf( '[%s] %s', $this->_procid, $e->getMessage()) ); throw new Horde_ActiveSync_Exception($e); } try { $columns = $this->_db->columns($this->_syncStateTable); } catch (Horde_Db_Exception $e) { $this->_logger->err(sprintf( '[%s] %s', $this->_procid, $e->getMessage()) ); throw new Horde_ActiveSync_Exception($e); } $update = 'UPDATE ' . $this->_syncStateTable . ' SET sync_data = ? WHERE ' . 'sync_devid = ? AND sync_user = ? AND sync_folderid = ? AND sync_key = ?'; foreach ($results as $result) { $folder = unserialize($columns['sync_data']->binaryToString($result['sync_data'])); $folder->setServerId($serverid); $folder = serialize($folder); try { $this->_db->update($update, array( new Horde_Db_Value_Binary($folder), $this->_deviceInfo->id, $this->_deviceInfo->user, $uid, $result['sync_key'] ) ); } catch (Horde_Db_Exception $e) { $this->_logger->err(sprintf( '[%s] %s', $this->_procid, $e->getMessage()) ); throw new Horde_ActiveSync_Exception($e); } } } /** * Load the state represented by $syncKey from storage. * * @param string $type The type of state a * Horde_ActiveSync::REQUEST_TYPE constant. * * @throws Horde_ActiveSync_Exception, Horde_ActiveSync_Exception_StateGone */ protected function _loadState($type) { // Load the previous syncState from storage try { $results = $this->_db->selectOne('SELECT sync_data, sync_devid, sync_mod, sync_pending FROM ' . $this->_syncStateTable . ' WHERE sync_key = ?', array($this->_syncKey)); } catch (Horde_Db_Exception $e) { $this->_logger->err(sprintf( '[%s] %s', $this->_procid, $e->getMessage()) ); throw new Horde_ActiveSync_Exception($e); } if (empty($results)) { $this->_logger->warn(sprintf( '[%s] Could not find state for synckey %s.', $this->_procid, $this->_syncKey)); throw new Horde_ActiveSync_Exception_StateGone(); } $this->_loadStateFromResults($results, $type); } /** * Actually load the state data into the object from the query results. * * @param array $results The results array from the state query. * @param string $type The type of request we are handling. * * @throws Horde_ActiveSync_Exception_StateGone */ protected function _loadStateFromResults($results, $type = Horde_ActiveSync::REQUEST_TYPE_SYNC) { // Load the last known sync time for this collection $this->_lastSyncStamp = !empty($results['sync_mod']) ? $results['sync_mod'] : 0; // Pre-Populate the current sync timestamp in case this is only a // Client -> Server sync. $this->_thisSyncStamp = $this->_lastSyncStamp; // Restore any state or pending changes try { $columns = $this->_db->columns($this->_syncStateTable); } catch (Horde_Db_Exception $e) { $this->_logger->err(sprintf( '[%s] %s', $this->_procid, $e->getMessage()) ); throw new Horde_ActiveSync_Exception($e); } $data = unserialize($columns['sync_data']->binaryToString($results['sync_data'])); $pending = unserialize($results['sync_pending']); if ($type == Horde_ActiveSync::REQUEST_TYPE_FOLDERSYNC) { $this->_folder = ($data !== false) ? $data : array(); $this->_logger->info( sprintf('[%s] Loading FOLDERSYNC state containing %d folders.', $this->_procid, count($this->_folder))); } elseif ($type == Horde_ActiveSync::REQUEST_TYPE_SYNC) { // @TODO: This shouldn't default to an empty folder object, // if we don't have the data, it's an exception. $this->_folder = ($data !== false ? $data : ($this->_collection['class'] == Horde_ActiveSync::CLASS_EMAIL ? new Horde_ActiveSync_Folder_Imap($this->_collection['serverid'], Horde_ActiveSync::CLASS_EMAIL) : new Horde_ActiveSync_Folder_Collection($this->_collection['serverid'], $this->_collection['class'])) ); $this->_changes = ($pending !== false) ? $pending : null; if ($this->_changes) { $this->_logger->info( sprintf('[%s] Found %d changes remaining from previous SYNC.', $this->_procid, count($this->_changes))); } } } /** * Save the current state to storage * * @throws Horde_ActiveSync_Exception */ public function save() { // Prepare state and pending data if ($this->_type == Horde_ActiveSync::REQUEST_TYPE_FOLDERSYNC) { $data = (isset($this->_folder) ? serialize($this->_folder) : ''); $pending = ''; } elseif ($this->_type == Horde_ActiveSync::REQUEST_TYPE_SYNC) { $pending = (isset($this->_changes) ? serialize(array_values($this->_changes)) : ''); $data = (isset($this->_folder) ? serialize($this->_folder) : ''); } else { $pending = ''; $data = ''; } // If we are setting the first synckey iteration, do not save the // timestamp, otherwise we will never get the initial set of data. $params = array( 'sync_key' => $this->_syncKey, 'sync_data' => new Horde_Db_Value_Binary($data), 'sync_devid' => $this->_deviceInfo->id, 'sync_mod' => (self::getSyncKeyCounter($this->_syncKey) == 1 ? 0 : $this->_thisSyncStamp), 'sync_folderid' => (!empty($this->_collection['id']) ? $this->_collection['id'] : Horde_ActiveSync::REQUEST_TYPE_FOLDERSYNC), 'sync_user' => $this->_deviceInfo->user, 'sync_pending' => $pending, 'sync_timestamp' => time() ); $this->_logger->info( sprintf('[%s] Saving state: %s', $this->_procid, serialize(array( $params['sync_key'], $params['sync_data'], $params['sync_devid'], $params['sync_mod'], $params['sync_folderid'], $params['sync_user'], count($this->_changes), time())) ) ); try { $this->_db->insertBlob($this->_syncStateTable, $params); } catch (Horde_Db_Exception $e) { // Might exist already if the last sync attempt failed. $this->_logger->notice( sprintf('[%s] Previous request processing for synckey %s failed to be accepted by the client, removing previous state and trying again.', $this->_procid, $this->_syncKey) ); $this->_db->delete('DELETE FROM ' . $this->_syncStateTable . ' WHERE sync_key = ?', array($this->_syncKey)); $this->_db->insertBlob($this->_syncStateTable, $params); } } /** * Update the state to reflect changes * * Notes: If we are importing client changes, need to update the syncMapTable * so we don't mirror back the changes on next sync. If we are exporting * server changes, we need to track which changes have been sent (by * removing them from $this->_changes) so we know which items to send on the * next sync if a MOREAVAILBLE response was needed. If this is being called * from a FOLDERSYNC command, update state accordingly. * * @param string $type The type of change (change, delete, flags or * foldersync) * @param array $change A stat/change hash describing the change. * Contains: * - id: (mixed) The message uid the change applies to. * - serverid: (string) The backend server id for the folder. * - folderuid: (string) The EAS folder UID for the folder. * - parent: (string) The parent of the current folder, if any. * - flags: (array) If this is a flag change, the state of the flags. * - mod: (integer) The modtime of this change. * * @param integer $origin Flag to indicate the origin of the change: * Horde_ActiveSync::CHANGE_ORIGIN_NA - Not applicapble/not important * Horde_ActiveSync::CHANGE_ORIGIN_PIM - Change originated from client * * @param string $user The current sync user, only needed if change * origin is CHANGE_ORIGIN_PIM * @param string $clientid client clientid sent when adding a new message * * @todo This method needs some cleanup, abstraction. */ public function updateState( $type, array $change, $origin = Horde_ActiveSync::CHANGE_ORIGIN_NA, $user = null, $clientid = '') { $this->_logger->info(sprintf('[%s] Updating state during %s', $this->_procid, $type)); if ($origin == Horde_ActiveSync::CHANGE_ORIGIN_PIM) { if ($this->_type == Horde_ActiveSync::REQUEST_TYPE_FOLDERSYNC) { foreach ($this->_folder as $fi => $state) { if ($state['id'] == $change['id']) { unset($this->_folder[$fi]); break; } } if ($type != Horde_ActiveSync::CHANGE_TYPE_DELETE) { $this->_folder[] = $change; } $this->_folder = array_values($this->_folder); return; } // Some requests like e.g., MOVEITEMS do not include the state // information since there is no SYNCKEY. Attempt to map this from // the $change array. if (empty($this->_collection)) { $this->_collection = array( 'class' => $change['class'], 'id' => $change['folderuid']); } $syncKey = empty($this->_syncKey) ? $this->getLatestSynckeyForCollection($this->_collection['id']) : $this->_syncKey; // This is an incoming change from the client, store it so we // don't mirror it back to device. switch ($this->_collection['class']) { case Horde_ActiveSync::CLASS_EMAIL: if ($type == Horde_ActiveSync::CHANGE_TYPE_CHANGE && isset($change['flags']) && is_array($change['flags']) && !empty($change['flags'])) { $type = Horde_ActiveSync::CHANGE_TYPE_FLAGS; } switch ($type) { case Horde_ActiveSync::CHANGE_TYPE_FLAGS: if (isset($change['flags']['read'])) { // This is a mail sync changing only a read flag. $sql = 'INSERT INTO ' . $this->_syncMailMapTable . ' (message_uid, sync_key, sync_devid,' . ' sync_folderid, sync_user, sync_read)' . ' VALUES (?, ?, ?, ?, ?, ?)'; $flag_value = !empty($change['flags']['read']); } else { $sql = 'INSERT INTO ' . $this->_syncMailMapTable . ' (message_uid, sync_key, sync_devid,' . ' sync_folderid, sync_user, sync_flagged)' . ' VALUES (?, ?, ?, ?, ?, ?)'; $flag_value = !empty($change['flags']['flagged']); } break; case Horde_ActiveSync::CHANGE_TYPE_DELETE: $sql = 'INSERT INTO ' . $this->_syncMailMapTable . ' (message_uid, sync_key, sync_devid,' . ' sync_folderid, sync_user, sync_deleted)' . ' VALUES (?, ?, ?, ?, ?, ?)'; $flag_value = true; break; case Horde_ActiveSync::CHANGE_TYPE_CHANGE: // Used to remember "new" messages that are a result of // a MOVEITEMS request. $sql = 'INSERT INTO ' . $this->_syncMailMapTable . ' (message_uid, sync_key, sync_devid,' . ' sync_folderid, sync_user, sync_changed)' . ' VALUES (?, ?, ?, ?, ?, ?)'; $flag_value = true; break; } $params = array($change['id'], $syncKey, $this->_deviceInfo->id, $change['serverid'], $user, $flag_value ); break; default: $sql = 'INSERT INTO ' . $this->_syncMapTable . ' (message_uid, sync_modtime, sync_key, sync_devid,' . ' sync_folderid, sync_user, sync_clientid, sync_deleted)' . ' VALUES (?, ?, ?, ?, ?, ?, ?, ?)'; $params = array( $change['id'], $change['mod'], $syncKey, $this->_deviceInfo->id, $change['serverid'], $user, $clientid, $type == Horde_ActiveSync::CHANGE_TYPE_DELETE); } try { $this->_db->insert($sql, $params); } catch (Horde_Db_Exception $e) { throw new Horde_ActiveSync_Exception($e); } } else { // We are sending server changes; $this->_changes will contain all // changes so we need to track which ones are sent since not all // may be sent. We need to store the leftovers for sending next // request. // @todo FIX BC HACK for differing data structures when sending // initial change set. foreach ($this->_changes as $key => $value) { if ((is_array($value) && $value['id'] == $change['id']) || $value == $change['id']) { if ($this->_type == Horde_ActiveSync::REQUEST_TYPE_FOLDERSYNC) { foreach ($this->_folder as $fi => $state) { if ($state['id'] == $value['id']) { unset($this->_folder[$fi]); break; } } // Only save what we need. Note that 'mod' is eq to the // folder id, since that is the only thing that can // change in a folder. if ($type != Horde_ActiveSync::CHANGE_TYPE_DELETE) { $folder = $this->_backend->getFolder($value['serverid']); $stat = $this->_backend->statFolder( $value['id'], (empty($value['parent']) ? '0' : $value['parent']), $folder->displayname, $folder->_serverid, $folder->type); $this->_folder[] = $stat; $this->_folder = array_values($this->_folder); } } unset($this->_changes[$key]); break; } } } } /** * Load the device object. * * @param string $devId The device id to obtain * @param string $user The user to retrieve user-specific device info for * @param array $params Additional parameters: * - force: (boolean) If true, reload the device info even if it's * already loaded. Used to refresh values such as device_rwstatus that * may have changed during a long running PING/SYNC. DEFAULT: false. * @since 2.31.0 * * @return Horde_ActiveSync_Device The device object * @throws Horde_ActiveSync_Exception */ public function loadDeviceInfo($devId, $user = null, $params = array()) { // See if we already have this device, for this user loaded if (empty($params['force']) && !empty($this->_deviceInfo) && $this->_deviceInfo->id == $devId && !empty($this->_deviceInfo) && $user == $this->_deviceInfo->user) { return $this->_deviceInfo; } $query = 'SELECT device_type, device_agent, ' . 'device_rwstatus, device_supported, device_properties FROM ' . $this->_syncDeviceTable . ' WHERE device_id = ?'; try { if (!$device = $this->_db->selectOne($query, array($devId))) { throw new Horde_ActiveSync_Exception('Device not found.'); } $columns = $this->_db->columns($this->_syncDeviceTable); $device['device_properties'] = $columns['device_properties'] ->binaryToString($device['device_properties']); $device['device_supported'] = $columns['device_supported'] ->binaryToString($device['device_supported']); } catch (Horde_Db_Exception $e) { throw new Horde_ActiveSync_Exception($e); } if (!empty($user)) { $query = 'SELECT device_policykey FROM ' . $this->_syncUsersTable . ' WHERE device_id = ? AND device_user = ?'; try { $duser = $this->_db->selectOne($query, array($devId, $user)); } catch (Horde_Db_Exception $e) { throw new Horde_ActiveSync_Exception($e); } } $this->_deviceInfo = new Horde_ActiveSync_Device($this); $this->_deviceInfo->rwstatus = $device['device_rwstatus']; $this->_deviceInfo->deviceType = $device['device_type']; $this->_deviceInfo->userAgent = $device['device_agent']; $this->_deviceInfo->id = $devId; $this->_deviceInfo->user = $user; $this->_deviceInfo->supported = unserialize($device['device_supported']); if (empty($duser)) { $this->_deviceInfo->policykey = 0; } else { $this->_deviceInfo->policykey = empty($duser['device_policykey']) ? 0 : $duser['device_policykey']; } $this->_deviceInfo->properties = unserialize($device['device_properties']); return $this->_deviceInfo; } /** * Set new device info * * @param Horde_ActiveSync_Device $data The device information * @param array $dirty Array of dirty properties. * @since 2.9.0 * * @throws Horde_ActiveSync_Exception */ public function setDeviceInfo(Horde_ActiveSync_Device $data, array $dirty = array()) { // Make sure we have the device entry try { if (!$this->deviceExists($data->id)) { $this->_logger->info(sprintf('[%s] Device entry does not exist for %s creating it.', $this->_procid, $data->id)); $query = 'INSERT INTO ' . $this->_syncDeviceTable . ' (device_type, device_agent, device_rwstatus, device_id, device_supported)' . ' VALUES(?, ?, ?, ?, ?)'; $values = array( $data->deviceType, (!empty($data->userAgent) ? $data->userAgent : ''), $data->rwstatus, $data->id, (!empty($data->supported) ? serialize($data->supported) : '') ); $this->_db->insert($query, $values); } else { $this->_logger->info((sprintf( '[%s] Device entry exists for %s, updating userAgent, version, and supported.', $this->_procid, $data->id))); // device_supported is immutable, and only sent during the initial // sync request, so only set it if it's non-empty. $query = 'UPDATE ' . $this->_syncDeviceTable . ' SET device_agent = ?, device_properties = ?' . (!empty($data->supported) ? ', device_supported = ?' : '') . ' WHERE device_id = ?'; $values = array( (!empty($data->userAgent) ? $data->userAgent : ''), serialize($data->properties) ); if (!empty($data->supported)) { $values[] = serialize($data->supported); } $values[] = $data->id; $this->_db->update($query, $values); } } catch(Horde_Db_Exception $e) { throw new Horde_ActiveSync_Exception($e); } $this->_deviceInfo = $data; // See if we have the user already also try { $query = 'SELECT COUNT(*) FROM ' . $this->_syncUsersTable . ' WHERE device_id = ? AND device_user = ?'; $cnt = $this->_db->selectValue($query, array($data->id, $data->user)); if ($cnt == 0) { $this->_logger->info(sprintf('[%s] Device entry does not exist for device %s and user %s - creating it.', $this->_procid, $data->id, $data->user)); $query = 'INSERT INTO ' . $this->_syncUsersTable . ' (device_id, device_user, device_policykey)' . ' VALUES(?, ?, ?)'; $values = array( $data->id, $data->user, $data->policykey ); return $this->_db->insert($query, $values); } } catch (Horde_Db_Exception $e) { throw new Horde_ActiveSync_Exception($e); } } /** * Set the device's properties as sent by a SETTINGS request. * * @param array $data The device settings * @param string $deviceId The device id. * * @throws Horde_ActiveSync_Exception */ public function setDeviceProperties(array $data, $deviceId) { $query = 'UPDATE ' . $this->_syncDeviceTable . ' SET device_properties = ?' . ' WHERE device_id = ?'; $properties = array( serialize($data), $deviceId); try { $this->_db->update($query, $properties); } catch (Horde_Db_Exception $e) { throw new Horde_ActiveSync_Exception($e); } } /** * Check that a given device id is known to the server. This is regardless * of Provisioning status. If $user is provided, checks that the device * is attached to the provided username. * * @param string $devId The device id to check. * @param string $user The device should be owned by this user. * * @return integer The numer of device entries found for the give devId, * user combination. I.e., 0 == no device exists. */ public function deviceExists($devId, $user = null) { if (!empty($user)) { $query = 'SELECT COUNT(*) FROM ' . $this->_syncDeviceTable . ' d INNER JOIN ' . $this->_syncUsersTable . ' u on d.device_id = u.device_id' . ' WHERE u.device_id = ? AND device_user = ?'; $values = array($devId, $user); } else { $query = 'SELECT COUNT(*) FROM ' . $this->_syncDeviceTable . ' WHERE device_id = ?'; $values = array($devId); } try { return $this->_db->selectValue($query, $values); } catch (Horde_Db_Exception $e) { throw new Horde_ActiveSync_Exception($e); } } /** * List all devices that we know about. * * @param string $user The username to list devices for. If empty, will * return all devices. * @param array $filter An array of optional filters where the keys are * field names and the values are values to match. * * @return array An array of device hashes * @throws Horde_ActiveSync_Exception */ public function listDevices($user = null, $filter = array()) { $query = 'SELECT d.device_id AS device_id, device_type, device_agent,' . ' device_policykey, device_rwstatus, device_user, device_properties FROM ' . $this->_syncDeviceTable . ' d INNER JOIN ' . $this->_syncUsersTable . ' u ON d.device_id = u.device_id'; $values = array(); $glue = false; if (!empty($user)) { $query .= ' WHERE u.device_user = ?'; $values[] = $user; $glue = true; } $explicit_fields = array('device_id', 'device_type', 'device_agent', 'device_user'); foreach ($filter as $key => $value) { if (in_array($key, $explicit_fields)) { $query .= ($glue ? ' AND ' : ' WHERE ') . 'd.' . $key . ' LIKE ?'; $values[] = $value . '%'; } } try { return $this->_db->selectAll($query, $values); } catch (Horde_Db_Exception $e) { throw new Horde_ActiveSync_Exception($e); } } /** * Get the last time the loaded device issued a SYNC request. * * @param string $id The (optional) devivce id. If empty will use the * currently loaded device. * @param string $user The (optional) user id. If empty wil use the * currently loaded device. * * @return integer The timestamp of the last sync, regardless of collection * @throws Horde_ActiveSync_Exception */ public function getLastSyncTimestamp($id = null, $user = null) { if (empty($id) && empty($this->_deviceInfo)) { throw new Horde_ActiveSync_Exception('Device not loaded.'); } $id = empty($id) ? $this->_deviceInfo->id : $id; $user = empty($user) ? $this->_deviceInfo->user : $user; $params = array($id); $sql = 'SELECT MAX(sync_timestamp) FROM ' . $this->_syncStateTable . ' WHERE sync_devid = ?'; if (!empty($user)) { $sql .= ' AND sync_user = ?'; $params[] = $user; } try { return $this->_db->selectValue($sql, $params); } catch (Horde_Db_Exception $e) { throw new Horde_ActiveSync_Exception($e); } } /** * Save a new device policy key to storage. * * @param string $devId The device id * @param integer $key The new policy key * * @throws Horde_ActiveSync_Exception */ public function setPolicyKey($devId, $key) { if (empty($this->_deviceInfo) || $devId != $this->_deviceInfo->id) { $this->_logger->err('Device not loaded'); throw new Horde_ActiveSync_Exception('Device not loaded'); } $query = 'UPDATE ' . $this->_syncUsersTable . ' SET device_policykey = ? WHERE device_id = ? AND device_user = ?'; try { $this->_db->update($query, array($key, $devId, $this->_backend->getUser())); } catch (Horde_Db_Exception $e) { throw new Horde_ActiveSync_Exception($e); } } /** * Reset ALL device policy keys. Used when server policies have changed * and you want to force ALL devices to pick up the changes. This will * cause all devices that support provisioning to be reprovisioned. * * @throws Horde_ActiveSync_Exception * */ public function resetAllPolicyKeys() { $query = 'UPDATE ' . $this->_syncUsersTable . ' SET device_policykey = 0'; try { $this->_db->update($query); } catch (Horde_Db_Exception $e) { throw new Horde_ActiveSync_Exception($e); } } /** * Set a new remotewipe status for the device * * @param string $devId The device id. * @param string $status A Horde_ActiveSync::RWSTATUS_* constant. * * @throws Horde_ActiveSync_Exception */ public function setDeviceRWStatus($devId, $status) { $query = 'UPDATE ' . $this->_syncDeviceTable . ' SET device_rwstatus = ?' . ' WHERE device_id = ?'; $values = array($status, $devId); try { $this->_db->update($query, $values); } catch (Horde_Db_Exception $e) { throw new Horde_ActiveSync_Exception($e); } if ($status == Horde_ActiveSync::RWSTATUS_PENDING) { // Need to clear the policykey to force a PROVISION. Clear ALL // entries, to ensure the device is wiped. $query = 'UPDATE ' . $this->_syncUsersTable . ' SET device_policykey = 0 WHERE device_id = ?'; try { $this->_db->update($query, array($devId)); } catch (Horde_Db_Exception $e) { throw new Horde_ActiveSync_Exception($e); } } } /** * Explicitly remove a state from storage. * * @param array $options An options array containing at least one of: * - synckey: (string) Remove only the state associated with this synckey. * DEFAULT: All synckeys are removed for the specified device. * - devId: (string) Remove all information for this device. * DEFAULT: None. If no device, a synckey is required. * - user: (string) Restrict to removing data for this user only. * DEFAULT: None - all users for the specified device are removed. * - id: (string) When removing device state, restrict ro removing data * only for this collection. * * @throws Horde_ActiveSyncException */ public function removeState(array $options) { $state_query = 'DELETE FROM ' . $this->_syncStateTable . ' WHERE'; $map_query = 'DELETE FROM %TABLE% WHERE'; // If the device is flagged as wiped, and we are removing the state, // we MUST NOT restrict to user since it will not remove the device's // device table entry, and the device will continue to be wiped each // time it connects. if (!empty($options['devId']) && !empty($options['user'])) { $q = 'SELECT device_rwstatus FROM ' . $this->_syncDeviceTable . ' WHERE device_id = ?'; try { $results = $this->_db->selectValue($q, array($options['devId'])); if ($results != Horde_ActiveSync::RWSTATUS_NA && $results != Horde_ActiveSync::RWSTATUS_OK) { return $this->removeState(array('devId' => $options['devId'])); } } catch (Horde_Db_Exception $e) { throw new Horde_ActiveSync_Exception($e); } $state_query .= ' sync_devid = ? AND sync_user = ?'; $map_query .= ' sync_devid = ? AND sync_user = ?'; $user_query = 'DELETE FROM ' . $this->_syncUsersTable . ' WHERE device_id = ? AND device_user = ?'; $state_values = $values = array($options['devId'], $options['user']); if (!empty($options['id'])) { $state_query .= ' AND sync_folderid = ?'; $map_query .= ' AND sync_folderid = ?'; $state_values[] = $options['id']; $this->_logger->info(sprintf( '[%s] Removing device %s state for user %s and collection %s.', $this->_procid, $options['devId'], $options['user'], $options['id']) ); } else { $this->_logger->info(sprintf( '[%s] Removing device %s state for user %s.', $this->_procid, $options['devId'], $options['user']) ); $this->deleteSyncCache($options['devId'], $options['user']); } } elseif (!empty($options['devId'])) { $state_query .= ' sync_devid = ?'; $map_query .= ' sync_devid = ?'; $user_query = 'DELETE FROM ' . $this->_syncUsersTable . ' WHERE device_id = ?'; $device_query = 'DELETE FROM ' . $this->_syncDeviceTable . ' WHERE device_id = ?'; $state_values = $values = array($options['devId']); $this->_logger->info(sprintf( '[%s] Removing all device state for device %s.', $this->_procid, $options['devId']) ); $this->deleteSyncCache($options['devId']); } elseif (!empty($options['user'])) { $state_query .= ' sync_user = ?'; $map_query .= ' sync_user = ?'; $user_query = 'DELETE FROM ' . $this->_syncUsersTable . ' WHERE device_user = ?'; $state_values = $values = array($options['user']); $this->_logger->info(sprintf( '[%s] Removing all device state for user %s.', $this->_procid, $options['user']) ); $this->deleteSyncCache(null, $options['user']); } elseif (!empty($options['synckey'])) { $state_query .= ' sync_key = ?'; $map_query .= ' sync_key = ?'; $state_values = $values = array($options['synckey']); $this->_logger->info(sprintf( '[%s] Removing device state for sync_key %s only.', $this->_procid, $options['synckey']) ); } else { return; } try { $this->_db->delete($state_query, $state_values); $this->_db->delete( str_replace('%TABLE%', $this->_syncMapTable, $map_query), $state_values); $this->_db->delete( str_replace('%TABLE%', $this->_syncMailMapTable, $map_query), $state_values); if (!empty($user_query)) { $this->_db->delete($user_query, $values); } if (!empty($device_query)) { $this->_db->delete($device_query, $values); } elseif (!empty($user_query)) { $sql = 'SELECT t1.device_id FROM horde_activesync_device t1 ' . 'LEFT JOIN horde_activesync_device_users t2 ' . 'ON t1.device_id = t2.device_id WHERE t2.device_id IS NULL'; try { $devids = $this->_db->selectValues($sql); foreach ($devids as $id) { $this->_db->delete( 'DELETE FROM horde_activesync_device WHERE device_id = ?', array($id)); } } catch (Horde_Db_Exception $e) { throw new Horde_ActiveSync_Exception($e->getMessage()); } } } catch (Horde_Db_Exception $e) { throw new Horde_ActiveSync_Exception($e); } } /** * Check and see that we didn't already see the incoming change from the client. * This would happen e.g., if the client failed to receive the server response * after successfully importing new messages. * * @param string $id The client id sent during message addition. * * @return string The UID for the given clientid, null if none found. * @throws Horde_ActiveSync_Exception */ public function isDuplicatePIMAddition($id) { $sql = 'SELECT message_uid FROM ' . $this->_syncMapTable . ' WHERE sync_clientid = ? AND sync_user = ?'; try { $uid = $this->_db->selectValue($sql, array($id, $this->_deviceInfo->user)); return $uid; } catch (Horde_Db_Exception $e) { throw new Horde_ActiveSync_Exception($e); } } /** * Check if the UID provided was altered during the SYNC_KEY provided. * * @param string $uid The UID to check. * @param string $synckey The synckey to check. * * @return boolean True if the provided UID was updated during the * SYNC for the synckey provided. * @since 2.31.0 */ public function isDuplicatePIMChange($uid, $synckey) { $sql = 'SELECT count(*) FROM ' . $this->_syncMapTable . ' WHERE message_uid = ? AND sync_user = ? AND sync_key = ?'; try { return $this->_db->selectValue($sql, array($uid, $this->_deviceInfo->user, $synckey)); } catch (Horde_Db_Exception $e) { throw new Horde_ActiveSync_Exception($e); } } /** * Return the sync cache. * * @param string $devid The device id. * @param string $user The user id. * @param array $fields An array of fields to return. Default is to return * the full cache. @since 2.9.0 * * @return array The current sync cache for the user/device combination. * @throws Horde_ActiveSync_Exception */ public function getSyncCache($devid, $user, array $fields = null) { $sql = 'SELECT cache_data FROM ' . $this->_syncCacheTable . ' WHERE cache_devid = ? AND cache_user = ?'; try { $data = $this->_db->selectValue($sql, array($devid, $user)); $columns = $this->_db->columns($this->_syncCacheTable); $data = $columns['cache_data']->binaryToString($data); } catch (Horde_Db_Exception $e) { throw new Horde_ActiveSync_Exception($e); } if (!$data = unserialize($data)) { $data = array( 'confirmed_synckeys' => array(), 'lasthbsyncstarted' => false, 'lastsyncendnormal' => false, 'timestamp' => false, 'wait' => false, 'hbinterval' => false, 'folders' => array(), 'hierarchy' => false, 'collections' => array(), 'pingheartbeat' => false, 'synckeycounter' => array()); } if (!is_null($fields)) { $data = array_intersect_key($data, array_flip($fields)); } return $data; } /** * Save the provided sync_cache. * * @param array $cache The cache to save. * @param string $devid The device id. * @param string $user The user id. * @param array $dirty An array of dirty properties. @since 2.9.0 * * @throws Horde_ActiveSync_Exception */ public function saveSyncCache(array $cache, $devid, $user, array $dirty = null) { $cache['timestamp'] = strval($cache['timestamp']); $sql = 'SELECT count(*) FROM ' . $this->_syncCacheTable . ' WHERE cache_devid = ? AND cache_user = ?'; try { $have = $this->_db->selectValue($sql, array($devid, $user)); } catch (Horde_Db_Exception $e) { $this->_logger->err(sprintf( '[%s] %s', $this->_procid, $e->getMessage()) ); throw new Horde_ActiveSync_Exception($e); } $cache = serialize($cache); if ($have) { $this->_logger->info( sprintf('[%s] Replacing SYNC_CACHE entry for user %s and device %s: %s', $this->_procid, $user, $devid, $cache)); $sql = 'UPDATE ' . $this->_syncCacheTable . ' SET cache_data = ? WHERE cache_devid = ? AND cache_user = ?'; try { $this->_db->update( $sql, array($cache, $devid, $user) ); } catch (Horde_Db_Exception $e) { $this->_logger->err(sprintf( '[%s] %s', $this->_procid, $e->getMessage()) ); throw new Horde_ActiveSync_Exception($e); } } else { $this->_logger->info( sprintf('[%s] Adding new SYNC_CACHE entry for user %s and device %s: %s', $this->_procid, $user, $devid, $cache)); $sql = 'INSERT INTO ' . $this->_syncCacheTable . ' (cache_data, cache_devid, cache_user) VALUES (?, ?, ?)'; try { $this->_db->insert( $sql, array($cache, $devid, $user) ); } catch (Horde_Db_Exception $e) { $this->_logger->err(sprintf( '[%s] %s', $this->_procid, $e->getMessage()) ); throw new Horde_ActiveSync_Exception($e); } } } /** * Delete a complete sync cache * * @param string $devid The device id * @param string $user The user name. * * @throws Horde_ActiveSync_Exception */ public function deleteSyncCache($devid, $user = null) { $this->_logger->info(sprintf( '[%s] Horde_ActiveSync_State_Sql::deleteSyncCache(%s, %s)', $this->_procid, $devid, $user)); $sql = 'DELETE FROM ' . $this->_syncCacheTable . ' WHERE '; $params = array(); if (!empty($devid)) { $sql .= 'cache_devid = ? '; $params[] = $devid; } if (!empty($user)) { $sql .= (!empty($devid) ? 'AND ' : '') . 'cache_user = ?'; $params[] = $user; } try { $this->_db->delete($sql, $params); } catch (Horde_Db_Exception $e) { throw new Horde_ActiveSync_Exception($e); } } /** * Return an array of timestamps from the map table for the last * client-initiated change for the provided uid. Used to avoid mirroring back * changes to the client that it sent to the server. * * @param array $changes The changes array, each entry a hash containing * 'id' and 'type' keys. * * @return array An array of UID -> timestamp of the last client-initiated * change for the specified UIDs, or null if none found. */ protected function _getPIMChangeTS(array $changes) { $sql = 'SELECT message_uid, MAX(sync_modtime) FROM ' . $this->_syncMapTable . ' WHERE sync_devid = ? AND sync_user = ? AND sync_key IN (?, ?) '; // Get the allowed synckeys to include. $uuid = self::getSyncKeyUid($this->_syncKey); $cnt = self::getSyncKeyCounter($this->_syncKey); $values = array($this->_deviceInfo->id, $this->_deviceInfo->user); foreach (array($uuid . $cnt, $uuid . ($cnt - 1)) as $v) { $values[] = $v; } $conditions = array(); foreach ($changes as $change) { $d = $change['type'] == Horde_ActiveSync::CHANGE_TYPE_DELETE; $conditions[] = '(message_uid = ?' . ($d ? ' AND sync_deleted = ?) ' : ') '); $values[] = $change['id']; if ($d) { $values[] = $d; } } $sql .= 'AND (' . implode('OR ', $conditions) . ') GROUP BY message_uid'; try { return $this->_db->selectAssoc($sql, $values); } catch (Horde_Db_Exception $e) { throw new Horde_ActiveSync_Exception($e); } } /** * Check for the existence of ANY entries in the map table for this device * and user. * * An extra database query for each sync, but the payoff is that we avoid * having to stat every message change we send to the client if there are no * client generated changes for this sync period. * * @return boolean * @throws Horde_ActiveSync_Exception */ protected function _havePIMChanges() { // No benefit from making this extra query for email collections. if ($this->_collection['class'] == Horde_ActiveSync::CLASS_EMAIL) { return true; } $sql = 'SELECT COUNT(*) FROM ' . $this->_syncMapTable . ' WHERE sync_devid = ? AND sync_user = ? AND sync_folderid = ?'; try { return (bool)$this->_db->selectValue( $sql, array($this->_deviceInfo->id, $this->_deviceInfo->user, $this->_collection['serverid'])); } catch (Horde_Db_Exception $e) { throw new Horde_ActiveSync_Exception($e); } } /** * Return all available mailMap changes for the current folder. * * @param array $changes The changes array * * @return array An array of hashes, each in the form of * {uid} => array( * Horde_ActiveSync::CHANGE_TYPE_FLAGS => true|false, * Horde_ActiveSync::CHANGE_TYPE_DELETE => true|false * ) */ protected function _getMailMapChanges(array $changes) { $sql = 'SELECT message_uid, sync_read, sync_flagged, sync_deleted,' . 'sync_changed FROM ' . $this->_syncMailMapTable . ' WHERE sync_folderid = ? AND sync_devid = ?' . ' AND sync_user = ? AND message_uid IN ' . '(' . implode(',', array_fill(0, count($changes), '?')) . ')'; $ids = array(); foreach ($changes as $change) { $ids[] = $change['id']; } $values = array_merge( array($this->_collection['serverid'], $this->_deviceInfo->id, $this->_deviceInfo->user), $ids); try { $rows = $this->_db->selectAll($sql, $values); } catch (Horde_Db_Exception $e) { throw new Horde_ActiveSync_Exception($e); } $results = array(); foreach ($rows as $row) { foreach ($changes as $change) { if ($change['id'] == $row['message_uid']) { switch ($change['type']) { case Horde_ActiveSync::CHANGE_TYPE_FLAGS: $results[$row['message_uid']][$change['type']] = (!is_null($row['sync_read']) && $row['sync_read'] == $change['flags']['read']) || (!is_null($row['sync_flagged'] && $row['sync_flagged'] == $change['flags']['flagged'])); continue 3; case Horde_ActiveSync::CHANGE_TYPE_DELETE: $results[$row['message_uid']][$change['type']] = !is_null($row['sync_deleted']) && $row['sync_deleted'] == true; continue 3; case Horde_ActiveSync::CHANGE_TYPE_CHANGE: $results[$row['message_uid']][$change['type']] = !is_null($row['sync_changed']) && $row['sync_changed'] == true; continue 3; } } } } return $results; } /** * Garbage collector - clean up from previous sync requests. * * @param string $syncKey The sync key * * @throws Horde_ActiveSync_Exception */ protected function _gc($syncKey) { if (!preg_match('/^\{([0-9A-Za-z-]+)\}([0-9]+)$/', $syncKey, $matches)) { return; } $guid = $matches[1]; $n = $matches[2]; // Clean up all but the last 2 syncs for any given sync series, this // ensures that we can still respond to SYNC requests for the previous // key if the client never received the new key in a SYNC response. $sql = 'SELECT sync_key FROM ' . $this->_syncStateTable . ' WHERE sync_devid = ? AND sync_folderid = ?'; $values = array( $this->_deviceInfo->id, !empty($this->_collection['id']) ? $this->_collection['id'] : Horde_ActiveSync::CHANGE_TYPE_FOLDERSYNC); try { $results = $this->_db->selectAll($sql, $values); } catch (Horde_Db_Exception $e) { $this->_logger->err(sprintf( '[%s] %s', $this->_procid, $e->getMessage()) ); throw new Horde_ActiveSync_Exception($e); } $remove = array(); $guids = array($guid); foreach ($results as $oldkey) { if (preg_match('/^\{([0-9A-Za-z-]+)\}([0-9]+)$/', $oldkey['sync_key'], $matches)) { if ($matches[1] == $guid && $matches[2] < ($n - 1)) { $remove[] = $oldkey['sync_key']; } } else { /* stale key from previous key series */ $remove[] = $oldkey['sync_key']; $guids[] = $matches[1]; } } if (count($remove)) { $sql = 'DELETE FROM ' . $this->_syncStateTable . ' WHERE sync_key IN (' . str_repeat('?,', count($remove) - 1) . '?)'; try { $this->_db->delete($sql, $remove); } catch (Horde_Db_Exception $e) { $this->_logger->err(sprintf( '[%s] %s', $this->_procid, $e->getMessage()) ); throw new Horde_ActiveSync_Exception($e); } } // Also clean up the map table since this data is only needed for one // SYNC cycle. Keep the same number of old keys for the same reasons as // above. foreach (array($this->_syncMapTable, $this->_syncMailMapTable) as $table) { $remove = array(); $sql = 'SELECT DISTINCT sync_key FROM ' . $table . ' WHERE sync_devid = ? AND sync_user = ?'; try { $maps = $this->_db->selectValues( $sql, array($this->_deviceInfo->id, $this->_deviceInfo->user) ); } catch (Horde_Db_Exception $e) { $this->_logger->err(sprintf( '[%s] %s', $this->_procid, $e->getMessage()) ); throw new Horde_ActiveSync_Exception($e); } foreach ($maps as $key) { if (preg_match('/^\{([0-9A-Za-z-]+)\}([0-9]+)$/', $key, $matches)) { if ($matches[1] == $guid && $matches[2] < $n) { $remove[] = $key; } } } if (count($remove)) { $sql = 'DELETE FROM ' . $table . ' WHERE sync_key IN (' . str_repeat('?,', count($remove) - 1) . '?)'; try { $this->_db->delete($sql, $remove); } catch (Horde_Db_Exception $e) { $this->_logger->err(sprintf( '[%s] %s', $this->_procid, $e->getMessage()) ); throw new Horde_ActiveSync_Exception($e); } } } } /** * Reset the sync state for this device, for the specified collection. * * @param string $id The collection to reset. * * @return void * @throws Horde_ActiveSync_Exception */ protected function _resetDeviceState($id) { $this->_logger->info(sprintf( '[%s] Resetting device state for device: %s, user: %s, and collection: %s.', $this->_procid, $this->_deviceInfo->id, $this->_deviceInfo->user, $id)); $state_query = 'DELETE FROM ' . $this->_syncStateTable . ' WHERE sync_devid = ? AND sync_folderid = ? AND sync_user = ?'; $map_query = 'DELETE FROM ' . $this->_syncMapTable . ' WHERE sync_devid = ? AND sync_folderid = ? AND sync_user = ?'; $mailmap_query = 'DELETE FROM ' . $this->_syncMailMapTable . ' WHERE sync_devid = ? AND sync_folderid = ? AND sync_user = ?'; try { $this->_db->delete($state_query, array($this->_deviceInfo->id, $id, $this->_deviceInfo->user)); $this->_db->delete($map_query, array($this->_deviceInfo->id, $id, $this->_deviceInfo->user)); $this->_db->delete($mailmap_query, array($this->_deviceInfo->id, $id, $this->_deviceInfo->user)); } catch (Horde_Db_Exception $e) { throw new Horde_ActiveSync_Exception($e); } // Remove the collection data from the synccache as well. $cache = new Horde_ActiveSync_SyncCache($this, $this->_deviceInfo->id, $this->_deviceInfo->user, $this->_logger); if ($id != Horde_ActiveSync::REQUEST_TYPE_FOLDERSYNC) { $cache->removeCollection($id, false); } else { $this->_logger->notice(sprintf( '[%s] Clearing foldersync state from synccache.', $this->_procid)); $cache->clearFolders(); $cache->clearCollections(); $cache->hierarchy = '0'; } $cache->save(); } } Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Wbxml/Decoder.php0000664000076500000240000005372112654565405020761 0ustar * @package ActiveSync */ /** * ActiveSync specific WBXML decoder. * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2009-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync */ class Horde_ActiveSync_Wbxml_Decoder extends Horde_ActiveSync_Wbxml { /** * Store the wbxml version value. Used to verify we have a valid wbxml * input stream. * * @todo H6 Make this (and most of the other) properties protected. * * @var integer */ public $version; public $publicid; public $publicstringid; public $charsetid; public $stringtable; /** * Temporary string buffer * * @var stream */ protected $_buffer; /** * Flag to indicate we have a valid wbxml input stream * * @var boolean */ protected $_isWbxml; protected $_attrcp = 0; protected $_ungetbuffer; protected $_readHeader = false; /** * Cache the last successfully fetched start tag array. Used to be able * to easily detected emtpy nodes after the element was already fetched. * * @var array */ protected $_lastStartElement; /** * Start reading the wbxml stream, pulling off the initial header and * populate the properties. */ public function readWbxmlHeader() { $this->_readHeader = true; $this->_readVersion(); if ($this->version != self::WBXML_VERSION) { // Not Wbxml - save the byte we already read. $this->_getTempStream(); $this->_buffer->add(chr($this->version)); $this->_isWbxml = false; return; } else { $this->_isWbxml = true; } $this->publicid = $this->_getMBUInt(); if ($this->publicid == 0) { $this->publicstringid = $this->_getMBUInt(); } $this->charsetid = $this->_getMBUInt(); $this->stringtable = $this->_getStringTable(); } /** * Check that the input stream contains wbxml. Basically looks for a valid * WBXML_VERSION header. self::readWbxmlHeader MUST have been called already. * * @return boolean */ public function isWbxml() { if (!$this->_readHeader) { throw new Horde_ActiveSync_Exception('Failed to read WBXML header prior to calling isWbxml()'); } return $this->_isWbxml; } /** * Return the full, raw, input stream. Used for things like SendMail request * where we don't have wbxml to parse. The calling code is responsible for * closing the stream. * * @return resource */ public function getFullInputStream() { // Ensure the buffer was created $this->_getTempStream(); // Need to read the stream into memeory since php://input isn't // always seekable (Bug: 13160); $this->_buffer->add($this->_stream->getString()); $this->_buffer->rewind(); return $this->_buffer->stream; } /** * Returns either start, content or end, and auto-concatenates successive * content. * * @return array|boolean The element structure or false on failure. */ public function getElement() { $element = $this->getToken(); switch ($element[self::EN_TYPE]) { case self::EN_TYPE_STARTTAG: return $element; case self::EN_TYPE_ENDTAG: return $element; case self::EN_TYPE_CONTENT: while (1) { $next = $this->getToken(); if ($next == false) { return false; } elseif ($next[self::EN_TYPE] == self::EN_CONTENT) { $element[self::EN_CONTENT] .= $next[self::EN_CONTENT]; } else { $this->_ungetElement($next); break; } } return $element; } return false; } /** * Returns whether or not the passed in element array represents an * empty tag. * * @param array $el The element array. * * @return boolean True if $el is an empty start tag, otherwise false. */ public function isEmptyElement($el) { if (!is_array($el)) { return false; } switch ($el[self::EN_TYPE]) { case self::EN_TYPE_STARTTAG: return !($el[self::EN_FLAGS] & self::EN_FLAGS_CONTENT); default: // Not applicable. return false; } } /** * Returns the last element array fetched using getElementStartTag() * * @return array|boolean The element array, or false if none available. */ public function getLastStartElement() { return $this->_lastStartElement; } /** * Peek at the next element in the stream. * * @return array The next element in the stream. */ public function peek() { $element = $this->getElement(); $this->_ungetElement($element); return $element; } /** * Get the next tag, which is assumed to be a start tag. * * @param string $tag The element that this should be a start tag for. * * @return array|boolean The start tag array | false on failure. */ public function getElementStartTag($tag) { $element = $this->getToken(); if ($element[self::EN_TYPE] == self::EN_TYPE_STARTTAG && $element[self::EN_TAG] == $tag) { $this->_lastStartElement = $element; return $element; } else { $this->_lastStartElement = false; $this->_ungetElement($element); } return false; } /** * Get the next tag, which is assumed to be an end tag. * * @return array|boolean The element array | false on failure. */ public function getElementEndTag() { $element = $this->getToken(); if ($element[self::EN_TYPE] == self::EN_TYPE_ENDTAG) { return $element; } else { $this->_logger->err(sprintf( '[%s] Unmatched end tag:', $this->_procid)); $this->_logger->err(print_r($element, true)); $this->_ungetElement($element); } return false; } /** * Get the element contents * * @return mixed The content of the current element | false on failure. */ public function getElementContent() { $element = $this->getToken(); if ($element[self::EN_TYPE] == self::EN_TYPE_CONTENT) { return $element[self::EN_CONTENT]; } $this->_ungetElement($element); return false; } /** * Get the next [start | content | end] tag. * * @return array The next, complete, token array. */ public function getToken() { // See if there's something in the ungetBuffer if ($this->_ungetbuffer) { $element = $this->_ungetbuffer; $this->_ungetbuffer = false; return $element; } $el = $this->_getToken(); $this->_logToken($el); return $el; } /** * Log the token. * * @param array The element array. * * @return void */ protected function _logToken($el) { switch ($el[self::EN_TYPE]) { case self::EN_TYPE_STARTTAG: if ($el[self::EN_FLAGS] & self::EN_FLAGS_CONTENT) { $spaces = str_repeat(' ', count($this->_logStack)); $this->_logStack[] = $el[self::EN_TAG]; $this->_logger->debug(sprintf( '[%s] I %s <%s>', $this->_procid, $spaces, $el[self::EN_TAG])); } else { $spaces = str_repeat(' ', count($this->_logStack)); $this->_logger->debug(sprintf( '[%s] I %s <%s />', $this->_procid, $spaces, $el[self::EN_TAG])); } break; case self::EN_TYPE_ENDTAG: $tag = array_pop($this->_logStack); $spaces = str_repeat(' ', count($this->_logStack)); $this->_logger->debug(sprintf( '[%s] I %s ', $this->_procid, $spaces, $tag)); break; case self::EN_TYPE_CONTENT: $spaces = str_repeat(' ', count($this->_logStack) + 1); if ($this->_logLevel == self::LOG_PROTOCOL && ($l = Horde_String::length($el[self::EN_CONTENT])) > self::LOG_MAXCONTENT) { $this->_logger->debug(sprintf( '[%s] I %s %s', $this->_procid, $spaces, sprintf('[%d bytes of content]', $l) )); } else { $this->_logger->debug(sprintf( '[%s] I %s %s', $this->_procid, $spaces, $el[self::EN_CONTENT]) ); } break; } } /** * Get the next start tag, content or end tag * * @return array The element array. */ protected function _getToken() { // Get the data from the input stream $element = array(); while (1) { $byte = $this->_getByte(); if (!isset($byte)) { break; } switch ($byte) { case self::SWITCH_PAGE: $this->_tagcp = $this->_getByte(); continue; case self::END: $element[self::EN_TYPE] = self::EN_TYPE_ENDTAG; return $element; case self::ENTITY: $entity = $this->_getMBUInt(); $element[self::EN_TYPE] = self::EN_TYPE_CONTENT; $element[self::EN_CONTENT] = $this->entityToCharset($entity); return $element; case self::STR_I: $element[self::EN_TYPE] = self::EN_TYPE_CONTENT; $element[self::EN_CONTENT] = $this->_getTermStr(); return $element; case self::LITERAL: $element[self::EN_TYPE] = self::EN_TYPE_STARTTAG; $element[self::EN_TAG] = $this->_getStringTableEntry($this->_getMBUInt()); $element[self::EN_FLAGS] = 0; return $element; case self::EXT_I_0: case self::EXT_I_1: case self::EXT_I_2: $this->_getTermStr(); // Ignore extensions continue; case self::PI: // Ignore PI $this->_getAttributes(); continue; case self::LITERAL_C: $element[self::EN_TYPE] = self::EN_TYPE_STARTTAG; $element[self::EN_TAG] = $this->_getStringTableEntry($this->_getMBUInt()); $element[self::EN_FLAGS] = self::EN_FLAGS_CONTENT; return $element; case self::EXT_T_0: case self::EXT_T_1: case self::EXT_T_2: $this->_getMBUInt(); // Ingore extensions; continue; case self::STR_T: $element[self::EN_TYPE] = self::EN_TYPE_CONTENT; $element[self::EN_CONTENT] = $this->_getStringTableEntry($this->_getMBUInt()); return $element; case self::LITERAL_A: $element[self::EN_TYPE] = self::EN_TYPE_STARTTAG; $element[self::EN_TAG] = $this->_getStringTableEntry($this->_getMBUInt()); $element[self::EN_ATTRIBUTES] = $this->_getAttributes(); $element[self::EN_FLAGS] = self::EN_FLAGS_ATTRIBUTES; return $element; case self::EXT_0: case self::EXT_1: case self::EXT_2: continue; case self::OPAQUE: $length = $this->_getMBUInt(); $element[self::EN_TYPE] = self::EN_TYPE_CONTENT; $element[self::EN_CONTENT] = $this->_getOpaque($length); return $element; case self::LITERAL_AC: $element[self::EN_TYPE] = self::EN_TYPE_STARTTAG; $element[self::EN_TAG] = $this->_getStringTableEntry($this->_getMBUInt()); $element[self::EN_ATTRIBUTES] = $this->_getAttributes(); $element[self::EN_FLAGS] = self::EN_FLAGS_ATTRIBUTES | self::EN_FLAGS_CONTENT; return $element; default: $element[self::EN_TYPE] = self::EN_TYPE_STARTTAG; $element[self::EN_TAG] = $this->_getMapping($this->_tagcp, $byte & 0x3f); $element[self::EN_FLAGS] = ($byte & 0x80 ? self::EN_FLAGS_ATTRIBUTES : 0) | ($byte & 0x40 ? self::EN_FLAGS_CONTENT : 0); if ($byte & 0x80) { $element[self::EN_ATTRIBUTES] = $this->_getAttributes(); } return $element; } } } /** * Unget the specified element from the stream. Places the element into * the unget buffer. * * @param array $element The element array to unget. * * @return void */ public function _ungetElement($element) { if ($this->_ungetbuffer) { $this->_logger->err('Double unget!'); } $this->_ungetbuffer = $element; } /** * Read the Wbxml version header byte, and buffer the input incase we * need the full stream later. */ protected function _readVersion() { $b = $this->_getByte(); if ($b != NULL) { $this->version = $b; } } /** * Get the element attributes * * @return mixed The value of the element's attributes. */ protected function _getAttributes() { $attributes = array(); $attr = ''; while (1) { $byte = $this->_getByte(); if (count($byte) == 0) { break; } switch($byte) { case self::SWITCH_PAGE: $this->_attrcp = $this->_getByte(); break; case self::END: if ($attr != '') { $attributes += $this->_splitAttribute($attr); } return $attributes; case self::ENTITY: $entity = $this->_getMBUInt(); $attr .= $this->entityToCharset($entity); return $element; case self::STR_I: $attr .= $this->_getTermStr(); return $element; case self::LITERAL: if ($attr != '') { $attributes += $this->_splitAttribute($attr); } $attr = $this->_getStringTableEntry($this->_getMBUInt()); return $element; case self::EXT_I_0: case self::EXT_I_1: case self::EXT_I_2: $this->_getTermStr(); continue; case self::PI: case self::LITERAL_C: // Invalid return false; case self::EXT_T_0: case self::EXT_T_1: case self::EXT_T_2: $this->_getMBUInt(); continue; case self::STR_T: $attr .= $this->_getStringTableEntry($this->_getMBUInt()); return $element; case self::LITERAL_A: return false; case self::EXT_0: case self::EXT_1: case self::EXT_2: continue; case self::OPAQUE: $length = $this->_getMBUInt(); $attr .= $this->_getOpaque($length); return $element; case self::LITERAL_AC: return false; default: if ($byte < 128) { if ($attr != '') { $attributes += $this->_splitAttribute($attr); $attr = ''; } } $attr .= $this->_getMapping($this->_attrcp, $byte); break; } } } /** * Parses an attribute string * * @param string $attr The raw attribute value. * * @return array The attribute hash */ protected function _splitAttribute($attr) { $attributes = array(); $pos = strpos($attr,chr(61)); // equals sign if ($pos) { $attributes[substr($attr, 0, $pos)] = substr($attr, $pos+1); } else { $attributes[$attr] = null; } return $attributes; } /** * Get a null terminated string from the stream. * * @return string The string */ protected function _getTermStr() { $str = ''; while(1) { $in = $this->_getByte(); if ($in == 0) { break; } else { $str .= chr($in); } } return $str; } /** * Get an opaque value from the stream of the specified length. * * @param integer $len The length of the data to fetch. * * @return string A string of bytes representing the opaque value. */ protected function _getOpaque($len) { // See http://php.net/fread for why we can't simply use a single fread() // here. Bottom line, for buffered network streams it may be possible // that fread will only return a portion of the stream if chunk // is smaller then $len, so we use a loop to reach $len. $d = ''; while (1) { $l = (($len - strlen($d)) > 8192) ? 8192 : ($len - strlen($d)); if ($l > 0) { $data = $this->_stream->substring(0, $l); // Stream ends prematurely on instable connections and big mails if ($data === false || $this->_stream->eof()) { throw new Horde_ActiveSync_Exception(sprintf( 'Connection unavailable while trying to read %d bytes from stream. Aborting after %d bytes read.', $len, strlen($d))); } else { $d .= $data; } } if (strlen($d) >= $len) { break; } } return $d; } /** * Fetch a single byte from the stream. * * @return string The single byte. */ protected function _getByte() { $ch = $this->_stream->getChar(); if (strlen($ch) > 0) { $ch = ord($ch); return $ch; } else { return; } } /** * Get an MBU integer * * @return integer */ protected function _getMBUInt() { $uint = 0; while (1) { $byte = $this->_getByte(); $uint |= $byte & 0x7f; if ($byte & 0x80) { $uint = $uint << 7; } else { break; } } return $uint; } /** * Fetch the string table. Don't think we use the results anywhere though. * * @return string The string table. */ protected function _getStringTable() { $stringtable = ''; $length = $this->_getMBUInt(); if ($length > 0) { $stringtable = $this->_stream->substring(0, $length); } return $stringtable; } /** * Really don't know for sure what this method is supposed to do, it is * called from numerous places in this class, but the original zpush code * did not contain this method...so, either it's completely broken, or * normal use-cases do not reach the calling code. Either way, it needs to * eventually be fixed. * * @param integer $id The entry to return?? * * @return string */ protected function _getStringTableEntry($id) { throw new Horde_ActiveSync_Exception('Not implemented'); } /** * Get a dtd mapping * * @param integer $cp The codepage to use. * @param integer $id The property. * * @return mixed The mapped value. */ protected function _getMapping($cp, $id) { if (!isset($this->_dtd['codes'][$cp]) || !isset($this->_dtd['codes'][$cp][$id])) { return false; } else { if (isset($this->_dtd['namespaces'][$cp])) { return $this->_dtd['namespaces'][$cp] . ':' . $this->_dtd['codes'][$cp][$id]; } else { return $this->_dtd['codes'][$cp][$id]; } } } /** * Return the temporary buffer stream. * * @return stream */ protected function _getTempStream() { if (!isset($this->_buffer)) { $this->_buffer = new Horde_Stream_Temp(); } return $this->_buffer; } } Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Wbxml/Encoder.php0000664000076500000240000003342512654565405020772 0ustar * @package ActiveSync */ /** * Horde_ActiveSync_Wbxml_Encoder:: Encapsulates all Wbxml encoding from * server to client. * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2009-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync */ class Horde_ActiveSync_Wbxml_Encoder extends Horde_ActiveSync_Wbxml { /** * Cache the tags to output. The stack is output when content() is called. * We only output tags when they actually contain something. i.e. calling * startTag() 10 times, then endTag() will cause 0 bytes of output apart * from the header. * * @var array */ private $_stack = array(); /** * Flag to indicate if we are outputing multipart binary data during e.g., * ITEMOPERATION requests. * * @var boolean */ public $multipart; /** * Collection of parts to send in MULTIPART responses. * * @var array */ protected $_parts = array(); /** * Private stream when handling multipart output * * @var resource */ protected $_tempStream; /** * Const'r * * @param stream $output The output stream * @param integer $log_level The logging level * * @return Horde_ActiveSync_Wbxml_Encoder */ function __construct($output, $log_level = self::LOG_PROTOCOL) { parent::__construct($output, $log_level); /* reverse-map the DTD */ $dtd = array(); foreach ($this->_dtd['namespaces'] as $nsid => $nsname) { $dtd['namespaces'][$nsname] = $nsid; } foreach ($this->_dtd['codes'] as $cp => $value) { $dtd['codes'][$cp] = array(); foreach ($this->_dtd['codes'][$cp] as $tagid => $tagname) { $dtd['codes'][$cp][$tagname] = $tagid; } } $this->_dtd = $dtd; } /** * Starts the wbxml output. * * @param boolean $multipart Indicates we need to output mulitpart binary * binary data. See MS-ASCMD 2.2.1.8.1 * */ public function startWBXML($multipart = false) { $this->multipart = $multipart; if ($multipart) { $this->_tempStream = $this->_stream; $this->_stream = new Horde_Stream_Temp(); } $this->outputWbxmlHeader(); } /** * Output the Wbxml header to the output stream. * */ public function outputWbxmlHeader() { $this->_outByte(0x03); // WBXML 1.3 $this->_outMBUInt(0x01); // Public ID 1 $this->_outMBUInt(106); // UTF-8 $this->_outMBUInt(0x00); // string table length (0) } /** * Start output for the specified tag * * @param string $tag The name of the tag to start * @param mixed $attributes Any attributes for the start tag * @param boolean $output_empty Force output of empty tags * */ public function startTag($tag, $attributes = false, $output_empty = false) { $stackelem = array(); if (!$output_empty) { $stackelem['tag'] = $tag; $stackelem['attributes'] = $attributes; $stackelem['sent'] = false; $this->_stack[] = $stackelem; } else { /* Flush the stack if we want to force empty tags */ $this->_outputStack(); $this->_startTag($tag, $attributes, $output_empty); } } /** * Output the end tag * */ public function endTag() { $stackelem = array_pop($this->_stack); if ($stackelem['sent']) { $this->_endTag(); if (count($this->_stack) == 0 && $this->multipart) { $this->_stream->rewind(); $len = $this->_stream->length(); $totalCount = count($this->_parts) + 1; $header = pack('i', $totalCount); $offset = (($totalCount * 2) * 4) + 4; $header .= pack('ii', $offset, $len); $offset += $len; // start/length of parts foreach ($this->_parts as $bp) { if (is_resource($bp)) { rewind($bp); $stat = fstat($bp); $len = $stat['size']; } else { $len = strlen(bin2hex($bp)) / 2; } $header .= pack('ii', $offset, $len); $offset += $len; } // Output $this->_tempStream->add($header); $this->_stream->rewind(); $this->_tempStream->add($this->_stream); foreach($this->_parts as $bp) { if (is_resource($bp)) { rewind($bp); $this->_tempStream->add($bp); fclose($bp); } else { $this->_tempStream->add($bp); } } $this->_stream->close(); $this->_stream = $this->_tempStream; } } } /** * Output the tag content * * @param mixed $content The value to output for this tag. A string or * a stream resource. */ public function content($content) { // Don't try to send a string containing \0 - it's the wbxml string // terminator. if (!is_resource($content)) { $content = str_replace("\0", '', $content); if ('x' . $content == 'x') { return; } } $this->_outputStack(); $this->_content($content); if (is_resource($content)) { fclose($content); $content = null; } } /** * Add a mulitpart part to be output. * * @param mixed $data The part data. A string or stream resource. */ public function addPart($data) { $this->_parts[] = $data; } /** * Return the parts array. * * @return array */ public function getParts() { return $this->_parts; } /** * Output any tags on the stack that haven't been output yet * */ private function _outputStack() { for ($i = 0; $i < count($this->_stack); $i++) { if (!$this->_stack[$i]['sent']) { $this->_startTag( $this->_stack[$i]['tag'], $this->_stack[$i]['attributes']); $this->_stack[$i]['sent'] = true; } } } /** * Actually outputs the start tag * * @param string $tag @see Horde_ActiveSync_Wbxml_Encoder::startTag * @param mixed $attributes @see Horde_ActiveSync_Wbxml_Encoder::startTag * @param boolean $output_empty @see Horde_ActiveSync_Wbxml_Encoder::startTag */ private function _startTag($tag, $attributes = false, $output_empty = false) { $this->_logStartTag($tag, $attributes, $output_empty); $mapping = $this->_getMapping($tag); if (!$mapping) { return false; } /* Make sure we don't need to switch code pages */ if ($this->_tagcp != $mapping['cp']) { $this->_outSwitchPage($mapping['cp']); $this->_tagcp = $mapping['cp']; } /* Build and send the code */ $code = $mapping['code']; if (isset($attributes) && is_array($attributes) && count($attributes) > 0) { $code |= 0x80; } elseif (!$output_empty) { $code |= 0x40; } $this->_outByte($code); if ($code & 0x80) { $this->_outAttributes($attributes); } } /** * Outputs data * * @param mixed $content A string or stream resource to write to the output */ private function _content($content) { if (!is_resource($content)) { if ($this->_logLevel == self::LOG_PROTOCOL && ($l = Horde_String::length($content)) > self::LOG_MAXCONTENT) { $this->_logContent(sprintf('[%d bytes of content]', $l)); } else { $this->_logContent($content); } } else { $this->_logContent('[STREAM]'); } $this->_outByte(self::STR_I); $this->_outTermStr($content); } /** * Output the endtag * */ private function _endTag() { $this->_logEndTag(); $this->_outByte(self::END); } /** * Output a single byte to the stream * * @param byte $byte The byte to output. */ private function _outByte($byte) { $this->_stream->add(chr($byte)); } /** * Outputs an MBUInt to the stream * * @param $uint The data to write. */ private function _outMBUInt($uint) { while (1) { $byte = $uint & 0x7f; $uint = $uint >> 7; if ($uint == 0) { $this->_outByte($byte); break; } else { $this->_outByte($byte | 0x80); } } } /** * Output a string along with the terminator. * * @param mixed $content A string or a stream resource. */ private function _outTermStr($content) { if (is_resource($content)) { rewind($content); } $this->_stream->add($content); $this->_stream->add(chr(0)); } /** * Output attributes */ private function _outAttributes() { // We don't actually support this, because to do so, we would have // to build a string table before sending the data (but we can't // because we're streaming), so we'll just send an END, which just // terminates the attribute list with 0 attributes. $this->_outByte(self::END); } /** * Switch code page. * * @param integer $page The code page to switch to. */ private function _outSwitchPage($page) { $this->_outByte(self::SWITCH_PAGE); $this->_outByte($page); } /** * Obtain the wbxml mapping for the given tag * * @param string $tag * * @return array */ private function _getMapping($tag) { $mapping = array(); $split = $this->_splitTag($tag); if (isset($split['ns'])) { $cp = $this->_dtd['namespaces'][$split['ns']]; } else { $cp = 0; } $code = $this->_dtd['codes'][$cp][$split['tag']]; $mapping['cp'] = $cp; $mapping['code'] = $code; return $mapping; } /** * Split a tag into it's atomic parts * * @param string $fulltag The full tag name * (e.g. POOMCONTACTS:Email1Address) * * @return array An array containing the namespace and tagname */ private function _splitTag($fulltag) { $ns = false; $pos = strpos($fulltag, chr(58)); // chr(58) == ':' if ($pos) { $ns = substr($fulltag, 0, $pos); $tag = substr($fulltag, $pos+1); } else { $tag = $fulltag; } $ret = array(); if ($ns) { $ret['ns'] = $ns; } $ret['tag'] = $tag; return $ret; } /** * Log the start tag output * * @param string $tag * @param mixed $attr * @param boolean $output_empty * * @return void */ private function _logStartTag($tag, $attr, $output_empty) { $spaces = str_repeat(' ', count($this->_logStack)); if ($output_empty) { $this->_logger->debug(sprintf( '[%s] O %s <%s/>', $this->_procid, $spaces, $tag)); } else { $this->_logStack[] = $tag; $this->_logger->debug(sprintf( '[%s] O %s <%s>', $this->_procid, $spaces, $tag)); } } /** * Log the endtag output * * @return void */ private function _logEndTag() { $spaces = str_repeat(' ', count($this->_logStack) - 1); $tag = array_pop($this->_logStack); $this->_logger->debug(sprintf( '[%s] O %s ', $this->_procid, $spaces, $tag)); } /** * Log the content output * * @param string $content The output * * @return void */ private function _logContent($content) { $spaces = str_repeat(' ', count($this->_logStack)); $this->_logger->debug(sprintf( '[%s] O %s %s', $this->_procid, $spaces, $content)); } }Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Collections.php0000664000076500000240000013510312654565405020574 0ustar * @package ActiveSync */ /** * Horde_ActiveSync_Collections:: Responsible for all functionality related to * collections and managing the sync cache. * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2010-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync * @internal Not intended for use outside of the ActiveSync library. */ class Horde_ActiveSync_Collections implements IteratorAggregate { const COLLECTION_ERR_FOLDERSYNC_REQUIRED = -1; const COLLECTION_ERR_SERVER = -2; const COLLECTION_ERR_STALE = -3; const COLLECTION_ERR_SYNC_REQUIRED = -4; const COLLECTION_ERR_PING_NEED_FULL = -5; /** * The collection data * * @var array */ protected $_collections = array(); /** * Cache a temporary syncCache. * * @var Horde_ActiveSync_SyncCache */ protected $_tempSyncCache; /** * The syncCache * * @var Horde_ActiveSync_SyncCache */ protected $_cache; /** * The logger * * @var Horde_Log_Logger */ protected $_logger; /** * Count of unchanged collections calculated for PARTIAL sync. * * @var integer */ protected $_unchangedCount = 0; /** * Count of available synckeys * * @var integer */ protected $_synckeyCount = 0; /** * Global WINDOWSIZE * Defaults to 100 (MS-ASCMD 2.2.3.188) * * @var integer */ protected $_globalWindowSize = 100; /** * Flag to indicate we have overridden the globalWindowSize * * @var boolean */ protected $_windowsizeOverride = false; /** * Imported changes flag. * * @var boolean */ protected $_importedChanges = false; /** * Short sync request flag. * * @var boolean */ protected $_shortSyncRequest = false; /** * Cache of collections that have had changes detected. * * @var array */ protected $_changedCollections = array(); /** * The ActiveSync server object. * * @var Horde_ActiveSync */ protected $_as; /** * Cache the process id for logging. * * @var integer */ protected $_procid; /** * Cache of changes. * * @var array */ protected $_changes; /** * Flag to indicate the client is requesting a hanging SYNC. * * @var boolean */ protected $_hangingSync = false; /** * Const'r * * @param Horde_ActiveSync_SyncCache $cache The SyncCache. * @param Horde_ActiveSync $as The ActiveSync server object. */ public function __construct( Horde_ActiveSync_SyncCache $cache, Horde_ActiveSync $as) { $this->_cache = $cache; $this->_as = $as; $this->_logger = $as->logger; $this->_procid = getmypid(); } /** * Load all the collections we know about from the cache. */ public function loadCollectionsFromCache() { foreach ($this->_cache->getCollections(false) as $collection) { if (empty($collection['synckey']) && !empty($collection['lastsynckey'])) { $collection['synckey'] = $collection['lastsynckey']; } // Load the class if needed for EAS >= 12.1 if (empty($collection['class'])) { $collection['class'] = $this->getCollectionClass($collection['id']); } if (empty($collection['serverid'])) { try { $collection['serverid'] = $this->getBackendIdForFolderUid($collection['id']); } catch (Horde_ActiveSync_Exception $e) { $this->_logger->err($e->getMessage()); continue; } } $this->_collections[$collection['id']] = $collection; $this->_logger->info(sprintf( '[%s] Loaded %s from the cache.', $this->_procid, $collection['serverid'])); } } /** * Magic... */ public function __call($method, $parameters) { switch ($method) { case 'hasPingChangeFlag': case 'addConfirmedKey': case 'updateCollection': case 'collectionExists': case 'updateWindowSize': return call_user_func_array(array($this->_cache, $method), $parameters); } throw new BadMethodCallException('Unknown method: ' . $method); } /** * Property getter */ public function __get($property) { switch ($property) { case 'hbinterval': case 'wait': case 'confirmed_synckeys': case 'lasthbsyncstarted': case 'lastsyncendnormal': return $this->_cache->$property; case 'importedChanges': case 'shortSyncRequest': case 'hangingSync': $p = '_' . $property; return $this->$p; } throw new InvalidArgumentException('Unknown property: ' . $property); } /** * Property setter. */ public function __set($property, $value) { switch ($property) { case 'importedChanges': case 'shortSyncRequest': case 'hangingSync': $p = '_' . $property; $this->$p = $value; return; case 'lasthbsyncstarted': case 'lastsyncendnormal': case 'hbinterval': case 'wait': $this->_cache->$property = $value; return; case 'confirmed_synckeys': throw new InvalidArgumentException($property . ' is READONLY.'); } throw new InvalidArgumentException('Unknown property: ' . $property); } /** * Get a new collection array, populated with default values. * * @return array */ public function getNewCollection() { return array( 'clientids' => array(), 'fetchids' => array(), 'windowsize' => 100, 'soft' => false, 'conflict' => Horde_ActiveSync::CONFLICT_OVERWRITE_PIM ); } /** * Ensure default OPTIONS values are populated, while not overwriting any * existing values. * * @since 2.20.0 */ public function ensureOptions() { foreach ($this->_collections as &$collection) { $this->_logger->info(sprintf( '[%s] Loading default OPTIONS for %s collection.', $this->_procid, $collection['id'])); if (!isset($collection['mimesupport'])) { $collection['mimesupport'] = Horde_ActiveSync::MIME_SUPPORT_NONE; } if (!isset($collection['bodyprefs'])) { $collection['bodyprefs'] = array(); } } } /** * Add a new populated collection array to the sync cache. * * @param array $collection The collection array. * @param boolean $requireSyncKey Attempt to read missing synckey from * cache if true. If not found, set to 0. */ public function addCollection(array $collection, $requireSyncKey = false) { if ($requireSyncKey && empty($collection['synckey'])) { $cached_collections = $this->_cache->getCollections(false); $collection['synckey'] = !empty($cached_collections[$collection['id']]) ? $cached_collections[$collection['id']]['lastsynckey'] : 0; if ($collection['synckey'] === 0) { $this->_logger->err(sprintf('[%s] Attempting to add a collection to the sync cache while requiring a synckey, but no synckey could be found. Most likely a client error in requesting a collection during PING before it has issued a SYNC.', $this->_procid)); throw new Horde_ActiveSync_Exception_StateGone('Synckey required in Horde_ActiveSync_Collections::addCollection, but none was found.'); } $this->_logger->info(sprintf( '[%s] Obtained synckey for collection %s from cache: %s', $this->_procid, $collection['id'], $collection['synckey'])); } // Load the class if needed for EAS >= 12.1 and ensure we have the // backend folder id. if (empty($collection['class'])) { $collection['class'] = $this->getCollectionClass($collection['id']); } try { $collection['serverid'] = $this->getBackendIdForFolderUid($collection['id']); } catch (Horde_ActiveSync_Exception $e) { throw new Horde_ActiveSync_Exception_StateGone($e->getMessage()); } $this->_collections[$collection['id']] = $collection; $this->_logger->info(sprintf( '[%s] Collection added to collection handler: collection: %s, synckey: %s.', $this->_procid, $collection['serverid'], !empty($collection['synckey']) ? $collection['synckey'] : 'NONE')); } /** * Translate an EAS folder uid into a backend serverid. * * @param $id The uid. * * @return string The backend server id. * @throws Horde_ActiveSync_Exception_FolderGone */ public function getBackendIdForFolderUid($folderid) { // Always use RI for recipient cache. if ($folderid == 'RI') { return $folderid; } $folder = $this->_cache->getFolder($folderid); if ($folder) { return $folder['serverid']; } else { $this->_logger->err( sprintf('[%s] Horde_ActiveSync_Collections::getBackendIdForFolderUid failed because folder was not found in cache.', $this->_procid)); throw new Horde_ActiveSync_Exception_FolderGone('Folder not found in cache.'); } } /** * Translate a backend id E.g., INBOX into an EAS folder uid. * * @param string $folderid The backend id. * * @return string The EAS uid. */ public function getFolderUidForBackendId($folderid) { // Always use 'RI' for Recipient cache. if ($folderid == 'RI') { return $folderid; } $map = $this->_as->state->getFolderUidToBackendIdMap(); if (empty($map[$folderid])) { return false; } return $map[$folderid]; } /** * Return the count of available collections. * * @return integer */ public function collectionCount() { return count($this->_collections); } /** * Return the count of collections in the cache only. * * @return integer */ public function cachedCollectionCount() { return $this->_cache->countCollections(); } /** * Set the getchanges flag on the specified collection. * * @param string $collection_id The collection id. * * @throws Horde_ActiveSync_Exception */ public function setGetChangesFlag($collection_id) { if (empty($this->_collections[$collection_id])) { throw new Horde_ActiveSync_Exception('Missing collection data'); } $this->_collections[$collection_id]['getchanges'] = true; } /** * Get the getchanges flag on the specified collection. * * @param string $collection_id The collection id. * * @return boolean * @throws Horde_ActiveSync_Exception */ public function getChangesFlag($collection_id) { if (empty($this->_collections[$collection_id])) { throw new Horde_ActiveSync_Exception('Missing collection data'); } return !empty($this->_collections[$collection_id]['getchanges']); } /** * Sets the default WINDOWSIZE. * * Note that this is really a ceiling on the number of TOTAL responses * that can be sent (including all collections). This method should be * renamed for 3.0 * * @param integer $window The windowsize * @param boolean $override If true, this value will override any client * supplied value. */ public function setDefaultWindowSize($window, $override = false) { if ($override) { $this->_windowsizeOverride = true; } if ($override || empty($this->_windowsizeOverride)) { $this->_globalWindowSize = $window; } } public function getDefaultWindowSize() { return $this->_globalWindowSize; } /** * Validates the collection data from the syncCache, filling in missing * values from the folder cache. */ public function validateFromCache() { $this->_cache->validateCollectionsFromCache($this->_collections); } /** * Updates data from the cache for collectons that are already loaded. Used * to ensure looping SYNC and PING requests are operating on the most * recent syncKey. */ public function updateCollectionsFromCache() { $this->_cache->refreshCollections(); $collections = $this->_cache->getCollections(); foreach (array_keys($this->_collections) as $id) { if (!empty($collections[$id])) { $this->_logger->info(sprintf( '[%s] Refreshing %s from the cache.', $this->_procid, $id)); $this->_collections[$id] = $collections[$id]; } } } /** * Return a collection class given the collection id. * * @param string $id The collection id. * * @return string|boolean The collection class or false if not found. */ public function getCollectionClass($id) { if ($id == 'RI') { return $id; } // First try existing, loaded collections. if (!empty($this->_collections[$id])) { return $this->_collections[$id]['class']; } // Next look in the SyncCache. if (isset($this->_cache->folders[$id]['class'])) { $class = $this->_cache->folders[$id]['class']; $this->_logger->info(sprintf( '[%s] Obtaining collection class of %s for collection id %s', $this->_procid, $class, $id)); return $class; } return false; } public function getCollectionType($id) { if ($id == 'RI') { return $id; } // First try existing, loaded collections. if (!empty($this->_collections[$collection['id']])) { return $this->_collections[$collection['id']]['type']; } // Next look in the SyncCache. if (isset($this->_cache->folders[$id]['type'])) { $type = $this->_cache->folders[$id]['type']; $this->_logger->info(sprintf( '[%s] Obtaining collection type of %s for collection id %s', $this->_procid, $type, $id)); return $type; } return false; } /** * Determine if we have any syncable collections either locally or in the * sync cache. * * @param long $version The EAS version * * @return boolean */ public function haveSyncableCollections($version) { // Ensure we have syncable collections, using the cache if needed. if ($version >= Horde_ActiveSync::VERSION_TWELVEONE && empty($this->_collections)) { $this->_logger->info('No collections - looking in sync_cache.'); $found = false; foreach ($this->_cache->getCollections() as $value) { if (isset($value['synckey'])) { $this->_logger->info(sprintf( '[%s] Found a syncable collection: %s : %s. Adding it to the collections object.', $this->_procid, $value['serverid'], $value['synckey'])); $this->_collections[$value['id']] = $value; $found = true; } } return $found; } elseif (empty($this->_collections)) { return false; } $this->_logger->info('Have syncable collections'); return true; } /** * Set the looping sync heartbeat values. * * @param array $hb An array containing one or both of: hbinterval, wait. */ public function setHeartbeat(array $hb) { if (isset($hb['wait'])) { $this->_cache->wait = $hb['wait']; } if (isset($hb['hbinterval'])) { $this->_cache->hbinterval = $hb['hbinterval']; } } /** * Return the heartbeat interval. Always returned as the heartbeat (seconds) * not wait interval (minutes). * * @return integer|boolean The number of seconds in a heartbeat, or false * if no heartbeat set. */ public function getHeartbeat() { return !empty($this->_cache->hbinterval) ? $this->_cache->hbinterval : (!empty($this->_cache->wait) ? $this->_cache->wait * 60 : false); } /** * Return whether or not we want a looping sync. We can do a looping sync * if we have no imported changes AND we have either a hbinterval, wait, * or a shortSync. * * @return boolean True if we want a looping sync, false otherwise. */ public function canDoLoopingSync() { return $this->_hangingSync && !$this->_importedChanges && ($this->_shortSyncRequest || $this->_cache->hbinterval !== false || $this->_cache->wait !== false); } /** * Return if the current looping sync is stale. A stale looping sync is one * which has begun earlier than the most recently running sync reported by * the syncCache. * * @return boolean True if the current looping sync is stale. False * otherwise. */ public function checkStaleRequest() { return !$this->_cache->validateCache(true); } /** * Return if we have a current folder hierarchy. * * @return boolean */ public function haveHierarchy() { return isset($this->_cache->hierarchy); } /** * Prepare for a hierarchy sync. * * @param string $synckey The current synckey from the client. * * @return array An array of known folders. */ public function initHierarchySync($synckey) { $this->_as->state->loadState( array(), $synckey, Horde_ActiveSync::REQUEST_TYPE_FOLDERSYNC); // Refresh the cache since it might have changed like e.g., if synckey // was empty. $this->_cache->loadCacheFromStorage(); return $this->_as->state->getKnownFolders(); } /** * Update/Add a folder in the hierarchy cache. * * @param Horde_ActiveSync_Message_Folder $folder The folder object. * @param boolean $update Update the state objects? @since 2.4.0 */ public function updateFolderinHierarchy( Horde_ActiveSync_Message_Folder $folder, $update = false) { $this->_cache->updateFolder($folder); $cols = $this->_cache->getCollections(false); $cols[$folder->serverid]['serverid'] = $folder->_serverid; $this->_cache->updateCollection($cols[$folder->serverid]); if ($update) { $this->_as->state->updateServerIdInState($folder->serverid, $folder->_serverid); } } /** * Delete a folder from the hierarchy cache. * * @param string $id The folder's uid. */ public function deleteFolderFromHierarchy($uid) { $this->_cache->deleteFolder($uid); $this->_as->state->removeState(array( 'id' => $uid, 'devId' => $this->_as->device->id, 'user' => $this->_as->device->user)); } /** * Return all know hierarchy changes. * * @return array An array of changes. */ public function getHierarchyChanges() { return $this->_as->state->getChanges(); } /** * Validate and perform some sanity checks on the hierarchy changes before * being sent to the client. * * @param Horde_ActiveSync_Connector_Exporter $exporter The exporter. * @param array $seenFolders An array of folders. */ public function validateHierarchyChanges(Horde_ActiveSync_Connector_Exporter $exporter, array $seenFolders) { if ($this->_as->device->version < Horde_ActiveSync::VERSION_TWELVEONE || count($exporter->changed)) { return; } // Remove unnecessary changes. foreach ($exporter->changed as $key => $folder) { if (isset($folder->serverid) && $syncFolder = $this->_cache->getFolder($folder->serverid) && in_array($folder->serverid, $seenfolders) && $syncFolder['parentid'] == $folder->parentid && $syncFolder['displayname'] == $folder->displayname && $syncFolder['type'] == $folder->type) { $this->_logger->info(sprintf( '[%s] Ignoring %s from changes because it contains no changes from device.', $this->_procid, $folder->serverid) ); unset($exporter->changed[$key]); $exporter->count--; } } // Remove unnecessary deletions. foreach ($exporter->deleted as $key => $folder) { if (($sid = array_search($folder, $seenfolders)) === false) { $this->_logger->info(sprintf( '[%s] Ignoring %s from deleted list because the device does not know it', $this->_procid, $folder) ); unset($exporter->deleted[$key]); $exporter->count--; } } } /** * Update the hierarchy synckey in the cache. * * @param string $key The new/existing synckey. */ public function updateHierarchyKey($key) { $this->_cache->hierarchy = $key; } /** * Prepares the syncCache for a full sync request. */ public function initFullSync() { $this->_cache->confirmed_synckeys = array(); $this->_cache->clearCollectionKeys(); } /** * Prepare the syncCache for an EMPTY sync request. * * @return boolean False if EMPTY request cannot be performed, otherwise * true. * @since 2.25.0 */ public function initEmptySync() { $this->loadCollectionsFromCache(); foreach ($this->_collections as $value) { // Remove keys from confirmed synckeys array and count them if (isset($value['synckey'])) { if (isset($this->_cache->confirmed_synckeys[$value['synckey']])) { $this->_logger->info(sprintf( 'Removed %s from confirmed_synckeys', $value['synckey']) ); $this->_cache->removeConfirmedKey($value['synckey']); } $this->_synckeyCount++; } } if (!$this->_checkConfirmedKeys()) { $this->_logger->err('Some synckeys were not confirmed, but handling an empty request. Requesting full SYNC'); $this->save(); return false; } $this->shortSyncRequest = true; $this->hangingSync = true; $this->save(true); return true; } /** * Prepares the syncCache for a partial sync request and checks that * it is allowed. * * @return boolean True if parital sync is possible, false otherwise. */ public function initPartialSync() { // PARTIAL is allowed without a tag if the waitinterval, // heartbeat, or windowsize changed. So, short circuit the logic for // checking for changed collections in this case. if (empty($this->_collections)) { $this->_logger->info('No collections in collection handler, loading full collection set from cache.'); $this->loadCollectionsFromCache(); // Need this for all PARTIAL sync requests. $this->_tempSyncCache = clone $this->_cache; foreach ($this->_collections as $value) { // Remove keys from confirmed synckeys array and count them if (isset($value['synckey'])) { if (isset($this->_cache->confirmed_synckeys[$value['synckey']])) { $this->_logger->info(sprintf( 'Removed %s from confirmed_synckeys', $value['synckey']) ); $this->_cache->removeConfirmedKey($value['synckey']); } $this->_synckeyCount++; } } if (!$this->_checkConfirmedKeys()) { $this->_logger->err('Some synckeys were not confirmed. Requesting full SYNC'); $this->save(); return false; } return true; } $this->_tempSyncCache = clone $this->_cache; $c = $this->_tempSyncCache->getCollections(); foreach ($this->_collections as $key => $value) { // Collections from cache might not all have synckeys. if (!empty($c[$key])) { $v1 = $value; foreach ($v1 as $k => $o) { if (is_null($o)) { unset($v1[$k]); } } unset($v1['id'], $v1['serverid'], $v1['clientids'], $v1['fetchids'], $v1['getchanges'], $v1['changeids'], $v1['pingable'], $v1['class'], $v1['synckey'], $v1['lastsynckey']); $v2 = $c[$key]; foreach ($v2 as $k => $o) { if (is_null($o)) { unset($v2[$k]); } } unset($v2['id'], $v2['serverid'], $v2['pingable'], $v2['class'], $v2['synckey'], $v2['lastsynckey']); ksort($v1); if (isset($v1['bodyprefs'])) { ksort($v1['bodyprefs']); foreach (array_keys($v1['bodyprefs']) as $k) { if (is_array($v1['bodyprefs'][$k])) { ksort($v1['bodyprefs'][$k]); } } } ksort($v2); if (isset($v2['bodyprefs'])) { ksort($v2['bodyprefs']); foreach (array_keys($v2['bodyprefs']) as $k) { if (is_array($v2['bodyprefs'][$k])) { ksort($v2['bodyprefs'][$k]); } } } if (md5(serialize($v1)) == md5(serialize($v2))) { $this->_unchangedCount++; } // Unset in tempSyncCache, since we have it from device. // Afterwards, anything left in tempSyncCache needs to be added // to _collections (this is done in // self::_haveNoChangesInPartialSync()). $this->_tempSyncCache->removeCollection($key); // Remove keys from confirmed synckeys array and count them. if (isset($value['synckey'])) { if (isset($this->_cache->confirmed_synckeys[$value['synckey']])) { $this->_logger->info(sprintf( 'Removed %s from confirmed_synckeys', $value['synckey']) ); $this->_cache->removeConfirmedKey($value['synckey']); } $this->_synckeyCount++; } } } if (!$this->_checkConfirmedKeys()) { $this->_logger->err('Some synckeys were not confirmed. Requesting full SYNC'); $this->save(); return false; } if ($this->_haveNoChangesInPartialSync()) { $this->_logger->warn(sprintf( '[%s] Partial Request with completely unchanged collections. Request a full SYNC', $this->_procid)); return false; } return true; } protected function _checkConfirmedKeys() { $csk = $this->_cache->confirmed_synckeys; if ($csk) { $this->_logger->info(sprintf( '[%s] Confirmed Synckeys contains %s', $this->_procid, serialize($csk)) ); return false; } return true; } /** * Return if we can do an empty response * * @return boolean */ public function canSendEmptyResponse() { return !$this->_importedChanges && ($this->_hangingSync && ($this->_cache->wait !== false || $this->_cache->hbinterval !== false)); } /** * Return if we have no changes, but have requested a partial sync. A * partial sync must have either a wait, hbinterval, or some subset of * collections to be valid. * * @return boolean */ protected function _haveNoChangesInPartialSync() { return $this->_synckeyCount > 0 && $this->_unchangedCount == $this->_synckeyCount && $this->_cache->wait == false && $this->_cache->hbinterval == false; } /** * Populate the collections data with missing data from the syncCache. */ public function getMissingCollectionsFromCache() { if (empty($this->_tempSyncCache)) { throw new Horde_ActiveSync_Exception('Did not initialize the PARTIAL sync.'); } // Update _collections with all data that was not sent, but we // have a synckey for in the sync_cache. foreach ($this->_tempSyncCache->getCollections() as $value) { // The collection might have been updated due to incoming // changes. Some clients send COMMANDS in a PARTIAL sync and // initializing the PARTIAL afterwards will overwrite the various // flags stored in $collection['id'][] if (!empty($this->_collections[$value['id']])) { continue; } $this->_logger->info(sprintf( 'Using SyncCache State for %s', $value['serverid'] )); if (empty($value['synckey'])) { $value['synckey'] = $value['lastsynckey']; } $this->_collections[$value['id']] = $value; } } /** * Check for an update FILTERTYPE * * @param string $id The collection id to check * @param string $filter The new filter value. * * @return boolean True if filtertype passed, false if it has changed. */ public function checkFilterType($id, $filter) { $cc = $this->_cache->getCollections(); if (!empty($cc[$id]['filtertype']) && !is_null($filter) && $cc[$id]['filtertype'] != $filter) { $this->_logger->info(sprintf( '[%s] Filtertype change from: %d to %d', $this->_procid, $cc[$id]['filtertype'], $filter)); $this->_cache->updateFiltertype($id, $filter); return false; } return true; } /** * Update the syncCache with current collection data. */ public function updateCache() { foreach ($this->_collections as $value) { $this->_cache->updateCollection($value); } } /** * Save the syncCache to storage. * * @param boolean $preserve_folders If true, ensure the folder cache is not * overwritten. @since 2.18.0 * @todo Refactor this hack away. Requires a complete refactor of the cache. */ public function save($preserve_folders = false) { // HOTFIX. Need to check the timestamp to see if we should reload the // folder cache before saving to ensure it isn't overwritten. See // Bug: 13273 if ($preserve_folders && !$this->_cache->validateCache()) { $this->_logger->info(sprintf('[%s] Updating the foldercache before saving.', $this->_procid)); $this->_cache->refreshFolderCache(); } $this->_cache->save(); } /** * Attempt to initialize the sync state. * * @param array $collection The collection array. * @param boolean $requireSyncKey Require collection to have a synckey and * throw exception if it's not present. * * @throws Horde_ActiveSync_Exception_InvalidRequest * @throws Horde_ActiveSync_Exception_FolderGone */ public function initCollectionState(array &$collection, $requireSyncKey = false) { // Clear the changes cache. $this->_changes = null; // Ensure we have a collection class. if (empty($collection['class'])) { if (!($collection['class'] = $this->getCollectionClass($collection['id']))) { throw new Horde_ActiveSync_Exception_FolderGone('Could not load collection class for ' . $collection['id']); } } // Load the collection's type if we can. if (empty($collection['type'])) { $collection['type'] = $this->getCollectionType($collection['id']); } // Get the backend serverid. if (empty($collection['serverid'])) { $collection['serverid'] = $this->getBackendIdForFolderUid($collection['id']); } if ($requireSyncKey && empty($collection['synckey'])) { throw new Horde_ActiveSync_Exception_InvalidRequest(sprintf( '[%s] Empty synckey for %s.', $this->_procid, $collection['id'])); } // Initialize the state $this->_logger->info(sprintf( '[%s] Initializing state for collection: %s, synckey: %s', $this->_procid, $collection['serverid'], $collection['synckey'])); $this->_as->state->loadState( $collection, $collection['synckey'], Horde_ActiveSync::REQUEST_TYPE_SYNC, $collection['id']); } /** * Poll the backend for changes. * * @param integer $heartbeat The heartbeat lifetime to wait for changes. * @param integer $interval The wait interval between poll iterations. * @param array $options An options array containing any of: * - pingable: (boolean) Only poll collections with the pingable flag set. * DEFAULT: false * * @return boolean|integer True if changes were detected in any of the * collections, false if no changes detected * or a status code if failed. */ public function pollForChanges($heartbeat, $interval, array $options = array()) { $dataavailable = false; $started = time(); $until = $started + $heartbeat; $this->_logger->info(sprintf( 'Waiting for changes for %s seconds', $heartbeat) ); // If pinging, make sure we have pingable collections. Note we can't // filter on them here because the collections might change during the // loop below. if (!empty($options['pingable']) && !$this->havePingableCollections()) { $this->_logger->err('No pingable collections.'); return self::COLLECTION_ERR_SERVER; } // Need to update AND SAVE the timestamp for race conditions to be // detected. $this->lasthbsyncstarted = $started; $this->save(); // We only check for remote wipe request once every 5 iterations to // save on DB load since we must reload the device's state each time. $rw_check_countdown = 5; while (($now = time()) < $until) { // Try not to go over the heartbeat interval. if ($until - $now < $interval) { $interval = $until - $now; } // See if another process has altered the sync_cache. if ($this->checkStaleRequest()) { return self::COLLECTION_ERR_STALE; } // Make sure the collections are still there (there might have been // an error in refreshing them from the cache). Ideally this should // NEVER happen. if (!count($this->_collections)) { $this->_logger->err('NO COLLECTIONS! This should not happen!'); return self::COLLECTION_ERR_SERVER; } // Check for WIPE request once every 5 iterations to balance between // performance and speed of catching a remote wipe request. if ($rw_check_countdown-- == 0) { $rw_check_countdown = 5; if ($this->_as->provisioning != Horde_ActiveSync::PROVISIONING_NONE) { $rwstatus = $this->_as->state->getDeviceRWStatus($this->_as->device->id, true); if ($rwstatus == Horde_ActiveSync::RWSTATUS_PENDING || $rwstatus == Horde_ActiveSync::RWSTATUS_WIPED) { return self::COLLECTION_ERR_FOLDERSYNC_REQUIRED; } } } // Check each collection we are interested in. foreach ($this->_collections as $id => $collection) { // Initialize the collection's state data in the state handler. try { $this->initCollectionState($collection, true); } catch (Horde_ActiveSync_Exception_StateGone $e) { $this->_logger->notice(sprintf( '[%s] State not found for %s. Continuing.', $this->_procid, $id) ); if (!empty($options['pingable'])) { return self::COLLECTION_ERR_PING_NEED_FULL; } $dataavailable = true; $this->setGetChangesFlag($id); continue; } catch (Horde_ActiveSync_Exception_InvalidRequest $e) { // Thrown when state is unable to be initialized because the // collection has not yet been synched, but was requested to // be pinged. $this->_logger->err(sprintf( '[%s] Unable to initialize state for %s. Ignoring during pollForChanges: %s.', $this->_procid, $id, $e->getMessage())); continue; } catch (Horde_ActiveSync_Exception_FolderGone $e) { $this->_logger->warn('Folder gone for collection ' . $collection['id']); return self::COLLECTION_ERR_FOLDERSYNC_REQUIRED; } catch (Horde_ActiveSync_Exception $e) { $this->_logger->err('Error loading state: ' . $e->getMessage()); $this->_as->state->loadState( array(), null, Horde_ActiveSync::REQUEST_TYPE_SYNC, $id); $this->setGetChangesFlag($id); $dataavailable = true; continue; } if (!empty($options['pingable']) && !$this->_cache->collectionIsPingable($id)) { $this->_logger->notice(sprintf( '[%s] Skipping %s because it is not PINGable.', $this->_procid, $id)); continue; } try { if ($cnt = $this->getCollectionChangeCount(true)) { $dataavailable = true; $this->setGetChangesFlag($id); if (!empty($options['pingable'])) { $this->_cache->setPingChangeFlag($id); } } } catch (Horde_ActiveSync_Exception_StaleState $e) { $this->_logger->notice(sprintf( '[%s] SYNC terminating and force-clearing device state: %s', $this->_procid, $e->getMessage()) ); $this->_as->state->loadState( array(), null, Horde_ActiveSync::REQUEST_TYPE_SYNC, $id); $this->setGetChangesFlag($id); $dataavailable = true; } catch (Horde_ActiveSync_Exception_FolderGone $e) { $this->_logger->notice(sprintf( '[%s] SYNC terminating: %s', $this->_procid, $e->getMessage()) ); // If we are missing a folder, we should clear the PING // cache also, to be sure it picks up any hierarchy changes // since most clients don't seem smart enough to figure this // out on their own. $this->resetPingCache(); return self::COLLECTION_ERR_FOLDERSYNC_REQUIRED; } catch (Horde_ActiveSync_Exception $e) { $this->_logger->err(sprintf( '[%s] Sync object cannot be configured, throttling: %s', $this->_procid, $e->getMessage()) ); sleep(30); continue; } } if (!empty($dataavailable)) { $this->_logger->info(sprintf( '[%s] Found changes!', $this->_procid) ); break; } // Wait. $this->_logger->info(sprintf( '[%s] Sleeping for %s seconds.', $this->_procid, $interval)); sleep ($interval); // Refresh the collections. $this->updateCollectionsFromCache(); } // Check that no other Sync process already started // If so, we exit here and let the other process do the export. if ($this->checkStaleRequest()) { $this->_logger->info('Changes in cache determined during Sync Wait/Heartbeat, exiting here.'); return self::COLLECTION_ERR_STALE; } $this->_logger->info(sprintf( '[%s] Looping Sync complete: DataAvailable: %s, DataImported: %s', $this->_procid, $dataavailable, $this->importedChanges) ); return $dataavailable; } /** * Check if we have any pingable collections. * * @return boolean True if we have collections marked as pingable. */ public function havePingableCollections() { foreach (array_keys($this->_collections) as $id) { if ($this->_cache->collectionIsPingable($id)) { return true; } } return false; } /** * Marks all loaded collections with a synckey as pingable. */ public function updatePingableFlag() { $collections = $this->_cache->getCollections(false); foreach ($collections as $id => $collection) { if (!empty($this->_collections[$id]['synckey'])) { $this->_logger->info(sprintf( 'Setting collection %s (%s) PINGABLE.', $collection['serverid'], $id)); $this->_cache->setPingableCollection($id); } else { $this->_logger->info(sprintf( 'UNSETTING collection %s (%s) PINGABLE flag.', $collection['serverid'], $id)); $this->_cache->removePingableCollection($id); } } } /** * Force reset all collection's PINGABLE flag. Used to force client * to issue a non-empty PING request. * */ public function resetPingCache() { $collections = $this->_cache->getCollections(false); foreach ($collections as $id => $collection) { $this->_logger->info(sprintf( 'UNSETTING collection %s (%s) PINGABLE flag.', $collection['serverid'], $id)); $this->_cache->removePingableCollection($id); } } /** * Return any changes for the current collection. * * @param boolean $ping True if this is a PING request, false otherwise. * If true, we only detect that a change has occured, * not the data on all of the changes. * @param array $ensure An array of UIDs that should be sent in the * current response if possible, and not put off * because of a MOREAVAILABLE situation. * @deprecated and no longer used. * * @return array The changes array. */ public function getCollectionChanges($ping = false, array $ensure = array()) { if (empty($this->_changes)) { $this->_changes = $this->_as->state->getChanges(array('ping' => $ping)); } return $this->_changes; } /** * Return the count of the current collection's chagnes. * * @param boolean $ping Only ping the collection if true. * * @return integer The change count. */ public function getCollectionChangeCount($ping = false) { if (empty($this->_changes)) { $this->getCollectionChanges($ping); } return count($this->_changes); } /** * Iterator */ public function getIterator() { return new ArrayIterator($this->_collections); } } Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Credentials.php0000664000076500000240000001002612654565405020547 0ustar * @package ActiveSync */ /** * Provides an abstraction for obtaining the correct EAS credentials. * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2009-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync * @internal Not intended for use outside of the ActiveSync library. * * @property string username The authentication username. * @property-read string password The password, if available. */ class Horde_ActiveSync_Credentials { /** * The server object. * * @var Horde_ActiveSync */ protected $_server; /** * The user's credentials. Username is in index 0 and password is in * index 1. * * @var array */ protected $_credentials = array(); /** * Const'r * * @param Horde_ActiveSync $server The server object. */ public function __construct(Horde_ActiveSync $server) { $this->_server = $server; $this->_credentials = $this->_getCredentials(); } /** * Accessor * * @return string|boolean The value of the requested property, or false * if it does not exist. */ public function __get($property) { switch ($property) { case 'username': return !empty($this->_credentials[0]) ? $this->_credentials[0] : false; case 'password': return !empty($this->_credentials[1]) ? $this->_credentials[1] : false; } } public function __set($property, $value) { switch ($property) { case 'username': $this->_credentials[0] = $value; break; default: throw new InvalidArgumentException(sprintf('%s is not a valid property.', $property)); } } /** * Return the username and password to use for authentication. * * @return array The username in index 0 and password in index 1. */ protected function _getCredentials() { $user = $pass = ''; $serverVars = $this->_server->request->getServerVars(); if (!empty($serverVars['PHP_AUTH_PW'])) { // Standard case, PHP was passed the needed authentication info. $user = $serverVars['PHP_AUTH_USER']; $pass = $serverVars['PHP_AUTH_PW']; } elseif (!empty($serverVars['HTTP_AUTHORIZATION']) || !empty($serverVars['REDIRECT_HTTP_AUTHORIZATION']) || !empty($serverVars['Authorization'])) { $authorization = !empty($serverVars['HTTP_AUTHORIZATION']) ? $serverVars['HTTP_AUTHORIZATION'] : (!empty($serverVars['REDIRECT_HTTP_AUTHORIZATION']) ? $serverVars['REDIRECT_HTTP_AUTHORIZATION'] : $serverVars['Authorization']); $hash = base64_decode(str_replace('Basic ', '', $authorization)); if (strpos($hash, ':') !== false) { list($user, $pass) = explode(':', $hash, 2); } } else { // Might be using X509 certs, so won't have the Auth headers or a // password. $get = $this->_server->getGetVars(); if (!empty($get['User'])) { $user = $get['User']; } } return array($user, $pass); } }Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Device.php0000664000076500000240000007461112654565405017523 0ustar * @package ActiveSync */ /** * Horde_ActiveSync_Device:: Wraps all functionality related to device data. * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2010-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync * * @property string $id The device id. * @property string $deviceType The device type string. * @property string $clientType The client name, if available. * @property integer $rwstatus The RemoteWipe status - a * Horde_ActiveSync::RWSTATUS_* constant. * @property string $userAgent The device's user agent string. * @property string $user The userid for the current device account. * @property array $supported The SUPPORTED data sent from this device. * @property string $policykey The current policykey, if provisioned. * @property array $properties The device properties, sent in DEVICEINFO, * along with any custom properties set. * @property string $announcedVersion The most last EAS supported versions * announced to the device. * @property integer $multiplex Bitmask describing collections that this * device does not support user created * folders for, therefore all sources must * be multiplexed together. Masks are * the MULTIPLEX_* constants. * @property boolean $blocked True if device has been marked as blocked. * */ class Horde_ActiveSync_Device { const MODEL = 'Settings:Model'; const IMEI = 'Settings:IMEI'; const NAME = 'Settings:FriendlyName'; const OS = 'Settings:OS'; const OS_LANGUAGE = 'Settings:OSLanguage'; const PHONE_NUMBER = 'Settings:PhoneNumber'; const OPERATOR = 'Settings:MobileOperator'; const VERSION = 'version'; const MULTIPLEX = 'multiplex'; const ANNOUNCED_VERSION = 'announcedVersion'; const BLOCKED = 'blocked'; // Bitwise constants for flagging device must use multiplexed collections. // @since 2.9.0 const MULTIPLEX_CONTACTS = 1; const MULTIPLEX_CALENDAR = 2; const MULTIPLEX_TASKS = 4; const MULTIPLEX_NOTES = 8; const TYPE_IPHONE = 'iphone'; const TYPE_IPOD = 'ipod'; const TYPE_IPAD = 'ipad'; const TYPE_WEBOS = 'webos'; const TYPE_ANDROID = 'android'; const TYPE_BLACKBERRY = 'blackberry'; const TYPE_WP = 'windowsphone'; const TYPE_TOUCHDOWN = 'touchdown'; const TYPE_UNKNOWN = 'unknown'; const TYPE_NINE = 'nine'; /** * Quirk to specify if the client fails to property ghost the * POOMCONTACTS:Picture field. If this quirk is present, it means we should * add the POOMCONTACTS:Picture field to the SUPPORTED array for this client. */ const QUIRK_NEEDS_SUPPORTED_PICTURE_TAG = 1; /** * Device properties. * * @var array */ protected $_properties = array(); /** * State handler * * @var Horde_ActiveSync_State_Base */ protected $_state; /** * Dirty flag * * @var array */ protected $_dirty = array(); /** * Flag to indicate self::multiplex was set externally. * * @var boolean */ protected $_multiplexSet = false; /** * Local override/cache of detected clientType. * * @var string */ protected $_clientType; /** * Cache of OS version. * * @var string */ protected $_iOSVersion; /** * Const'r * * @param Horde_ActiveSync_State_Base $state The state driver. * @param array $data The current device data. */ public function __construct(Horde_ActiveSync_State_Base $state, array $data = array()) { $this->_state = $state; $this->_properties = $data; } /** * Getter */ public function &__get($property) { switch ($property) { case self::MULTIPLEX: if (!$this->_multiplexSet && empty($this->_properties['properties'][self::MULTIPLEX])) { $this->_sniffMultiplex(); $this->multiplex = $this->_properties['properties'][self::MULTIPLEX]; $this->save(); } case self::ANNOUNCED_VERSION: case self::BLOCKED: return $this->_properties['properties'][$property]; case 'clientType': if (!isset($this->_clientType)) { $this->_clientType = $this->_getClientType(); } return $this->_clientType; case self::VERSION: if (isset($this->_properties['properties'][self::VERSION])) { return $this->_properties['properties'][self::VERSION]; } break; case self::OS: if (isset($this->_properties['properties'][self::OS])) { return $this->_properties['properties'][self::OS]; } break; case 'properties': if (!isset($this->_properties['properties'])) { $return = array(); return $return; } // Fall through. default: if (isset($this->_properties[$property])) { return $this->_properties[$property]; } } $return = null; return $return; } /** * Setter */ public function __set($property, $value) { switch ($property) { case self::MULTIPLEX: $this->_multiplexSet = true; // fallthrough case self::ANNOUNCED_VERSION: case self::BLOCKED: case self::VERSION: case self::OS: $properties = $this->properties; if (empty($properties)) { $properties = array(); } $properties[$property] = $value; $this->setDeviceProperties($properties); break; case 'clientType': $this->_clientType = $value; break; default: if (!isset($this->_properties[$property]) || $value != $this->_properties[$property]) { $this->_dirty[$property] = true; $this->_properties[$property] = $value; } } } /** * Magic isset */ public function __isset($property) { return !empty($this->_properties[$property]); } /** * Indicates if we need to announce new EAS version string to the client. * If the property is empty, we don't send it since we are sending the * EAS-Version header anyway and this is a new device. * * @param string $supported The current EAS-Version header. * * @return boolean True if we need to send the MS-RP header, otherwise false. */ public function needsVersionUpdate($supported) { if (empty($this->properties[self::ANNOUNCED_VERSION])) { $properties = $this->properties; $properties[self::ANNOUNCED_VERSION] = $supported; $this->setDeviceProperties($properties); return false; } if ($this->properties[self::ANNOUNCED_VERSION] != $supported) { $properties = $this->properties; $properties[self::ANNOUNCED_VERSION] = $supported; $this->setDeviceProperties($properties); return true; } return false; } /** * Returns if the current device is an expected non-provisionable device. * I.e., the client does not support provisioning at all, but should still * be allowed to connect to a server that has provisioning set to Force. * Currently, this only applies to Windows Communication Apps (Outlook 2013). * * @return boolean True if the device should be allowed to connect to a * Forced provision server. False if not. */ public function isNonProvisionable() { // Outlook? The specs say that "Windows Communication Apps" should // provide the 'OS' parameter of the ITEMSETTINGS data equal to 'Windows', // but Outlook 2013 doesn't even send the ITEMSETTINGS command, so we // need to check the userAgent header. Early versions used Microsoft.Outlook, // but after some update it was changed to 'Outlook/15.0' if (strpos($this->deviceType, 'MicrosoftOutlook') !== false || strpos($this->userAgent, 'Outlook') !== false) { return true; } return false; } /** * Check if we should enforce provisioning on this device. * * @return boolean */ public function enforceProvisioning() { if (strpos($this->userAgent, 'Android') !== false) { if (preg_match('@EAS[/-]{0,1}([.0-9]{2,})@', $this->userAgent, $matches)) { return $matches[1] > 1.2; } return false; } return !$this->isNonProvisionable(); } /** * Set the device's DEVICEINFO data. * * @param array $data The data array sent from the device. */ public function setDeviceProperties(array $data) { $data = array_merge($this->properties, $data); if (empty($data['userAgent']) && !empty($this->_properties['userAgent'])) { $data['userAgent'] = $this->_properties['userAgent']; } $this->properties = $data; $this->_dirty['properties'] = true; } /** * Return an array of DEVICEINFO data, with keys suitable for displaying. * * @return array */ public function getFormattedDeviceProperties() { $data = array( _("Id") => $this->id, _("Policy Key") => $this->policykey, _("User Agent") => $this->userAgent ); if (!empty($this->properties[self::MODEL])) { $data[_("Model")] = $this->properties[self::MODEL]; } if (!empty($this->properties[self::IMEI])) { $data[_("IMEI")] = $this->properties[self::IMEI]; } if (!empty($this->properties[self::NAME])) { $data[_("Common Name")] = $this->properties[self::NAME]; } if (!empty($this->properties[self::OPERATOR])) { $data[_("Mobile Operator")] = $this->properties[self::OPERATOR]; } if (!empty($this->properties[self::OS])) { $data[_("OS")] = $this->properties[self::OS]; } if (!empty($this->properties[self::OS_LANGUAGE])) { $data[_("OS Language")] = $this->properties[self::OS_LANGUAGE]; } if (!empty($this->properties[self::PHONE_NUMBER])) { $data[_("Phone Number")] = $this->properties[self::PHONE_NUMBER]; } if (!empty($this->properties[self::VERSION])) { $data[_("EAS Version")] = $this->properties[self::VERSION]; } if (!empty($this->properties[self::MULTIPLEX])) { $data[_("Forced Multiplexed Bitmask")] = $this->properties[self::MULTIPLEX]; } return $data; } /** * Return the last time the device issued a SYNC request. * * @return integer The timestamp. */ public function getLastSyncTimestamp() { return $this->_state->getLastSyncTimestamp($this->id, $this->user); } /** * Save the dirty device info data. * * @param boolean $all If true, save all properties (deviceInfo and * deviceProperties). Otherwise, just save dirty * deviceProperties. @since 2.16.0 * @todo For 3.0, make it clearer that deviceInfo is per-user and * deviceProperties is per-device. */ public function save($all = true) { if ($all) { $this->_state->setDeviceInfo($this, $this->_dirty); } if (!empty($this->_dirty['properties'])) { $this->_state->setDeviceProperties($this->properties, $this->id); } $this->_dirty = array(); } /** * Return the major version number of the OS (or client app) as reported * by the client. * * @return integer The version number. */ public function getMajorVersion() { switch (Horde_String::lower($this->clientType)) { case self::TYPE_BLACKBERRY: if (preg_match('/(.+)\/(.+)/', $this->userAgent, $matches)) { return $matches[2]; } break; case self::TYPE_IPOD: case self::TYPE_IPAD: case self::TYPE_IPHONE: if (empty($this->_iOSVersion)) { $this->_getIosVersion(); } if (preg_match('/(\d+)\.(\d+)/', $this->_iOSVersion, $matches)) { return $matches[1]; } break; case self::TYPE_ANDROID: case self::TYPE_NINE: // Most newer Android clients send self::OS, so check that first if (!empty($this->properties[self::OS]) && preg_match('/(\d+)\.(\d+)/', $this->properties[self::OS], $matches)) { return $matches[1]; } // Some newer devices send userAgent like Android/4.3.3-EAS-1.3 if (preg_match('/Android\/(\d+)\.(\d+)/', $this->userAgent, $matches)) { return $matches[1]; } // Older Android/0.3 type userAgent strings. if (preg_match('/(.+)\/(\d+)\.(\d+)/', $this->userAgent, $matches)) { return $matches[2]; } break; case self::TYPE_TOUCHDOWN: if (preg_match('/(.+)\/(\d+)\.(\d+)/', $this->userAgent, $matches)) { return $matches[2]; } break; } return 0; } /** * Detects the iOS version in M.m format and caches locally. */ protected function _getIosVersion() { // First see if we have a newer client that sends the OS version // Newer iOS sends e.g., "iOS 8.2.2" in OS field. if (!empty($this->properties[self::OS]) && preg_match('/\d+\.\d+\.?\d+?/', $this->properties[self::OS], $matches)) { if (!empty($matches[0])) { $this->_iOSVersion = $matches[0]; return; } } // Match to a known UserAgent string version. foreach (Horde_ActiveSync_Device_Ios::$VERSION_MAP as $userAgent => $version) { if (preg_match('/\w+\/(' . $userAgent . ')$/', $this->userAgent, $matches)) { $this->_iOSVersion = $version; return; } } } /** * Return the minor version number of the OS (or client app) as reported * by the client. * * @return integer The version number. */ public function getMinorVersion() { switch (Horde_String::lower($this->clientType)) { case self::TYPE_BLACKBERRY: if (preg_match('/(.+)\/(.+)/', $this->userAgent, $matches)) { return $matches[2]; } break; case self::TYPE_IPOD: case self::TYPE_IPAD: case self::TYPE_IPHONE: if (empty($this->_iOSVersion)) { $this->_getIosVersion(); } if (preg_match('/(\d+)\.(\d+)/', $this->_iOSVersion, $matches)) { return $matches[2]; } break; case self::TYPE_ANDROID: // Most newer Android clients send self::OS, so check that first if (!empty($this->properties[self::OS]) && preg_match('/(\d+)\.(\d+)/', $this->properties[self::OS], $matches)) { return $matches[2]; } // Some newer devices send userAgent like Android/4.3.3-EAS-1.3 if (preg_match('/Android\/(\d+)\.(\d+)/', $this->userAgent, $matches)) { return $matches[2]; } // Older Android/0.3 type userAgent strings. if (preg_match('/(.+)\/(\d+)\.(\d+)/', $this->userAgent, $matches)) { return $matches[3]; } break; case self::TYPE_TOUCHDOWN: if (preg_match('/(.+)\/(\d+)\.(\d+)/', $this->userAgent, $matches)) { return $matches[3]; } break; } return 0; } /** * Return the number of hours to offset a POOMCONTACTS:BIRTHDAY * or ANNIVERSARY field in an attempt to work around a bug in the * protocol - which doesn't define a standard time for birthdays to occur. * * WP: * Devices seem to send the birthdays at the entered date, with * a time of 00:00:00 UTC during standard time and with 01:00:00 UTC * during DST if the client's configured timezone observes it. No idea * what purpose this serves since no timezone data is transmitted for * birthday values. * * iOS: * Seems different based on version. iOS 5+, at least seems to send * the birthday as midnight at the entered date in the device's timezone * then converted to UTC. Some minor issues with offsets being off an * hour or two for some timezones though. * * iOS < 5 sends the birthday time part as the time the birthday * was entered/edited on the device, converted to UTC, so it can't be * trusted at all. The best we can do here is transform the date to * midnight on date_default_timezone() converted to UTC. * * Android: * For contacts originating on the SERVER, the following is true: * * Stock 4.3 Takes the down-synched bday value which is assumed to be * UTC, does some magic to it (converts to milliseconds, creates a * gregorian calendar object, then converts to YYYY-MM-DD). When * sending the bday value up, it sends it up as-is. No conversion * to/from UTC or local is done. * * Stock 4.4.x does the above, but before sending the bday value, * validates that it's in a correct format for sending to the server. * This really only affects date data originally entered on the device * for non-stock android clients. * * There is some strange bit of code in Android that adds 1 to the * DAY_OF_MONTH when HOUR_OF_DAY >= 12 in an attempt to "fix" * birthday handling for GMT+n users. See: * https://android.googlesource.com/platform/packages/apps/Exchange/+/32daacdd71b9de8fd5e3f59c37934e3e4a9fa972%5E!/exchange2/src/com/android/exchange/adapter/ContactsSyncAdapter.java * Not sure what to make of it, or why it's not just converted to * local tz when displaying but this probably breaks birthday handling * for people in a few timezones. * * For contacts originating on the CLIENT, the datetime is sent as * 08:00:00 UTC, and this seems to be regardless of the timezone set * in the Android system. * * Given all of this, it makes sense to me to ALWAYS send birthday * data as occuring at 08:00:00 UTC for *native* Android clients. * * BB 10+ expects it at 12:00:00 UTC * * @param Horde_Date $date The date. This should normally be in the local * timezone if encoding the date for the client. * If decoding the date from the client, it will * normally be in UTC. * @param boolean $toEas Convert from local to device if true. * DEFAULT: false * * @return Horde_Date The date of the birthday/anniversary, with * any fixes applied for the current device. The * timezone set in the object will depend on the * client detected, and whether the date is being * encoding or decoding. */ public function normalizePoomContactsDates($date, $toEas = false) { switch (Horde_String::lower($this->clientType)) { case self::TYPE_WP: case 'wp8': // Legacy. Remove in H6. case 'wp': // Legacy. Remove in H6. if ($toEas) { return new Horde_Date($date->format('Y-m-d'), 'UTC'); } else { $date = new Horde_Date($date->format('Y-m-d')); return $date->setTimezone('UTC'); } case self::TYPE_ANDROID: // Need to protect against clients that don't send the actual Android // version in the OS field. if (stripos($this->deviceType, 'samsung') === 0) { // Samsung's native Contacts app works differently than stock // Android, always sending as 00:00:00 if ($toEas) { return new Horde_Date($date->format('Y-m-d'), 'UTC'); } $date = new Horde_Date($date->format('Y-m-d')); return $date->setTimezone('UTC'); } if ($this->getMajorVersion() >= 4 && $this->getMajorVersion() <= 10) { if ($toEas) { return new Horde_Date($date->format('Y-m-d 08:00:00'), 'UTC'); } else { $date = new Horde_Date($date->format('Y-m-d')); return $date->setTimezone('UTC'); } } else { // POOMCONTACTS:BIRTHDAY not really supported in early Android // versions. Return as is. return $date; } case self::TYPE_IPAD: case self::TYPE_IPHONE: case self::TYPE_IPOD: if ($this->getMajorVersion() >= 5) { // iOS >= 5 handles it correctly more or less. if ($toEas) { return new Horde_Date($date->format('Y-m-d 00:00:00')); } else { return $date; } } else { if ($toEas) { return new Horde_Date($date->format('Y-m-d'), 'UTC'); } else { return new Horde_Date($date->format('Y-m-d')); } } case self::TYPE_NINE: if ($toEas) { return new Horde_Date($date->format('Y-m-d 00:00:00')); } else { return $date; } case self::TYPE_BLACKBERRY: if ($toEas) { return new Horde_Date($date->format('Y-m-d 12:00:00'), 'UTC'); } else { $date = new Horde_Date($date->format('Y-m-d')); return $date->setTimezone('UTC'); } case self::TYPE_TOUCHDOWN: case self::TYPE_UNKNOWN: default: return $date; } } /** * Return if this client has the described quirk. * * @param integer $quirk The specified quirk to check for. * * @return boolean True if quirk is present. */ public function hasQuirk($quirk) { switch ($quirk) { case self::QUIRK_NEEDS_SUPPORTED_PICTURE_TAG: if ($this->_isIos() && $this->getMajorVersion() == 4) { return true; } return false; break; default: return false; } } /** * Attempt to determine the *client* application as opposed to the device, * which may or may not be the client. * * @return string The client name. */ protected function _getClientType() { // Differentiate between the deviceType and the client app. if ((!empty($this->properties[self::OS]) && stripos($this->properties[self::OS], 'Android') !== false) || Horde_String::lower($this->deviceType) == self::TYPE_ANDROID) { // We can detect native Android, TouchDown, and Nine. // Moxier does not distinguish itself, so we can't sniff it. if (strpos($this->userAgent, 'TouchDown') !== false) { return self::TYPE_TOUCHDOWN; } else if ($this->_isNine()) { return self::TYPE_NINE; } else if (stripos($this->userAgent, 'Android') !== false) { return $this->deviceType; } else { return self::TYPE_ANDROID; } } else { return $this->deviceType; } } /** * Helper method to sniff out the 9Folders client, "Nine". * @see https://ninefolders.plan.io/track/7048/46b213 for the discussion on * how to sniff out the Nine client. Not the best solution, but it's the one * they decided to use. * * @return boolean True if client is thought to be "Nine". */ protected function _isNine() { if (!ctype_xdigit($this->id)) { return false; } return stripos(pack('H*', $this->id), 'nine') === 0; } /** * Basic sniffing for determining if devices can support non-multiplexed * collections. */ protected function _sniffMultiplex() { $clientType = Horde_String::lower($this->clientType); if ($this->_isIos()) { // iOS seems to support multiple collections for everything except Notes. $this->_properties['properties'][self::MULTIPLEX] = Horde_ActiveSync_Device::MULTIPLEX_NOTES; } else if ($clientType == self::TYPE_ANDROID) { // Special cases: These clients don't support non-multiplexed // collections. Samsung's native client and HTCOnemini2. if (stripos($this->deviceType, 'samsung') === 0 || stripos($this->model, 'HTCOnemini2') === 0 || $this->deviceType == 'HTCOnemini2') { $this->_properties['properties'][self::MULTIPLEX] = Horde_ActiveSync_Device::MULTIPLEX_CONTACTS | Horde_ActiveSync_Device::MULTIPLEX_CALENDAR | Horde_ActiveSync_Device::MULTIPLEX_NOTES | Horde_ActiveSync_Device::MULTIPLEX_TASKS; return; } // All android before 4.4 KitKat requires multiplex. KitKat and // Android 5 native supports non-multiplexed calendars only. if (!empty($this->properties[self::OS]) && preg_match('/(\d+\.\d+(\.\d+)*)/', $this->properties[self::OS], $matches) && version_compare($matches[0], '4.4.0') >= 0) { $this->_properties['properties'][self::MULTIPLEX] = Horde_ActiveSync_Device::MULTIPLEX_NOTES | Horde_ActiveSync_Device::MULTIPLEX_CONTACTS | Horde_ActiveSync_Device::MULTIPLEX_TASKS; } else { $this->_properties['properties'][self::MULTIPLEX] = Horde_ActiveSync_Device::MULTIPLEX_CONTACTS | Horde_ActiveSync_Device::MULTIPLEX_CALENDAR | Horde_ActiveSync_Device::MULTIPLEX_NOTES | Horde_ActiveSync_Device::MULTIPLEX_TASKS; } } else if (strpos($this->userAgent, 'MSFT-WP/8.10') !== false) { // Windows Phone 8.10 supports multiple calendars and tasks, but // no contacts. $this->_properties['properties'][self::MULTIPLEX] = Horde_ActiveSync_Device::MULTIPLEX_CONTACTS; } else if (strpos($this->userAgent, 'MSFT-WP/8.0') !== false || $this->deviceType == 'WP8') { // Windows Phone 8.0 seems that only multiple tasklists are // supported. The rest must be multiplexed. $this->_properties['properties'][self::MULTIPLEX] = Horde_ActiveSync_Device::MULTIPLEX_CONTACTS | Horde_ActiveSync_Device::MULTIPLEX_CALENDAR; } else if (strpos($this->userAgent, 'MSFT-PPC') !== false || $this->deviceType == 'PocketPC') { // PocketPC versions seem to not support any user defined // collections at all, though I've only tested on a single HTC device. $this->_properties['properties'][self::MULTIPLEX] = Horde_ActiveSync_Device::MULTIPLEX_CONTACTS | Horde_ActiveSync_Device::MULTIPLEX_CALENDAR | Horde_ActiveSync_Device::MULTIPLEX_NOTES | Horde_ActiveSync_Device::MULTIPLEX_TASKS; } else if (strpos($this->userAgent, 'Outlook/15.0') !== false || strpos($this->userAgent, 'Outlook/16.0') !== false) { // OL2013 and OL2016 do not support multiple contact lists. $this->_properties['properties'][self::MULTIPLEX] = Horde_ActiveSync_Device::MULTIPLEX_CONTACTS; } else { $this->_properties['properties']['multiplex'] = 0; } } /** * Return if this client is an iOS device. Different versions require * different checks. * * @return boolean [description] */ protected function _isIos() { // Compare in order of likelyhood / most recent to least recent versions. if (strpos($this->{self::OS}, 'iOS') === 0 || strpos($this->userAgent, 'iOS') === 0 || in_array(Horde_String::lower($this->clientType), array(self::TYPE_IPAD, self::TYPE_IPOD, self::TYPE_IPHONE)) || strpos($this->userAgent, 'Apple-') === 0) { return true; } return false; } } Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Exception.php0000664000076500000240000000251412654565405020253 0ustar * @package ActiveSync */ /** * Base exception class for Horde_ActiveSync * * Copyright 2010-2016 Horde LLC (http://www.horde.org/) * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2010-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync */ class Horde_ActiveSync_Exception extends Horde_Exception_Wrapped { /** Error codes **/ // Defauld, unspecified. const UNSPECIFIED = 0; // Unsupported action was attempted. const UNSUPPORTED = 3; }Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Mime.php0000664000076500000240000001714312654565405017210 0ustar * @package ActiveSync * @since 2.19.0 */ /** * This class provides some base functionality for dealing with MIME objects in * the context of ActiveSync requests. * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2012-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync * @since 2.19.0 */ class Horde_ActiveSync_Mime { /** * The composited mime part. * * @var Horde_Mime_Part */ protected $_base; /** * Local cache of hasAttachments data. * * @var boolean */ protected $_hasAttachments; /** * Cont'r * * @param Horde_Mime_Part $mime The mime data. */ public function __construct(Horde_Mime_Part $mime) { $this->_base = $mime; } public function __destruct() { $this->_base = null; } /** * Accessor * * @param string $property The property name. * * @return mixed */ public function __get($property) { switch ($property) { case 'base': return $this->_base; } return $this->_base->property; } /** * Delegate calls to the composed MIME object. * * @param string $method The method name. * @param array $params The parameters. * * @return mixed */ public function __call($method, array $params) { if (is_callable(array($this->_base, $method))) { return call_user_func_array(array($this->_base, $method), $params); } throw new InvalidArgumentException(); } /** * Return the hasAttachments flag * * @return boolean */ public function hasAttachments() { if (isset($this->_hasAttachments)) { return $this->_hasAttachments; } foreach ($this->_base->contentTypeMap() as $id => $type) { if ($this->isAttachment($id, $type)) { $this->_hasAttachments = true; return true; } } $this->_hasAttachments = false; return false; } /** * Determines if a MIME type is an attachment. * For our purposes, an attachment is any MIME part that can be * downloaded by itself (i.e. all the data needed to view the part is * contained within the download data). * * @param string $id The MIME Id for the part we are checking. * @param string $mime_type The MIME type. * * @return boolean True if an attachment. * @todo Pass a single mime part as parameter. */ public function isAttachment($id, $mime_type) { switch ($mime_type) { case 'text/plain': if (!($this->_base->findBody('plain') == $id)) { return true; } return false; case 'text/html': if (!($this->_base->findBody('html') == $id)) { return true; } return false; case 'application/pkcs7-signature': case 'application/x-pkcs7-signature': return false; } if ($this->_base->getPart($id)->getDisposition() == 'attachment') { return true; } list($ptype,) = explode('/', $mime_type, 2); switch ($ptype) { case 'message': return in_array($mime_type, array('message/rfc822', 'message/disposition-notification')); case 'multipart': return false; default: return true; } } /** * Return the MIME part of the iCalendar attachment, if available. * * @return mixed The mime id of an iCalendar part, if present. Otherwise * false. */ public function hasiCalendar() { if (!$this->hasAttachments()) { return false; } foreach ($this->_base->contentTypeMap() as $id => $type) { if ($type == 'text/calendar' || $type == 'application/ics') { return $id; } } return false; } /** * Return the S/MIME status of this message (RFC2633) * * @param Horde_Mime_Part The part to test. If omitted, uses self::$_base * * @return boolean True if message is S/MIME signed, otherwise false. */ public function isSigned(Horde_Mime_Part $mime = null) { if (empty($mime)) { $mime = $this->_base; } if ($mime->getPrimaryType() == 'multipart') { if ($mime->getSubType() == 'signed' && $mime->getContentTypeParameter('protocol') != 'application/pgp-signature') { return true; } // Signed/encrypted part might be lower in the mime structure foreach ($mime->getParts() as $part) { if ($this->isSigned($part)) { return true; } } } return false; } /** * Return the S/MIME encryption status of this message. * * @param Horde_Mime_Part The part to test. If omitted, uses self::$_base * * @return boolean True if message is S/MIME encrypted, otherwise false. * @since 2.20.0 * @todo For 3.0, combine into one method with self::isSigned() and return * a bitmask result. */ public function isEncrypted(Horde_Mime_Part $mime = null) { if (empty($mime)) { $mime = $this->_base; } if ($mime->getType() == 'application/pkcs7-mime' || $mime->getType() == 'application/x-pkcs7-mime') { return true; } // Signed/encrypted part might be lower in the mime structure foreach ($mime->getParts() as $part) { if ($this->isEncrypted($part)) { return true; } } } /** * Finds the main "body" text part (if any) in a message. "Body" data is the * first text part under this part. Considers only body data that should * be displayed as the main body on an EAS client. I.e., this ignores any * text parts contained withing "attachment" parts such as messages/rfc822 * attachments. * * @param string $subtype Specifically search for this subtype. * * @return mixed The MIME ID of the main body part, or null if a body * part is not found. */ public function findBody($subtype = null) { $this->buildMimeIds(); $iterator = new Horde_ActiveSync_Mime_Iterator($this->_base, true); foreach ($iterator as $val) { $id = $val->getMimeId(); if (($val->getPrimaryType() == 'text') && ((intval($id) === 1) || !$this->getMimeId()) && (is_null($subtype) || ($val->getSubType() == $subtype)) && !$this->isAttachment($id, $val->getType())) { return $id; } } return null; } }Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Policies.php0000664000076500000240000003157712654565405020077 0ustar * @package ActiveSync */ /** * Horde_ActiveSync_Policies:: Wraps all functionality related to generating * the XML or WBXML for EAS Policies. * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2010-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync */ class Horde_ActiveSync_Policies { /* Policy configuration keys */ const POLICY_PIN = 'DevicePasswordEnabled'; const POLICY_AEFVALUE = 'MaxInactivityTimeDeviceLock'; const POLICY_CODEFREQ = 'codewordfrequency'; const POLICY_MINLENGTH = 'MinDevicePasswordLength'; const POLICY_COMPLEXITY = 'AlphanumericDevicePasswordRequired'; // 12.0 //const POLICY_PWDRECOVERY = 'passwordrecovery'; //const POLICY_PWDEXPIRATION = 'passwordexpiration'; //const POLICY_PWDHISTORY = 'passwordhistory'; const POLICY_ENCRYPTION = 'DeviceEncryptionEnabled'; const POLICY_ATC = 'AttachmentsEnabled'; const POLICY_MAXATCSIZE = 'MaxAttachmentSize'; const POLICY_MAXFAILEDATTEMPTS = 'MaxDevicePasswordFailedAttempts'; // 12.1 const POLICY_ALLOW_SDCARD = 'AllowStorageCard'; const POLICY_ALLOW_CAMERA = 'AllowCamera'; const POLICY_ALLOW_SMS = 'AllowTextMessaging'; const POLICY_ALLOW_WIFI = 'AllowWiFi'; const POLICY_ALLOW_BLUETOOTH = 'AllowBluetooth'; const POLICY_ALLOW_POPIMAP = 'AllowPOPIMAPEmail'; const POLICY_ALLOW_BROWSER = 'AllowBrowser'; const POLICY_REQUIRE_SMIME_SIGNED = 'RequireSignedSMIMEMessages'; const POLICY_REQUIRE_SMIME_ENCRYPTED = 'RequireEncryptedSMIMEMessages'; const POLICY_DEVICE_ENCRYPTION = 'RequireDeviceEncryption'; const POLICY_ALLOW_HTML = 'AllowHTMLEmail'; const POLICY_MAX_EMAIL_AGE = 'MaxEmailAgeFilter'; //const POLICY_MAX_EMAIL_TRUNCATION = 'maxemailtruncation'; //const POLICY_MAX_HTMLEMAIL_TRUNCATION = 'maxhtmlemailtruncation'; const POLICY_ROAMING_NOPUSH = 'RequireManualSyncWhenRoaming'; /** * Default policy values used in both 12.0 and 12.1 * * @var array */ protected $_defaults = array( self::POLICY_PIN => false, self::POLICY_AEFVALUE => '0', self::POLICY_MAXFAILEDATTEMPTS => '5', self::POLICY_CODEFREQ => '0', self::POLICY_MINLENGTH => '5', ); /** * Deafaults for 12.0 policies. * * @var array */ protected $_defaults_twelve = array( self::POLICY_ATC => '1', self::POLICY_ENCRYPTION => '0', self::POLICY_MAXATCSIZE => '5000000', self::POLICY_COMPLEXITY => '0', //self::POLICY_PWDRECOVERY => '0', //self::POLICY_PWDEXPIRATION => '0', //self::POLICY_PWDHISTORY => '0', ); /** * Defaults used only in 12.1 * * @var array */ protected $_defaults_twelveone = array( // 1 == Allow/Yes, 0 == Disallow/No. self::POLICY_ALLOW_SDCARD => '1', self::POLICY_ALLOW_CAMERA => '1', self::POLICY_ALLOW_SMS => '1', self::POLICY_ALLOW_WIFI => '1', self::POLICY_ALLOW_BLUETOOTH => '1', self::POLICY_ALLOW_POPIMAP => '1', self::POLICY_ALLOW_BROWSER => '1', self::POLICY_REQUIRE_SMIME_ENCRYPTED => '0', self::POLICY_REQUIRE_SMIME_SIGNED => '0', self::POLICY_DEVICE_ENCRYPTION => '0', self::POLICY_ALLOW_HTML => '1', self::POLICY_MAX_EMAIL_AGE => '0', self::POLICY_ROAMING_NOPUSH => '0', ); /** * Explicitly set policies. * * @var array */ protected $_overrides; /** * Output stream * * @var Horde_ActiveSync_Wbxml_Encoder */ protected $_encoder; /** * EAS version to support. * * @var long */ protected $_version; /** * Local cache of all policies to send. * * @var array */ protected $_policies = array(); /** * Const'r * * @param Horde_ActiveSync_Wbxml_Encoder $encoder The output stream encoder * @param float $version The EAS Version. * @param array $policies The policy array. */ public function __construct( Horde_ActiveSync_Wbxml_Encoder $encoder = null, $version = Horde_ActiveSync::VERSION_TWELVEONE, array $policies = array()) { $this->_encoder = $encoder; if ($version >= Horde_ActiveSync::VERSION_TWELVE) { $this->_defaults = array_merge($this->_defaults, $this->_defaults_twelve); } if ($version >= Horde_ActiveSync::VERSION_TWELVEONE) { $this->_defaults = array_merge($this->_defaults, $this->_defaults_twelveone); } $this->_version = $version; $this->_overrides = $policies; } /** * Return a list of all configurable policy names. * * @return array */ public function getAvailablePolicies() { return array_keys($this->_defaults); } /** * Determine if the requested policy settings are available for the current * version being used. * * @return boolean True if policies are available in current version, false * otherwise. */ public function validatePolicyVersion() { $policies = $this->_getPolicies(); // Validate the version against the required policies. if ($this->_version < Horde_ActiveSync::VERSION_TWELVEONE) { foreach ($policies as $key => $value) { if (!empty($this->_defaults_twelveone[$key]) && $this->_defaults_twelveone[$key] != $value) { return false; } } } return true; } /** * Output the policies as XML. Only used in EAS Version 2.5. This method * only outputs the 2.5 compatible policies. * * @throws Horde_ActiveSync_Exception */ public function toXml() { if (empty($this->_encoder)) { throw new Horde_ActiveSync_Exception('No output stream'); } $policies = array_merge($this->_defaults, $this->_overrides); $xml = '' . '' . ''; if ($policies[self::POLICY_PIN]) { $xml .= '' . '' . '' . (!empty($policies[self::POLICY_AEFVALUE]) ? '' : '') . ''; if (!empty($policies[self::POLICY_MAXFAILEDATTEMPTS])) { $xml .= ''; } if (!empty($policies[self::POLICY_CODEFREQ])) { $xml .= ''; } if (!empty($policies[self::POLICY_MINLENGTH])) { $xml .= ''; } if ($policies[self::POLICY_COMPLEXITY] !== false) { $xml .= ''; } $xml .= ''; } $xml .= ''; $this->_encoder->content($xml); } /** * Output the policies as WBXML. Used in EAS Versions >= 12.0 */ public function toWbxml() { if (empty($this->_encoder)) { throw new Horde_ActiveSync_Exception('No output stream'); } $policies = $this->_getPolicies(); $this->_encoder->startTag('Provision:EASProvisionDoc'); $this->_sendPolicy(self::POLICY_PIN, $policies[self::POLICY_PIN] ? '1' : '0'); if ($policies[self::POLICY_PIN]) { $this->_sendPolicy(self::POLICY_COMPLEXITY, $policies[self::POLICY_COMPLEXITY], true); $this->_sendPolicy(self::POLICY_MINLENGTH, $policies[self::POLICY_MINLENGTH], true); $this->_sendPolicy(self::POLICY_MAXFAILEDATTEMPTS, $policies[self::POLICY_MAXFAILEDATTEMPTS], true); $this->_sendPolicy(self::POLICY_COMPLEXITY, $policies[self::POLICY_COMPLEXITY] >= 1 ? '1' : '0', true); } $this->_sendPolicy(self::POLICY_ENCRYPTION, $policies[self::POLICY_ENCRYPTION], true); $this->_sendPolicy(self::POLICY_ATC, $policies[self::POLICY_ATC], false); $this->_sendPolicy(self::POLICY_AEFVALUE, $policies[self::POLICY_AEFVALUE], true); $this->_sendPolicy(self::POLICY_MAXATCSIZE, $policies[self::POLICY_MAXATCSIZE], true); if ($this->_version > Horde_ActiveSync::VERSION_TWELVE) { $this->_sendPolicy(self::POLICY_ALLOW_SDCARD, $policies[self::POLICY_ALLOW_SDCARD], true); $this->_sendPolicy(self::POLICY_ALLOW_CAMERA, $policies[self::POLICY_ALLOW_CAMERA], true); $this->_sendPolicy(self::POLICY_DEVICE_ENCRYPTION, $policies[self::POLICY_DEVICE_ENCRYPTION], true); $this->_sendPolicy(self::POLICY_ALLOW_WIFI, $policies[self::POLICY_ALLOW_WIFI], true); $this->_sendPolicy(self::POLICY_ALLOW_SMS, $policies[self::POLICY_ALLOW_SMS], true); $this->_sendPolicy(self::POLICY_ALLOW_POPIMAP, $policies[self::POLICY_ALLOW_POPIMAP], true); $this->_sendPolicy(self::POLICY_ALLOW_BLUETOOTH, $policies[self::POLICY_ALLOW_BLUETOOTH], true); $this->_sendPolicy(self::POLICY_ROAMING_NOPUSH, $policies[self::POLICY_ROAMING_NOPUSH], true); $this->_sendPolicy(self::POLICY_ALLOW_HTML, $policies[self::POLICY_ALLOW_HTML], true); $this->_sendPolicy(self::POLICY_MAX_EMAIL_AGE, $policies[self::POLICY_MAX_EMAIL_AGE], true); $this->_sendPolicy(self::POLICY_REQUIRE_SMIME_SIGNED, $policies[self::POLICY_REQUIRE_SMIME_SIGNED], true); $this->_sendPolicy(self::POLICY_REQUIRE_SMIME_ENCRYPTED, $policies[self::POLICY_REQUIRE_SMIME_ENCRYPTED], true); $this->_sendPolicy(self::POLICY_ALLOW_BROWSER, $policies[self::POLICY_ALLOW_BROWSER], true); } $this->_encoder->endTag(); } /** * Output a single policy value * * @param string $policy The policy name * @param mixed $value The policy value * @param boolean $nodefault Don't send the policy if the value is default. */ protected function _sendPolicy($policy, $value, $nodefault = false) { if ($nodefault && $value == $this->_defaults[$policy]) { return; } if ($value === false) { $value = 0; } elseif ($value === true) { $value = 1; } $this->_encoder->startTag('Provision:' . $policy); $this->_encoder->content($value); $this->_encoder->endTag(); } protected function _getPolicies() { if (empty($this->_policies)) { $this->_policies = array_merge($this->_defaults, $this->_overrides); } return $this->_policies; } } Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Rfc822.php0000664000076500000240000001605512654565405017270 0ustar * @package ActiveSync */ /** * Horde_ActiveSync_Rfc822:: class provides functionality related to dealing * with raw RFC822 message strings within an ActiveSync context. * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2010-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync */ class Horde_ActiveSync_Rfc822 { /** * The memory limit for use with the PHP temp stream. * * @var integer */ public static $memoryLimit = 2097152; /** * Position of end of headers. * * @var integer */ protected $_hdr_pos; /** * The size of the EOL sequence. * * @var integer */ protected $_eol; /** * The raw message data in a stream. * * @var Horde_Stream */ protected $_stream; /** * The header text. * * @var string */ protected $_header_text; /** * Constructor. * * @param mixed $rfc822 The incoming message. Either a string * or a stream resource. * @param boolean $auto_add_headers Automatically add the standard * Message-ID and User-Agent headers? * @since 2.14.0 */ public function __construct($rfc822, $auto_add_headers = true) { if (is_resource($rfc822)) { $stream = new Horde_Stream_Existing(array('stream' => $rfc822)); $stream->rewind(); } else { $stream = new Horde_Stream_Temp(array('max_memory' => self::$memoryLimit)); $stream->add($rfc822, true); } $this->_parseStream($stream); if ($auto_add_headers) { $this->addStandardHeaders(); } } /** * Parse a Horde_Stream object to get the header and eol data. * * @param Horde_Stream $stream The stream object. */ protected function _parseStream(Horde_Stream $stream) { $this->_stream = $stream; list($this->_hdr_pos, $this->_eol) = $this->_findHeader(); } /** * Returns the raw message with the message headers stripped. * * @return Horde_Stream */ public function getMessage() { // Position to after the headers. fseek($this->_stream->stream, $this->_hdr_pos + $this->_eol); $new_stream = new Horde_Stream_Temp(array('max_memory' => self::$memoryLimit)); $new_stream->add($this->_stream, true); return $new_stream; } /** * Replace the MIME part of the message sent from the client. Headers from * the original message are always used. * * @param Horde_Mime_Part $part The new MIME part. * @since 2.19.0 */ public function replaceMime(Horde_Mime_Part $part) { $mime_stream = $part->toString(array( 'stream' => true, 'headers' => false) ); $mime_stream = new Horde_Stream_Existing(array('stream' => $mime_stream)); // Since we are still using the headers sent from the device, we can // simply zero out the position members etc... $this->_hdr_pos = 0; $this->_stream = $mime_stream; $mime_stream->rewind(); } /** * Return the raw message data. * * @return stream resource * @todo Rename to make it clear this returns a stream. */ public function getString() { if (!empty($this->_header_text)) { return Horde_Stream_Wrapper_Combine::getStream(array($this->_header_text, $this->getMessage()->stream)); } else { $this->_stream->rewind(); return $this->_stream->stream; } } /** * Return the message headers. * * @return Horde_Mime_Headers The header object. */ public function getHeaders() { if (!empty($this->_header_text)) { $hdr_text = $this->_header_text; } else { $this->_stream->rewind(); $hdr_text = $this->_stream->substring(0, $this->_hdr_pos); } return Horde_Mime_Headers::parseHeaders($hdr_text); } /** * Check for and add standard headers if needed. * * @since 2.14.0 */ public function addStandardHeaders() { $headers = $this->getHeaders(); $updated = false; // Check for required headers. if (!$headers->getValue('Message-ID')) { $headers->addMessageIdHeader(); $updated = true; } if (!$headers->getValue('User-Agent')) { $headers->addUserAgentHeader(); $updated = true; } if (!$headers->getValue('Date')) { $d = new Horde_Date(); $headers->addHeaderOb(Horde_Mime_Headers_Date::create()); $updated = true; } if ($updated) { $this->_header_text = $headers->toString(array('charset' => 'UTF-8')); } } /** * Return a Mime object representing the entire message. * * @return Horde_Mime_Part The Mime object. */ public function getMimeObject() { $this->_stream->rewind(); $part = Horde_Mime_Part::parseMessage($this->_stream->getString()); $part->isBasePart(true); return $part; } /** * Return the length of the message data. * * @return integer */ public function getBytes() { if (!isset($this->_bytes)) { $this->_bytes = $this->_stream->length(); } return $this->_bytes; } /** * Find the location of the end of the header text. * * @return array 1st element: Header position, 2nd element: Length of * trailing EOL. */ protected function _findHeader() { // Look for the EOL that is found first in the message. Some clients // are sending mixed EOL in S/MIME signed messages. This still doesn't // fix "Nine" currently, as they first send \r\n, but use \n\n to // separate the headers. // See: https://ninefolders.plan.io/track/10606/1dcfed switch ($this->_stream->getEOL()) { case "\n": return array($this->_stream->search("\n\n"), 2); case "\r\n": return array($this->_stream->search("\r\n\r\n"), 4); } } } Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Status.php0000664000076500000240000001056612654565405017606 0ustar * @package ActiveSync */ /** * Horde_ActiveSync_Status:: Constants for common EAS status codes. Common codes * were introduced in EAS 14. * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2013-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync */ class Horde_ActiveSync_Status { // EAS 12.1 const INVALID_CONTENT = 101; const INVALID_WBXML = 102; const INVALID_XML = 103; const INVALID_DATETIME = 104; const INVALID_COMBINATIONOFIDS = 105; const INVALID_IDS = 106; // was previously 400 or 500 (for SENDMAIL) in 12.0. const INVALID_MIME = 107; const INVALID_DEVICEID = 108; const INVALID_DEVICETYPE = 109; const SERVER_ERROR = 110; // was a general 500 error (server should not try again) in 12.0. const SERVER_ERROR_RETRY = 111; // was a 503 in 12.0 const MAILBOX_QUOTA_EXCEEDED = 113; const MAILBOX_OFFLINE = 114; const SEND_QUOTA_EXCEEDED = 115; const RECIPIENT_UNRESOLVED = 116; const DUPLICATE_MESSAGE = 118; // @TODO const NO_RECIPIENT = 119; const MAIL_SUBMISSION_FAILED = 120; const MAIL_REPLY_FAILED = 121; const ATT_TOO_LARGE = 122; const NO_MAILBOX = 123; const SYNC_NOT_ALLOWED = 126; const DEVICE_BLOCKED_FOR_USER = 129; const DENIED = 130; const DISABLED = 131; const STATEFILE_NOT_FOUND = 132; // was 500 in 12.0 const STATEVERSION_INVALID = 136; const DEVICE_NOT_FULLY_PROVISIONABLE = 139; // Device uses version that doesn't support policies defined on server. const REMOTEWIPE_REQUESTED = 140; const LEGACY_DEVICE_STRICT_POLICY = 141; const DEVICE_NOT_PROVISIONED = 142; const POLICY_REFRESH = 143; const INVALID_POLICY_KEY = 144; const EXTERNALLY_MANAGED_DEVICES_NOT_ALLOWED = 145; const UNEXPECTED_ITEM_CLASS = 147; const INVALID_STORED_REQUEST = 149; const ITEM_NOT_FOUND = 150; const TOO_MANY_FOLDERS = 151; const NO_FOLDERS_FOUND = 152; const ITEMS_LOST_AFTER_MOVE = 153; const FAILURE_IN_MOVE_OPERATION = 154; const MOVE_INVALID_DESTINATION = 156; // EAS 14.0 const AVAILABILITY_TOO_MANY_RECIPIENTS = 160; const AVAILABILITY_TRANSIENT_FAILURE = 162; const AVAILABILITY_FAILURE = 163; const AVAILABILITY_SUCCESS = 1; // EAS 14.1 const DEVICE_INFORMATION_REQUIRED = 165; const INVALID_ACCOUNT_ID = 166; const IRM_DISABLED = 168; const PICTURE_SUCCESS = 1; const NO_PICTURE = 173; const PICTURE_TOO_LARGE = 174; const PICTURE_LIMIT_REACHED = 175; const BODYPART_CONVERSATION_TOO_LARGE = 176; const MAXIMUM_DEVICES_REACHED = 177; }Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/SyncCache.php0000664000076500000240000007132012654565405020156 0ustar * @package ActiveSync */ /** * Horde_ActiveSync_SyncCache:: Wraps all functionality related to maintaining * the ActiveSync SyncCache. * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2010-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync * @internal Not intended for use outside of the Horde_ActiveSync library. * * The SyncCache maintains a number of "caches" - which are really more of a * shared memory store for keeping all relevant data from all running sync * requests up-to-date and available to other running requests: * * @property array $folders The folders cache: the list of * current folders, keyed by their internal uid and containing 'class', * 'serverid' and 'type'. * @property integer $hbinterval The heartbeat interval (in seconds). * @property integer $wait The wait interval (in minutes). * @property integer $pingheartbeat The heartbeat used in PING requests. * @property string $hierarchy The hierarchy synckey. * @property array $confirmed_synckeys Array of synckeys being confirmed * during a looping sync. * @property integer $lastuntil Timestamp representing the last * planned looping sync end time. * @property integer $lasthbsyncstarted Timestamp of the start of the last * looping sync. * @property integer $lastsyncendnormal Timestamp of the last looping sync * that ended normally. */ class Horde_ActiveSync_SyncCache { /** * The cache data. * * @var array */ protected $_data = array(); /** * The state driver * * @var Horde_ActiveSync_State_Base $state */ protected $_state; /** * The username * * @var string */ protected $_user; /** * The device id * * @var string */ protected $_devid; /** * Logger * * @var Horde_Log_Logger */ protected $_logger; /** * Track dirty properties. * * @var array */ protected $_dirty = array(); /** * Process id for logging. * * @var integer */ protected $_procid; /** * Constructor * * @param Horde_ActiveSync_State_Base $state The state driver * @param string $devid The device id * @param string $user The username * @param Horde_Log_Logger $logger The logger object * * @return Horde_ActiveSync_SyncCache */ public function __construct( Horde_ActiveSync_State_Base $state, $devid, $user, $logger = null) { $this->_state = $state; $this->_devid = $devid; $this->_user = $user; $this->_logger = $logger; $this->loadCacheFromStorage(); $this->_procid = getmypid(); $this->_logger = empty($logger) ? new Horde_Log_Logger(new Horde_Log_Handler_Null()) : $logger; $this->_logger->info(sprintf( '[%s] Creating new Horde_ActiveSync_SyncCache.', $this->_procid) ); } public function __get($property) { if (!$this->_isValidProperty($property)) { throw new InvalidArgumentException($property . ' is not a valid property'); } return !empty($this->_data[$property]) ? $this->_data[$property] : false; } public function __set($property, $value) { if (!$this->_isValidProperty($property)) { throw new InvalidArgumentException($property . ' is not a valid property'); } $this->_data[$property] = $value; $this->_dirty[$property] = true; } public function __isset($property) { if (!$this->_isValidProperty($property)) { throw new InvalidArgumentException($property . ' is not a valid property'); } return !empty($this->_data[$property]); } protected function _isValidProperty($property) { return in_array($property, array( 'hbinterval', 'wait', 'hierarchy', 'confirmed_synckeys', 'timestamp', 'lasthbsyncstarted', 'lastsyncendnormal', 'folders', 'pingheartbeat')); } /** * Validate the cache. Compares the cache timestamp with the current cache * timestamp in the state backend. If the timestamps are different, some * other request has modified the cache, so it should be invalidated. * * @param boolean $hb_only If true, only validate the hb timestamps. @since 2.4.0 * * @return boolean */ public function validateCache($hb_only = false) { $cache = $this->_state->getSyncCache( $this->_devid, $this->_user, array('lasthbsyncstarted', 'timestamp')); if ((!$hb_only && $cache['timestamp'] > $this->_data['timestamp']) || (!empty($cache['lasthbsyncstarted']) && $cache['lasthbsyncstarted'] > $this->_data['lasthbsyncstarted'])) { return false; } return true; } /** * Repopulate the cache data from storage. */ public function loadCacheFromStorage() { $this->_data = $this->_state->getSyncCache($this->_devid, $this->_user); $this->_dirty = array(); } /** * Perform some sanity checking on the various timestamps to ensure we * are in a valid state. Basically checks that we are not currently running * a looping sync and that the last looping sync ending normally. * * @return boolean * @deprecated Not needed any longer. Remove in H6. */ public function validateTimestamps() { if ((!empty($this->_data['lasthbsyncstarted']) && empty($this->_data['lastsyncendnormal'])) || (!empty($this->_data['lasthbsyncstarted']) && !empty($this->_data['lastsyncendnormal']) && ($this->_data['lasthbsyncstarted'] > $this->_data['lastsyncendnormal']))) { return false; } return true; } /** * Update the cache timestamp to the current time. */ public function updateTimestamp() { $this->timestamp = time(); } /** * Return all the collections in the syncCache. * * @param boolean $requireKey If true, only return collections with an * existing synckey in the cache. Otherwise * return all collections. * * @return array */ public function getCollections($requireKey = true) { $collections = array(); foreach ($this->_data['collections'] as $key => $collection) { if (!$requireKey || ($requireKey && !empty($collection['lastsynckey']))) { $collection['id'] = $key; $collections[$key] = $collection; } } return $collections; } /** * Return the count of available collections in the cache * * @param integer The count. */ public function countCollections() { if (empty($this->_data['collections'])) { return 0; } return count($this->_data['collections']); } /** * Remove all collection data. */ public function clearCollections() { $this->_logger->info(sprintf( '[%s] Clearing collections data from cache.', $this->_procid)); $this->_data['collections'] = array(); $this->_dirty['collections'] = true; } /** * Check for the existance of a specific collection in the cache. * * @param stirng $collectionid The collection id to search for. * * @return boolean */ public function collectionExists($collectionid) { return !empty($this->_data['collections'][$collectionid]); } /** * Set a specific collection to be PINGable. * * @param string $id The collection id. */ public function setPingableCollection($id) { if (empty($this->_data['collections'][$id])) { throw new InvalidArgumentException('Collection does not exist'); } $this->_data['collections'][$id]['pingable'] = true; $this->_markCollectionsDirty($id); } /** * Set a collection as non-PINGable. * * @param string $collectionid The collection id. */ public function removePingableCollection($id) { if (empty($this->_data['collections'][$id])) { $this->_logger->warn(sprintf( '[%s] Collection %s was asked to be removed from PINGABLE but does not exist.', $this->_procid, $id)); return; } $this->_data['collections'][$id]['pingable'] = false; $this->_markCollectionsDirty($id); } /** * Check if a specified collection is PINGable. * * @param string $id The collection id. * * @return boolean */ public function collectionIsPingable($id) { return !empty($this->_data['collections'][$id]) && !empty($this->_data['collections'][$id]['pingable']); } /** * Set the ping change flag on a collection. Indicatates that the last * PING was terminated with a change in this collection. * * @param string $id The collection id. * @throws InvalidArgumentException * @since 2.3.0 */ public function setPingChangeFlag($id) { if (empty($this->_data['collections'][$id])) { throw new InvalidArgumentException('Collection does not exist.'); } $this->_data['collections'][$id]['pingchange'] = true; $this->_markCollectionsDirty($id); } /** * Checks the status of the ping change flag. If true, the last PING request * detected a change in the specified collection. * * @param string $collectionid The collection id to check. * * @return boolean * @since 2.3.0 */ public function hasPingChangeFlag($collectionid) { return !empty($this->_data['collections'][$collectionid]['pingchange']); } /** * Reset the specified collection's ping change flag. * * @param string $id The collectionid to reset. * @since 2.3.0 */ public function resetPingChangeFlag($id) { $this->_data['collections'][$id]['pingchange'] = false; $this->_markCollectionsDirty($id); } /** * Refresh the cached collections from the state backend. * */ public function refreshCollections() { $syncCache = $this->_state->getSyncCache( $this->_devid, $this->_user, array('collections') ); $cache_collections = !is_array($syncCache['collections']) ? array() : $syncCache['collections']; foreach ($cache_collections as $id => $cache_collection) { if (!isset($cache_collection['lastsynckey'])) { continue; } $cache_collection['id'] = $id; $cache_collection['synckey'] = $cache_collection['lastsynckey']; $this->_data['collections'][$id] = $cache_collection; $this->_markCollectionsDirty($id); } $this->_logger->info(sprintf( '[%s] SyncCache collections refreshed.', $this->_procid)); } /** * Save the synccache to storage. */ public function save() { // Iterate over the collections and persist the last known synckey. foreach ($this->_data['collections'] as $id => &$collection) { if (!empty($collection['synckey'])) { $collection['lastsynckey'] = $collection['synckey']; unset($collection['synckey']); $this->_markCollectionsDirty($id); } } $this->timestamp = time(); $this->_state->saveSyncCache( $this->_data, $this->_devid, $this->_user, $this->_dirty); $this->_dirty = array(); } /** * Add a new collection to the cache * * @param array $collection The collection array */ public function addCollection(array $collection) { $this->_data['collections'][$collection['id']] = array( 'class' => $collection['class'], 'windowsize' => isset($collection['windowsize']) ? $collection['windowsize'] : null, 'deletesasmoves' => isset($collection['deletesasmoves']) ? $collection['deletesasmoves'] : null, 'filtertype' => isset($collection['filtertype']) ? $collection['filtertype'] : null, 'truncation' => isset($collection['truncation']) ? $collection['truncation'] : null, 'rtftruncation' => isset($collection['rtftruncation']) ? $collection['rtftruncation'] : null, 'mimesupport' => isset($collection['mimesupport']) ? $collection['mimesupport'] : null, 'mimetruncation' => isset($collection['mimetruncation']) ? $collection['mimetruncation'] : null, 'conflict' => isset($collection['conflict']) ? $collection['conflict'] : null, 'bodyprefs' => isset($collection['bodyprefs']) ? $collection['bodyprefs'] : null, 'serverid' => isset($collection['serverid']) ? $collection['serverid'] : $collection['id'] ); $this->_markCollectionsDirty($collection['id']); } /** * Remove a collection from the cache. * * @param string $id The collection id. * @param boolean $purge If true, completely remove the collection entry * otherwise, just resets the synckey. */ public function removeCollection($id, $purge = true) { if ($purge) { $this->_logger->info(sprintf( '[%s] Removing collection %s from SyncCache.', $this->_procid, $id) ); unset($this->_data['collections'][$id]); $this->_dirty['collections'] = true; } elseif (!empty($this->_data['collections'][$id])) { $this->_data['collections'][$id]['synckey'] = '0'; $this->_markCollectionsDirty($id); } } /** * Update the windowsize for the specified collection. * * @param string $id The collection id. * @param integer $size The updated windowsize. */ public function updateWindowSize($id, $windowsize) { $this->_data['collections'][$id]['windowsize'] = $windowsize; $this->_markCollectionsDirty($id); } /** * Update the filtertype for the specified collection. * * @param string $id The collection id. * @param integer $filtertype The updated filtertype. * @since 2.26.0 */ public function updateFiltertype($id, $filtertype) { $this->_data['collections'][$id]['filtertype'] = $filtertype; $this->_markCollectionsDirty($id); } /** * Clear all synckeys from the known collections. * */ public function clearCollectionKeys() { $this->_logger->info(sprintf( '[%s] Clearing all collection synckeys from the cache.', $this->_procid) ); foreach ($this->_data['collections'] as $id => &$c) { unset($c['synckey']); } $this->_dirty['collections'] = true; } /** * Add a confirmed synckey to the cache. * * @param string $key The synckey to add. */ public function addConfirmedKey($key) { $this->_data['confirmed_synckeys'][$key] = true; $this->_dirty['confirmed_synckeys'] = true; } /** * Remove a confirmed sycnkey from the cache * * @param string $key The synckey to remove. */ public function removeConfirmedKey($key) { unset($this->_data['confirmed_synckeys'][$key]); $this->_dirty['confirmed_synckeys'] = true; } /** * Update a collection in the cache. * * @param array $collection The collection data to add/update. * @param array $options Options: * - newsynckey: (boolean) Set the new synckey in the collection. * DEFAULT: false (Do not set the new synckey). * - unsetChanges: (boolean) Unset the GETCHANGES flag in the collection. * DEFAULT: false (Do not unset the GETCHANGES flag). * - unsetPingChangeFlag: (boolean) Unset the PINGCHANGES flag in the collection. * DEFUALT: false (Do not uset the PINGCHANGES flag). * @since 2.3.0 */ public function updateCollection(array $collection, array $options = array()) { $options = array_merge( array('newsynckey' => false, 'unsetChanges' => false, 'unsetPingChangeFlag' => false), $options ); if (!empty($collection['id'])) { if ($options['newsynckey']) { $this->_data['collections'][$collection['id']]['synckey'] = $collection['newsynckey']; $this->_markCollectionsDirty($collection['id']); } elseif (isset($collection['synckey'])) { $this->_data['collections'][$collection['id']]['synckey'] = $collection['synckey']; $this->_markCollectionsDirty($collection['id']); } if (isset($collection['class'])) { $this->_data['collections'][$collection['id']]['class'] = $collection['class']; $this->_markCollectionsDirty($collection['id']); } if (isset($collection['windowsize'])) { $this->_data['collections'][$collection['id']]['windowsize'] = $collection['windowsize']; $this->_markCollectionsDirty($collection['id']); } if (isset($collection['deletesasmoves'])) { $this->_data['collections'][$collection['id']]['deletesasmoves'] = $collection['deletesasmoves']; $this->_markCollectionsDirty($collection['id']); } if (isset($collection['filtertype'])) { $this->_data['collections'][$collection['id']]['filtertype'] = $collection['filtertype']; $this->_markCollectionsDirty($collection['id']); } if (isset($collection['truncation'])) { $this->_data['collections'][$collection['id']]['truncation'] = $collection['truncation']; $this->_markCollectionsDirty($collection['id']); } if (isset($collection['rtftruncation'])) { $this->_data['collections'][$collection['id']]['rtftruncation'] = $collection['rtftruncation']; $this->_markCollectionsDirty($collection['id']); } if (isset($collection['mimesupport'])) { $this->_data['collections'][$collection['id']]['mimesupport'] = $collection['mimesupport']; $this->_markCollectionsDirty($collection['id']); } if (isset($collection['mimetruncation'])) { $this->_data['collections'][$collection['id']]['mimetruncation'] = $collection['mimetruncation']; $this->_markCollectionsDirty($collection['id']); } if (isset($collection['conflict'])) { $this->_data['collections'][$collection['id']]['conflict'] = $collection['conflict']; $this->_markCollectionsDirty($collection['id']); } if (isset($collection['bodyprefs'])) { $this->_data['collections'][$collection['id']]['bodyprefs'] = $collection['bodyprefs']; $this->_markCollectionsDirty($collection['id']); } if (isset($collection['bodypartprefs'])) { $this->_data['collections'][$collection['id']]['bodypartprefs'] = $collection['bodypartprefs']; } if (isset($collection['pingable'])) { $this->_data['collections'][$collection['id']]['pingable'] = $collection['pingable']; $this->_markCollectionsDirty($collection['id']); } if (isset($collection['serverid'])) { $this->_data['collections'][$collection['id']]['serverid'] = $collection['serverid']; $this->_markCollectionsDirty($collection['id']); } if ($options['unsetChanges']) { unset($this->_data['collections'][$collection['id']]['getchanges']); $this->_markCollectionsDirty($collection['id']); } if ($options['unsetPingChangeFlag']) { unset($this->_data['collections'][$collection['id']]['pingchange']); $this->_markCollectionsDirty($collection['id']); } } else { $this->_logger->info(sprintf( '[%s] Collection without id found: %s', $this->_procid, serialize($collection)) ); } } /** * Validate the collections from the cache and fill in any missing values * from the folder cache. * * @param array $collections A reference to an array of collections. */ public function validateCollectionsFromCache(&$collections) { foreach ($collections as $key => $values) { if (!isset($values['class']) && isset($this->_data['folders'][$values['id']]['class'])) { $collections[$key]['class'] = $this->_data['folders'][$values['id']]['class']; $this->_markCollectionsDirty($key); } if (!isset($values['type']) && isset($this->_data['folders'][$values['id']]['type'])) { $collections[$key]['type'] = $this->_data['folders'][$values['id']]['type']; $this->_markCollectionsDirty($key); } if (!isset($values['filtertype']) && isset($this->_data['collections'][$values['id']]['filtertype'])) { $collections[$key]['filtertype'] = $this->_data['collections'][$values['id']]['filtertype']; $this->_markCollectionsDirty($key); } if (!isset($values['mimesupport']) && isset($this->_data['collections'][$values['id']]['mimesupport'])) { $collections[$key]['mimesupport'] = $this->_data['collections'][$values['id']]['mimesupport']; $this->_markCollectionsDirty($key); } if (empty($values['bodyprefs']) && isset($this->_data['collections'][$values['id']]['bodyprefs'])) { $collections[$key]['bodyprefs'] = $this->_data['collections'][$values['id']]['bodyprefs']; $this->_markCollectionsDirty($key); } if (empty($values['truncation']) && isset($this->_data['collections'][$values['id']]['truncation'])) { $collections[$key]['truncation'] = $this->_data['collections'][$values['id']]['truncation']; $this->_markCollectionsDirty($key); } if (empty($values['mimetruncation']) && isset($this->_data['collections'][$values['id']]['mimetruncation'])) { $collections[$key]['mimetruncation'] = $this->_data['collections'][$values['id']]['mimetruncation']; $this->_markCollectionsDirty($key); } if (empty($values['serverid']) && isset($this->_data['collections'][$values['id']]['serverid'])) { $collections[$key]['serverid'] = $this->_data['collections'][$values['id']]['serverid']; $this->_markCollectionsDirty($key); } if (!isset($values['windowsize'])) { $collections[$key]['windowsize'] = isset($this->_data['collections'][$values['id']]['windowsize']) ? $this->_data['collections'][$values['id']]['windowsize'] : 100; $this->_markCollectionsDirty($key); } // According to specs, if WINDOWSIZE is out of bounds, interpret as 512. if ($collections[$key]['windowsize'] > Horde_ActiveSync_Request_Sync::MAX_WINDOW_SIZE || $collections[$key]['windowsize'] == 0) { $collections[$key]['windowsize'] = self::MAX_WINDOW_SIZE; $this->_markCollectionsDirty($key); } if (isset($values['synckey']) && $values['synckey'] == '0' && isset($this->_data['collections'][$values['id']]['synckey']) && $this->_data['collections'][$values['id']]['synckey'] != '0') { unset($this->_data['collections'][$values['id']]['synckey']); $this->_markCollectionsDirty($key); } if (!isset($values['pingable']) && isset($this->_data['collections'][$values['id']]['pingable'])) { $collections[$key]['pingable'] = $this->_data['collections'][$values['id']]['pingable']; $this->_markCollectionsDirty($key); } } } /** * Return the folders cache. * * @param array The folders cache. */ public function getFolders() { return count($this->_data['folders']) ? $this->_data['folders'] : array(); } /** * Clear the folder cache * */ public function clearFolders() { $this->_data['folders'] = array(); $this->_dirty['folders'] = true; } /** * Refresh the folder cache from the backend. * * @since 2.18.0 */ public function refreshFolderCache() { $cache = $this->_state->getSyncCache($this->_devid, $this->_user); $this->_data['folders'] = $cache['folders']; $this->_dirty['folders'] = false; } /** * Update a folder entry in the cache. * * @param Horde_ActiveSync_Message_Folder $folder The folder object. */ public function updateFolder(Horde_ActiveSync_Message_Folder $folder) { switch ($folder->type) { case 7: case 15: $this->_data['folders'][$folder->serverid] = array('class' => 'Tasks'); break; case 8: case 13: $this->_data['folders'][$folder->serverid] = array('class' => 'Calendar'); break; case 9: case 14: $this->_data['folders'][$folder->serverid] = array('class' => 'Contacts'); break; case 17: case 10: $this->_data['folders'][$folder->serverid] = array('class' => 'Notes'); break; default: $this->_data['folders'][$folder->serverid] = array('class' => 'Email'); } $this->_data['folders'][$folder->serverid]['serverid'] = $folder->_serverid; $this->_data['folders'][$folder->serverid]['type'] = $folder->type; $this->_dirty['folders'] = true; } /** * Remove a folder from the cache * * @param string $folder The folder id to remove. */ public function deleteFolder($folder) { unset($this->_data['folders'][$folder]); unset($this->_data['collections'][$folder]); $this->_dirty['folders'] = true; $this->_markCollectionsDirty($folder); } /** * Return an entry from the folder cache. * * @param string $folder The folder id to return. * * @return array|boolean The folder cache array entry, false if not found. */ public function getFolder($folder) { return !empty($this->_data['folders'][$folder]) ? $this->_data['folders'][$folder] : false; } /** * Delete the entire synccache from the backend. */ public function delete() { $this->_state->deleteSyncCache($this->_devid, $this->_user); $this->_data = array(); $this->_dirty = array(); } /** * Mark specific collection as dirty, but only if the entire collection * data is not already marked dirty. * * @param boolean $id The collection to mark dirty. */ protected function _markCollectionsDirty($id) { if (isset($this->_dirty['collections']) && is_array($this->_dirty['collections'])) { $this->_dirty['collections'][$id] = true; } elseif (!isset($this->_dirty['collections']) || $this->_dirty['collections'] !== true) { $this->_dirty['collections'] = array(); $this->_markCollectionsDirty($id); } } }Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Timezone.php0000664000076500000240000004112712654565405020112 0ustar * @package ActiveSync */ /** * Utility functions for dealing with Microsoft ActiveSync's Timezone format. * * Copyright 2009-2016 Horde LLC (http://www.horde.org/) * * See the enclosed file COPYING for license information (LGPL). If you * did not receive this file, see http://www.horde.org/licenses/lgpl21. * * Code dealing with searching for a timezone identifier from an AS timezone * blob inspired by code in the Tine20 Project (http://tine20.org). * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2009-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync * @deprecated To be removed for Horde 6. Use Horde_Mapi_Timezone::. */ class Horde_ActiveSync_Timezone { /** * Date to use as start date when iterating through offsets looking for a * transition. * * @var Horde_Date */ protected $_startDate; /** * Convert a timezone from the ActiveSync base64 structure to a TZ offset * hash. * * @param base64 encoded timezone structure defined by MS as: *
     *      typedef struct TIME_ZONE_INFORMATION {
     *        LONG Bias;
     *        WCHAR StandardName[32];
     *        SYSTEMTIME StandardDate;
     *        LONG StandardBias;
     *        WCHAR DaylightName[32];
     *        SYSTEMTIME DaylightDate;
     *        LONG DaylightBias;};
     *  
* * With the SYSTEMTIME format being: *
     * typedef struct _SYSTEMTIME {
     *     WORD wYear;
     *     WORD wMonth;
     *     WORD wDayOfWeek;
     *     WORD wDay;
     *     WORD wHour;
     *     WORD wMinute;
     *     WORD wSecond;
     *     WORD wMilliseconds;
     *   } SYSTEMTIME, *PSYSTEMTIME;
     *  
* * See: http://msdn.microsoft.com/en-us/library/ms724950%28VS.85%29.aspx * and: http://msdn.microsoft.com/en-us/library/ms725481%28VS.85%29.aspx * * @return array Hash of offset information */ public static function getOffsetsFromSyncTZ($data) { if (version_compare(PHP_VERSION, '5.5', '>=')) { $format = 'lbias/Z64stdname/vstdyear/vstdmonth/vstdday/vstdweek/vstdhour/vstdminute/vstdsecond/vstdmillis/' . 'lstdbias/Z64dstname/vdstyear/vdstmonth/vdstday/vdstweek/vdsthour/vdstminute/vdstsecond/vdstmillis/' . 'ldstbias'; } else { $format = 'lbias/a64stdname/vstdyear/vstdmonth/vstdday/vstdweek/vstdhour/vstdminute/vstdsecond/vstdmillis/' . 'lstdbias/a64dstname/vdstyear/vdstmonth/vdstday/vdstweek/vdsthour/vdstminute/vdstsecond/vdstmillis/' . 'ldstbias'; } $tz = unpack($format, base64_decode($data)); $tz['timezone'] = $tz['bias']; $tz['timezonedst'] = $tz['dstbias']; if (!Horde_Mapi::isLittleEndian()) { $tz['bias'] = self::_chbo($tz['bias']); $tz['stdbias'] = self::_chbo($tz['stdbias']); $tz['dstbias'] = self::_chbo($tz['dstbias']); } return $tz; } /** * Build an ActiveSync TZ blob given a TZ Offset hash. * * @param array $offsets A TZ offset hash * * @return string A base64_encoded ActiveSync Timezone structure suitable * for transmitting via wbxml. */ public static function getSyncTZFromOffsets(array $offsets) { if (!Horde_Mapi::isLittleEndian()) { $offsets['bias'] = self::_chbo($offsets['bias']); $offsets['stdbias'] = self::_chbo($offsets['stdbias']); $offsets['dstbias'] = self::_chbo($offsets['dstbias']); } $packed = pack('la64vvvvvvvvla64vvvvvvvvl', $offsets['bias'], '', 0, $offsets['stdmonth'], $offsets['stdday'], $offsets['stdweek'], $offsets['stdhour'], $offsets['stdminute'], $offsets['stdsecond'], $offsets['stdmillis'], $offsets['stdbias'], '', 0, $offsets['dstmonth'], $offsets['dstday'], $offsets['dstweek'], $offsets['dsthour'], $offsets['dstminute'], $offsets['dstsecond'], $offsets['dstmillis'], $offsets['dstbias']); return base64_encode($packed); } /** * Create a offset hash suitable for use in ActiveSync transactions * * @param Horde_Date $date A date object representing the date to base the * the tz data on. * * @return array An offset hash. */ public static function getOffsetsFromDate(Horde_Date $date) { $offsets = array( 'bias' => 0, 'stdname' => '', 'stdyear' => 0, 'stdmonth' => 0, 'stdday' => 0, 'stdweek' => 0, 'stdhour' => 0, 'stdminute' => 0, 'stdsecond' => 0, 'stdmillis' => 0, 'stdbias' => 0, 'dstname' => '', 'dstyear' => 0, 'dstmonth' => 0, 'dstday' => 0, 'dstweek' => 0, 'dsthour' => 0, 'dstminute' => 0, 'dstsecond' => 0, 'dstmillis' => 0, 'dstbias' => 0 ); $timezone = $date->toDateTime()->getTimezone(); list($std, $dst) = self::_getTransitions($timezone, $date); if ($std) { $offsets['bias'] = $std['offset'] / 60 * -1; if ($dst) { $offsets = self::_generateOffsetsForTransition($offsets, $std, 'std'); $offsets = self::_generateOffsetsForTransition($offsets, $dst, 'dst'); $offsets['stdhour'] += $dst['offset'] / 3600; $offsets['dsthour'] += $std['offset'] / 3600; $offsets['dstbias'] = ($dst['offset'] - $std['offset']) / 60 * -1; } } return $offsets; } /** * Get the transition data for moving from DST to STD time. * * @param DateTimeZone $timezone The timezone to get the transition for * @param Horde_Date $date The date to start from. Really only the * year we are interested in is needed. * * @return array An array containing the the STD and DST transitions */ protected static function _getTransitions(DateTimeZone $timezone, Horde_Date $date) { $std = $dst = array(); $transitions = $timezone->getTransitions( mktime(0, 0, 0, 12, 1, $date->year - 1), mktime(24, 0, 0, 12, 31, $date->year) ); foreach ($transitions as $i => $transition) { try { $d = new Horde_Date($transition['time']); $d->setTimezone('UTC'); } catch (Exception $e) { continue; } if (($d->format('Y') == $date->format('Y')) && isset($transitions[$i + 1])) { $next = new Horde_Date($transitions[$i + 1]['ts']); if ($d->format('Y') == $next->format('Y')) { $dst = $transition['isdst'] ? $transition : $transitions[$i + 1]; $std = $transition['isdst'] ? $transitions[$i + 1] : $transition; } else { $dst = $transition['isdst'] ? $transition: null; $std = $transition['isdst'] ? null : $transition; } break; } elseif ($i == count($transitions) - 1) { $std = $transition; } } return array($std, $dst); } /** * Calculate the offsets for the specified transition * * @param array $offsets A TZ offset hash * @param array $transition A transition hash * @param string $type Transition type - dst or std * * @return array A populated offset hash */ protected static function _generateOffsetsForTransition(array $offsets, array $transition, $type) { // We can't use Horde_Date directly here, since it is unable to // properly convert to UTC from local ON the exact hour of a std -> dst // transition. This is due to a conversion to DateTime in the localtime // zone internally before the timezone change is applied $transitionDate = new DateTime($transition['time']); $transitionDate->setTimezone(new DateTimeZone('UTC')); $transitionDate = new Horde_Date($transitionDate); $offsets[$type . 'month'] = $transitionDate->format('n'); $offsets[$type . 'day'] = $transitionDate->format('w'); $offsets[$type . 'minute'] = (int)$transitionDate->format('i'); $offsets[$type . 'hour'] = (int)$transitionDate->format('H'); for ($i = 5; $i > 0; $i--) { if (self::_isNthOcurrenceOfWeekdayInMonth($transition['ts'], $i)) { $offsets[$type . 'week'] = $i; break; } } return $offsets; } /** * Attempt to guess the timezone identifier from the $offsets array. * * @param array|string $offsets The timezone to check. Either an array * of offsets or an activesynz tz blob. * @param string $expectedTimezone The expected timezone. If not empty, and * present in the results, will return. * * @return string The timezone identifier */ public function getTimezone($offsets, $expectedTimezone = null) { $timezones = $this->getListOfTimezones($offsets, $expectedTimezone); if (isset($timezones[$expectedTimezone])) { return $expectedTimezone; } else { return current($timezones); } } /** * Get the list of timezone identifiers that match the given offsets, having * a preference for $expectedTimezone if it's present in the results. * * @param array|string $offsets Either an offset array, or a AS timezone * structure. * @param string $expectedTimezone The expected timezone. * * @return array An array of timezone identifiers */ public function getListOfTimezones($offsets, $expectedTimezone = null) { if (is_string($offsets)) { $offsets = self::getOffsetsFromSyncTZ($offsets); } $this->_setDefaultStartDate($offsets); $timezones = array(); foreach (DateTimeZone::listIdentifiers() as $timezoneIdentifier) { $timezone = new DateTimeZone($timezoneIdentifier); if (false !== ($matchingTransition = $this->_checkTimezone($timezone, $offsets))) { if ($timezoneIdentifier == $expectedTimezone) { $timezones = array($timezoneIdentifier => $matchingTransition['abbr']); break; } else { $timezones[$timezoneIdentifier] = $matchingTransition['abbr']; } } } if (empty($timezones)) { throw new Horde_ActiveSync_Exception('No timezone found for the given offsets'); } return $timezones; } /** * Set default value for $_startDate. * * Tries to guess the correct startDate depending on object property falls * back to current date. * * @param array $offsets Offsets may be avaluated for a given start year */ protected function _setDefaultStartDate(array $offsets = null) { if (!empty($this->_startDate)) { return; } if (!empty($offsets['stdyear'])) { $this->_startDate = new Horde_Date($offsets['stdyear'] . '-01-01'); } else { $start = new Horde_Date(time()); $start->year--; $this->_startDate = $start; } } /** * Check if the given timezone matches the offsets and also evaluate the * daylight saving time transitions for this timezone if necessary. * * @param DateTimeZone $timezone The timezone to check. * @param array $offsets The offsets to check. * * @return array|boolean An array of transition data or false if timezone * does not match offset. */ protected function _checkTimezone(DateTimeZone $timezone, array $offsets) { list($std, $dst) = $this->_getTransitions($timezone, $this->_startDate); if ($this->_checkTransition($std, $dst, $offsets)) { return $std; } return false; } /** * Check if the given standardTransition and daylightTransition match to the * given offsets. * * @param array $std The Standard transition date. * @param array $dst The DST transition date. * @param array $offsets The offsets to check. * * @return boolean */ protected function _checkTransition(array $std, array $dst, array $offsets) { if (empty($std) || empty($offsets)) { return false; } $standardOffset = ($offsets['bias'] + $offsets['stdbias']) * 60 * -1; // check each condition in a single if statement and break the chain // when one condition is not met - for performance reasons if ($standardOffset == $std['offset']) { if ((empty($offsets['dstmonth']) && (empty($dst) || empty($dst['isdst']))) || (empty($dst) && !empty($offsets['dstmonth']))) { // Offset contains DST, but no dst to compare return true; } $daylightOffset = ($offsets['bias'] + $offsets['dstbias']) * 60 * -1; // the milestone is sending a positive value for daylightBias while it should send a negative value $daylightOffsetMilestone = ($offsets['dstbias'] + ($offsets['dstbias'] * -1) ) * 60 * -1; if ($daylightOffset == $dst['offset'] || $daylightOffsetMilestone == $dst['offset']) { $standardParsed = new DateTime($std['time']); $daylightParsed = new DateTime($dst['time']); if ($standardParsed->format('n') == $offsets['stdmonth'] && $daylightParsed->format('n') == $offsets['dstmonth'] && $standardParsed->format('w') == $offsets['stdday'] && $daylightParsed->format('w') == $offsets['dstday']) { return self::_isNthOcurrenceOfWeekdayInMonth($dst['ts'], $offsets['dstweek']) && self::_isNthOcurrenceOfWeekdayInMonth($std['ts'], $offsets['stdweek']); } } } return false; } /** * Test if the weekday of the given timestamp is the nth occurence of this * weekday within its month, where '5' indicates the last occurrence even if * there is less than five occurrences. * * @param integer $timestamp The timestamp to check. * @param integer $occurence 1 to 5, where 5 indicates the final occurrence * during the month if that day of the week does * not occur 5 times * @return boolean */ protected static function _isNthOcurrenceOfWeekdayInMonth($timestamp, $occurence) { $original = new Horde_Date($timestamp); $original->setTimezone('UTC'); if ($occurence == 5) { $modified = $original->add(array('mday' => 7)); return $modified->month > $original->month; } else { $modified = $original->sub(array('mday' => 7 * $occurence)); $modified2 = $original->sub(array('mday' => 7 * ($occurence - 1))); return $modified->month < $original->month && $modified2->month == $original->month; } } /** * Change the byte order of a number. Used to allow big endian machines to * decode the timezone blobs, which are encoded in little endian order. * * @param integer $num The number to reverse. * * @return integer The number, in the reverse byte order. */ protected static function _chbo($num) { $u = unpack('l', strrev(pack('l', $num))); return $u[1]; } } Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Translation.php0000664000076500000240000000262712654565405020620 0ustar * @package ActiveSync */ /** * Horde_ActiveSync_Translation is the translation wrapper class * for Horde_ActiveSync. * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2010-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync */ class Horde_ActiveSync_Translation extends Horde_Translation_Autodetect { /** * The translation domain * * @var string */ protected static $_domain = 'Horde_ActiveSync'; /** * The absolute PEAR path to the translations for the default gettext handler. * * @var string */ protected static $_pearDirectory = '@data_dir@'; } Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Utils.php0000664000076500000240000002373012654565405017420 0ustar * @package ActiveSync */ /** * Horde_ActiveSync_Utils:: contains general utilities. * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2010-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync */ class Horde_ActiveSync_Utils { /** * Decode a base64 encoded URI * * @param string $url The Base64 encoded string. * * @return array The decoded request */ public static function decodeBase64($uri) { $commandMap = array( 0 => 'Sync', 1 => 'SendMail', 2 => 'SmartForward', 3 => 'SmartReply', 4 => 'GetAttachment', 9 => 'FolderSync', 10 => 'FolderCreate', 11 => 'FolderDelete', 12 => 'FolderUpdate', 13 => 'MoveItems', 14 => 'GetItemEstimate', 15 => 'MeetingResponse', 16 => 'Search', 17 => 'Settings', 18 => 'Ping', 19 => 'ItemOperations', 20 => 'Provision', 21 => 'ResolveRecipients', 22 => 'ValidateCert' ); $stream = fopen('php://temp', 'r+'); fwrite($stream, base64_decode($uri)); rewind($stream); $results = array(); // Version, command, locale $data = unpack('CprotocolVersion/Ccommand/vlocale', fread($stream, 4)); $results['ProtVer'] = substr($data['protocolVersion'], 0, -1) . '.' . substr($data['protocolVersion'], -1); $results['Cmd'] = $commandMap[$data['command']]; $results['Locale'] = $data['locale']; // deviceId $length = ord(fread($stream, 1)); if ($length > 0) { $data = fread($stream, $length); $data = unpack('H' . ($length * 2) . 'DevID', $data); $results['DeviceId'] = $data['DevID']; } // policyKey $length = ord(fread($stream, 1)); if ($length > 0) { $data = unpack('VpolicyKey', fread($stream, $length)); $results['PolicyKey'] = $data['policyKey']; } // deviceType $length = ord(fread($stream, 1)); if ($length > 0) { $data = unpack('A' . $length . 'devType', fread($stream, $length)); $results['DeviceType'] = $data['devType']; } // Remaining properties while (!feof($stream)) { $tag = ord(fread($stream, 1)); $length = ord(fread($stream, 1)); if ($length > 0 || $tag == 7) { switch ($tag) { case 0: $data = unpack('A' . $length . 'AttName', fread($stream, $length)); $results['AttachmentName'] = $data['AttName']; break; case 1: $data = unpack('A' . $length . 'CollId', fread($stream, $length)); $results['CollectionId'] = $data['CollId']; break; case 3: $data = unpack('A' . $length . 'ItemId', fread($stream, $length)); $results['ItemId'] = $data['ItemId']; break; case 4: $data = unpack('A' . $length . 'Lid', fread($stream, $length)); $results['LongId'] = $data['Lid']; break; case 5: $data = unpack('A' . $length . 'Pid', fread($stream, $length)); $results['ParentId'] = $data['Pid']; break; case 6: $data = unpack('A' . $length . 'Oc', fread($stream, $length)); $results['Occurrence'] = $data['Oc']; break; case 7: $options = ord(fread($stream, 1)); $results['SaveInSent'] = !!($options & 0x01); $results['AcceptMultiPart'] = !!($options & 0x02); break; case 8: $data = unpack('A' . $length . 'User', fread($stream, $length)); $results['User'] = $data['User']; break; } } } return $results; } /** * Obtain the UID from a MAPI GOID. * * See http://msdn.microsoft.com/en-us/library/hh338153%28v=exchg.80%29.aspx * * @param string $goid Base64 encoded Global Object Identifier. * * @return string The UID * @deprecated Will be removed in H6. Use Horde_Mapi::getUidFromGoid */ public static function getUidFromGoid($goid) { $goid = base64_decode($goid); // First, see if it's an Outlook UID or not. if (substr($goid, 40, 8) == 'vCal-Uid') { // For vCal UID values: // Bytes 37 - 40 contain length of data and padding // Bytes 41 - 48 are == vCal-Uid // Bytes 53 until next to the last byte (/0) contain the UID. return trim(substr($goid, 52, strlen($goid) - 1)); } else { // If it's not a vCal UID, then it is Outlook style UID: // The entire decoded goid is converted to hex representation with // bytes 17 - 20 converted to zero $hex = array(); foreach (str_split($goid) as $chr) { $hex[] = sprintf('%02X', ord($chr)); } array_splice($hex, 16, 4, array('00', '00', '00', '00')); return implode('', $hex); } } /** * Create a MAPI GOID from a UID * See http://msdn.microsoft.com/en-us/library/ee157690%28v=exchg.80%29 * * @param string $uid The UID value to encode. * * @return string A Base64 encoded GOID * @deprecated Will be removed in H6. Use Horde_Mapi::createGoid */ public static function createGoid($uid, $options = array()) { // Bytes 1 - 16 MUST be equal to the GOID identifier: $arrayid = '040000008200E00074C5B7101A82E008'; // Bytes 17 - 20 - Exception replace time (YH YL M D) $exception = '00000000'; // Bytes 21 - 28 The 8 byte creation time (can be all zeros if not available). $creationtime = '0000000000000000'; // Bytes 29 - 36 Reserved 8 bytes must be all zeros. $reserved = '0000000000000000'; // Bytes 37 - 40 - A long value describing the size of the UID data. $size = strlen($uid); // Bytes 41 - 52 - MUST BE vCal-Uid 0x01 0x00 0x00 0x00 $vCard = '7643616C2D55696401000000'; // The UID Data: $hexuid = ''; foreach (str_split($uid) as $chr) { $hexuid .= sprintf('%02X', ord($chr)); } // Pack it $goid = pack('H*H*H*H*VH*H*x', $arrayid, $exception, $creationtime, $reserved, $size, $vCard, $hexuid); return base64_encode($goid); } /** * Ensure $data is converted to valid UTF-8 data. Works as follows: * Converts to UTF-8, assuming data is in $from_charset encoding. If * that produces invalid UTF-8, attempt to convert to most common mulitibyte * encodings. If that *still* fails, strip out non 7-Bit characters...and * force encoding to UTF-8 from $from_charset as a last resort. * * @param string $data The string data to convert to UTF-8. * @param string $from_charset The character set to assume $data is encoded * in. * * @return string A valid UTF-8 encoded string. */ public static function ensureUtf8($data, $from_charset) { $text = Horde_String::convertCharset($data, $from_charset, 'UTF-8'); if (!Horde_String::validUtf8($text)) { $test_charsets = array( 'windows-1252', 'UTF-8' ); foreach ($test_charsets as $charset) { if ($charset != $from_charset) { $text = Horde_String::convertCharset($data, $charset, 'UTF-8'); if (Horde_String::validUtf8($text)) { return $text; } } } // Invalid UTF-8 still found. Strip out non 7-bit characters, or if // that fails, force a conversion to UTF-8 as a last resort. Need // to break string into smaller chunks to avoid hitting // https://bugs.php.net/bug.php?id=37793 $chunk_size = 4000; $text = ''; while ($data !== false && strlen($data)) { $test = self::_stripNon7BitChars(substr($data, 0, $chunk_size)); if ($test !== false) { $text .= $test; } else { return Horde_String::convertCharset($data, $from_charset, 'UTF-8', true); } $data = substr($data, $chunk_size); } } return $text; } /** * Strip out non 7Bit characters from a text string. * * @param string $text The string to strip. * * @return string|boolean The stripped string, or false if failed. */ protected static function _stripNon7BitChars($text) { return preg_replace('/[^\x09\x0A\x0D\x20-\x7E]/', '', $text); } }Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync/Wbxml.php0000664000076500000240000007703212654565405017415 0ustar * @package ActiveSync */ /** * Wbxml handler * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2009-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync */ class Horde_ActiveSync_Wbxml { const SWITCH_PAGE = 0x00; const END = 0x01; const ENTITY = 0x02; const STR_I = 0x03; const LITERAL = 0x04; const EXT_I_0 = 0x40; const EXT_I_1 = 0x41; const EXT_I_2 = 0x42; const PI = 0x43; const LITERAL_C = 0x44; const EXT_T_0 = 0x80; const EXT_T_1 = 0x81; const EXT_T_2 = 0x82; const STR_T = 0x83; const LITERAL_A = 0x84; const EXT_0 = 0xC0; const EXT_1 = 0xC1; const EXT_2 = 0xC2; const OPAQUE = 0xC3; const LITERAL_AC = 0xC4; const EN_TYPE = 1; const EN_TAG = 2; const EN_CONTENT = 3; const EN_FLAGS = 4; const EN_ATTRIBUTES = 5; const EN_TYPE_STARTTAG = 1; const EN_TYPE_ENDTAG = 2; const EN_TYPE_CONTENT = 3; const EN_FLAGS_CONTENT = 1; const EN_FLAGS_ATTRIBUTES = 2; /* Valid WBXML Version header value */ const WBXML_VERSION = 0x03; /* Logging levels */ // LOG_DETAILED = All data logged. // LOG_PROTOCOL = Protocol detail is logged, but if content length of // non-stream data exceeds LOG_MAXCONTENT bytes, only the size is logged. const LOG_DETAILED = 1; const LOG_PROTOCOL = 2; const LOG_MAXCONTENT = 50; /** * The code page definitions for the wbxml encoder/decoders * * @var array */ protected $_dtd = array( 'codes' => array ( 0 => array ( 0x05 => 'Synchronize', 0x06 => 'Replies', 0x07 => 'Add', 0x08 => 'Modify', 0x09 => 'Remove', 0x0a => 'Fetch', 0x0b => 'SyncKey', 0x0c => 'ClientEntryId', 0x0d => 'ServerEntryId', 0x0e => 'Status', 0x0f => 'Folder', 0x10 => 'FolderType', 0x11 => 'Version', 0x12 => 'FolderId', 0x13 => 'GetChanges', 0x14 => 'MoreAvailable', 0x15 => 'WindowSize', 0x16 => 'Commands', 0x17 => 'Options', 0x18 => 'FilterType', 0x19 => 'Truncation', 0x1a => 'RtfTruncation', 0x1b => 'Conflict', 0x1c => 'Folders', 0x1d => 'Data', 0x1e => 'DeletesAsMoves', 0x1f => 'NotifyGUID', 0x20 => 'Supported', 0x21 => 'SoftDelete', 0x22 => 'MIMESupport', 0x23 => 'MIMETruncation', 0x24 => 'Wait', 0x25 => 'Limit', 0x26 => 'Partial', // EAS 14.0 0x27 => 'ConversationMode', 0x28 => 'MaxItems', 0x29 => 'HeartbeatInterval', ), /* POOMCONTACTS */ 1 => array ( 0x05 => 'Anniversary', 0x06 => 'AssistantName', 0x07 => 'AssistnamePhoneNumber', 0x08 => 'Birthday', 0x09 => 'Body', 0x0a => 'BodySize', 0x0b => 'BodyTruncated', 0x0c => 'Business2PhoneNumber', 0x0d => 'BusinessCity', 0x0e => 'BusinessCountry', 0x0f => 'BusinessPostalCode', 0x10 => 'BusinessState', 0x11 => 'BusinessStreet', 0x12 => 'BusinessFaxNumber', 0x13 => 'BusinessPhoneNumber', 0x14 => 'CarPhoneNumber', 0x15 => 'Categories', 0x16 => 'Category', 0x17 => 'Children', 0x18 => 'Child', 0x19 => 'CompanyName', 0x1a => 'Department', 0x1b => 'Email1Address', 0x1c => 'Email2Address', 0x1d => 'Email3Address', 0x1e => 'FileAs', 0x1f => 'FirstName', 0x20 => 'Home2PhoneNumber', 0x21 => 'HomeCity', 0x22 => 'HomeCountry', 0x23 => 'HomePostalCode', 0x24 => 'HomeState', 0x25 => 'HomeStreet', 0x26 => 'HomeFaxNumber', 0x27 => 'HomePhoneNumber', 0x28 => 'JobTitle', 0x29 => 'LastName', 0x2a => 'MiddleName', 0x2b => 'MobilePhoneNumber', 0x2c => 'OfficeLocation', 0x2d => 'OtherCity', 0x2e => 'OtherCountry', 0x2f => 'OtherPostalCode', 0x30 => 'OtherState', 0x31 => 'OtherStreet', 0x32 => 'PagerNumber', 0x33 => 'RadioPhoneNumber', 0x34 => 'Spouse', 0x35 => 'Suffix', 0x36 => 'Title', 0x37 => 'WebPage', 0x38 => 'YomiCompanyName', 0x39 => 'YomiFirstName', 0x3a => 'YomiLastName', 0x3b => 'Rtf', // EAS 2.5 only. 0x3c => 'Picture', // EAS 14.0 0x3d => 'Alias', 0x3e => 'WeightedRank', ), /* POOMMAIL */ 2 => array ( 0x05 => 'Attachment', 0x06 => 'Attachments', 0x07 => 'AttName', 0x08 => 'AttSize', 0x09 => 'AttOid', 0x0a => 'AttMethod', 0x0b => 'AttRemoved', 0x0c => 'Body', 0x0d => 'BodySize', 0x0e => 'BodyTruncated', 0x0f => 'DateReceived', 0x10 => 'DisplayName', 0x11 => 'DisplayTo', 0x12 => 'Importance', 0x13 => 'MessageClass', 0x14 => 'Subject', 0x15 => 'Read', 0x16 => 'To', 0x17 => 'Cc', 0x18 => 'From', 0x19 => 'Reply-To', 0x1a => 'AllDayEvent', 0x1b => 'Categories', // EAS 14.0 0x1c => 'Category', // EAS 14.0 0x1d => 'DtStamp', 0x1e => 'EndTime', 0x1f => 'InstanceType', 0x20 => 'BusyStatus', 0x21 => 'Location', 0x22 => 'MeetingRequest', 0x23 => 'Organizer', 0x24 => 'RecurrenceId', 0x25 => 'Reminder', 0x26 => 'ResponseRequested', 0x27 => 'Recurrences', 0x28 => 'Recurrence', 0x29 => 'Type', 0x2a => 'Until', 0x2b => 'Occurrences', 0x2c => 'Interval', 0x2d => 'DayOfWeek', 0x2e => 'DayOfMonth', 0x2f => 'WeekOfMonth', 0x30 => 'MonthOfYear', 0x31 => 'StartTime', 0x32 => 'Sensitivity', 0x33 => 'TimeZone', 0x34 => 'GlobalObjId', 0x35 => 'ThreadTopic', 0x36 => 'MIMEData', 0x37 => 'MIMETruncated', 0x38 => 'MIMESize', 0x39 => 'InternetCPID', // EAS 12.0 0x3a => 'Flag', 0x3b => 'FlagStatus', 0x3c => 'ContentClass', 0x3d => 'FlagType', 0x3e => 'CompleteTime', // EAS 14.0 0x3f => 'DisallowNewTimeProposal', ), /* 3 == AirNotify == deprecated */ /* POOMCAL */ 4 => array ( 0x05 => 'Timezone', 0x06 => 'AllDayEvent', 0x07 => 'Attendees', 0x08 => 'Attendee', 0x09 => 'Email', 0x0a => 'Name', 0x0b => 'Body', // 2.5 Only 0x0c => 'BodyTruncated', // 2.5 Only 0x0d => 'BusyStatus', 0x0e => 'Categories', 0x0f => 'Category', 0x10 => 'Rtf', // 2.5 ONly 0x11 => 'DtStamp', 0x12 => 'EndTime', 0x13 => 'Exception', 0x14 => 'Exceptions', 0x15 => 'Deleted', 0x16 => 'ExceptionStartTime', 0x17 => 'Location', 0x18 => 'MeetingStatus', 0x19 => 'OrganizerEmail', 0x1a => 'OrganizerName', 0x1b => 'Recurrence', 0x1c => 'Type', 0x1d => 'Until', 0x1e => 'Occurrences', 0x1f => 'Interval', 0x20 => 'DayOfWeek', 0x21 => 'DayOfMonth', 0x22 => 'WeekOfMonth', 0x23 => 'MonthOfYear', 0x24 => 'Reminder', 0x25 => 'Sensitivity', 0x26 => 'Subject', 0x27 => 'StartTime', 0x28 => 'UID', // EAS 12.0 0x29 => 'AttendeeStatus', 0x2A => 'AttendeeType', // EAS 12.1 (Apparently no longer documented). 0x2B => 'Attachment', 0x2C => 'Attachments', 0x2D => 'AttName', 0x2E => 'AttSize', 0x2F => 'AttOid', 0x30 => 'AttMethod', 0x31 => 'AttRemoved', 0x32 => 'DisplayName', // EAS 14 0x33 => 'DisallowNewTimeProposal', 0x34 => 'ResponseRequested', 0x35 => 'AppointmentReplyTime', 0x36 => 'ResponseType', 0x37 => 'CalendarType', 0x38 => 'IsLeapMonth', // EAS 14.1 0x39 => 'FirstDayOfWeek', 0x3a => 'OnlineMeetingConfLink', 0x3b => 'OnlineMeetingExternalLink', // EAS 16.0 0x3c => 'ClientUid', ), /* MOVE */ 5 => array ( 0x05 => 'Moves', 0x06 => 'Move', 0x07 => 'SrcMsgId', 0x08 => 'SrcFldId', 0x09 => 'DstFldId', 0x0a => 'Response', 0x0b => 'Status', 0x0c => 'DstMsgId', ), /* GETITEMESTIMATE */ 6 => array ( 0x05 => 'GetItemEstimate', 0x06 => 'Version', // 12.1 0x07 => 'Folders', 0x08 => 'Folder', 0x09 => 'FolderType', // 12.1 0x0a => 'FolderId', 0x0b => 'DateTime', // 12.1 0x0c => 'Estimate', 0x0d => 'Response', 0x0e => 'Status', ), /* FOLDERHIERARCHY */ 7 => array ( 0x05 => 'Folders', 0x06 => 'Folder', 0x07 => 'DisplayName', 0x08 => 'ServerEntryId', 0x09 => 'ParentId', 0x0a => 'Type', 0x0b => 'Response', 0x0c => 'Status', 0x0d => 'ContentClass', 0x0e => 'Changes', 0x0f => 'Add', 0x10 => 'Remove', 0x11 => 'Update', 0x12 => 'SyncKey', 0x13 => 'FolderCreate', 0x14 => 'FolderDelete', 0x15 => 'FolderUpdate', 0x16 => 'FolderSync', 0x17 => 'Count', 0x18 => 'Version', ), /* MEETINGRESPONSE */ 8 => array ( 0x05 => 'CalendarId', 0x06 => 'FolderId', 0x07 => 'MeetingResponse', 0x08 => 'RequestId', 0x09 => 'Request', 0x0a => 'Result', 0x0b => 'Status', 0x0c => 'UserResponse', 0x0d => 'Version', // EAS 14.1 0x0e => 'InstanceId', // EAS 16.0 0x12 => 'SendResponse', ), /* POOMTASKS */ 9 => array ( 0x05 => 'Body', 0x06 => 'BodySize', 0x07 => 'BodyTruncated', 0x08 => 'Categories', 0x09 => 'Category', 0x0a => 'Complete', 0x0b => 'DateCompleted', 0x0c => 'DueDate', 0x0d => 'UtcDueDate', 0x0e => 'Importance', 0x0f => 'Recurrence', 0x10 => 'Type', 0x11 => 'Start', 0x12 => 'Until', 0x13 => 'Occurrences', 0x14 => 'Interval', 0x16 => 'DayOfWeek', 0x15 => 'DayOfMonth', 0x17 => 'WeekOfMonth', 0x18 => 'MonthOfYear', 0x19 => 'Regenerate', 0x1a => 'DeadOccur', 0x1b => 'ReminderSet', 0x1c => 'ReminderTime', 0x1d => 'Sensitivity', 0x1e => 'StartDate', 0x1f => 'UtcStartDate', 0x20 => 'Subject', 0x21 => 'Rtf', // EAS 12.0 0x22 => 'OrdinalDate', 0x23 => 'SubOrdinalDate', // EAS 14.0 0x24 => 'CalendarType', 0x25 => 'IsLeapMonth', // EAS 14.1 0x26 => 'FirstDayOfWeek', ), /* RESOLVERECIPIENTS */ 0xa => array ( 0x05 => 'ResolveRecipients', 0x06 => 'Response', 0x07 => 'Status', 0x08 => 'Type', 0x09 => 'Recipient', 0x0a => 'DisplayName', 0x0b => 'EmailAddress', 0x0c => 'Certificates', 0x0d => 'Certificate', 0x0e => 'MiniCertificate', 0x0f => 'Options', 0x10 => 'To', 0x11 => 'CertificateRetrieval', 0x12 => 'RecipientCount', 0x13 => 'MaxCertificates', 0x14 => 'MaxAmbiguousRecipients', 0x15 => 'CertificateCount', 0x16 => 'Availability', 0x17 => 'StartTime', 0x18 => 'EndTime', 0x19 => 'MergedFreeBusy', // 14.1 0x1a => 'Picture', 0x1b => 'MaxSize', 0x1c => 'Data', 0x1d => 'MaxPictures', ), /* VALIDATECERT */ 0xb => array ( 0x05 => 'ValidateCert', 0x06 => 'Certificates', 0x07 => 'Certificate', 0x08 => 'CertificateChain', 0x09 => 'CheckCRL', 0x0a => 'Status', ), /* POOMCONTACTS2*/ 0xc => array ( 0x05 => 'CustomerId', 0x06 => 'GovernmentId', 0x07 => 'IMAddress', 0x08 => 'IMAddress2', 0x09 => 'IMAddress3', 0x0a => 'ManagerName', 0x0b => 'CompanyMainPhone', 0x0c => 'AccountName', 0x0d => 'NickName', 0x0e => 'MMS', ), /* PING */ 0xd => array ( 0x05 => 'Ping', 0x06 => 'AutdState', 0x07 => 'Status', 0x08 => 'HeartbeatInterval', 0x09 => 'Folders', 0x0a => 'Folder', 0x0b => 'ServerEntryId', 0x0c => 'FolderType', 0x0d => 'MaxFolders', ), /* PROVISION */ 0xe => array ( 0x05 => 'Provision', 0x06 => 'Policies', 0x07 => 'Policy', 0x08 => 'PolicyType', 0x09 => 'PolicyKey', 0x0A => 'Data', 0x0B => 'Status', 0x0C => 'RemoteWipe', 0x0D => 'EASProvisionDoc', // EAS 12.0 0x0E => 'DevicePasswordEnabled', 0x0F => 'AlphanumericDevicePasswordRequired', 0x10 => 'DeviceEncryptionEnabled', 0x11 => 'PasswordRecoveryEnabled', 0x12 => 'DocumentBrowseEnabled', 0x13 => 'AttachmentsEnabled', 0x14 => 'MinDevicePasswordLength', 0x15 => 'MaxInactivityTimeDeviceLock', 0x16 => 'MaxDevicePasswordFailedAttempts', 0x17 => 'MaxAttachmentSize', 0x18 => 'AllowSimpleDevicePassword', 0x19 => 'DevicePasswordExpiration', 0x1A => 'DevicePasswordHistory', // EAS 12.1 0x1B => 'AllowStorageCard', 0x1C => 'AllowCamera', 0x1D => 'RequireDeviceEncryption', 0x1E => 'AllowUnsignedApplications', 0x1F => 'AllowUnsignedInstallationPackages', 0x20 => 'MinDevicePasswordComplexCharacters', 0x21 => 'AllowWiFi', 0x22 => 'AllowTextMessaging', 0x23 => 'AllowPOPIMAPEmail', 0x24 => 'AllowBluetooth', 0x25 => 'AllowIrDA', 0x26 => 'RequireManualSyncWhenRoaming', 0x27 => 'AllowDesktopSync', 0x28 => 'MaxCalendarAgeFilter', 0x29 => 'AllowHTMLEmail', 0x2A => 'MaxEmailAgeFilter', 0x2B => 'MaxEmailBodyTruncationSize', 0x2C => 'MaxHTMLBodyTruncationSize', 0x2D => 'RequireSignedSMIMEMessages', 0x2E => 'RequireEncryptedSMIMEMessages', 0x2F => 'RequireSignedSMIMEAlgorithm', 0x30 => 'RequireEncryptedSMIMEAlgorithm', 0x31 => 'AllowSMIMEEncryptionAlgorithmNegotiation', 0x32 => 'AllowSMIMESoftCerts', 0x33 => 'AllowBrowser', 0x34 => 'AllowConsumerEmail', 0x35 => 'AllowRemoteDesktop', 0x36 => 'AllowInternetSharing', 0x37 => 'UnapprovedInROMApplicationList', 0x38 => 'ApplicationName', 0x39 => 'ApprovedApplicationList', 0x3A => 'Hash', ), /* SEARCH */ 0xf => array( 0x05 => 'Search', 0x07 => 'Store', 0x08 => 'Name', 0x09 => 'Query', 0x0A => 'Options', 0x0B => 'Range', 0x0C => 'Status', 0x0D => 'Response', 0x0E => 'Result', 0x0F => 'Properties', 0x10 => 'Total', 0x11 => 'EqualTo', 0x12 => 'Value', 0x13 => 'And', 0x14 => 'Or', 0x15 => 'FreeText', 0x17 => 'DeepTraversal', 0x18 => 'LongId', 0x19 => 'RebuildResults', 0x1A => 'LessThan', 0x1B => 'GreaterThan', 0x1C => 'Schema', 0x1D => 'Supported', // EAS 12.1 0x1E => 'UserName', 0x1F => 'Password', 0x20 => 'ConversationId', // EAS 14.1 0x21 => 'Picture', 0x22 => 'MaxSize', 0x23 => 'MaxPictures', ), /* GAL (Global Address List) */ 0x10 => array( 0x05 => 'DisplayName', 0x06 => 'Phone', 0x07 => 'Office', 0x08 => 'Title', 0x09 => 'Company', 0x0A => 'Alias', 0x0B => 'FirstName', 0x0C => 'LastName', 0x0D => 'HomePhone', 0x0E => 'MobilePhone', 0x0F => 'EmailAddress', // 14.1 0x10 => 'Picture', 0x11 => 'Status', 0x12 => 'Data', ), // EAS 12.0 /* AIRSYNCBASE */ 0x11 => array( 0x05 => 'BodyPreference', 0x06 => 'Type', 0x07 => 'TruncationSize', 0x08 => 'AllOrNone', 0x0A => 'Body', 0x0B => 'Data', 0x0C => 'EstimatedDataSize', 0x0D => 'Truncated', 0x0E => 'Attachments', 0x0F => 'Attachment', 0x10 => 'DisplayName', 0x11 => 'FileReference', 0x12 => 'Method', 0x13 => 'ContentId', 0x14 => 'ContentLocation', 0x15 => 'IsInline', 0x16 => 'NativeBodyType', 0x17 => 'ContentType', // EAS 14.0 0x18 => 'Preview', // EAS 14.1 0x19 => 'BodyPartPreference', 0x1a => 'BodyPart', 0x1b => 'Status', // EAS 16.0 0x1c => 'Add', 0x1d => 'Delete', 0x1e => 'ClientId', 0x1f => 'Content', 0x20 => 'Location', 0x21 => 'Annontation', 0x22 => 'Street', 0x23 => 'City', 0x24 => 'State', 0x25 => 'Country', 0x26 => 'PostalCode', 0x27 => 'Latitude', 0x28 => 'Longitude', 0x29 => 'Accuracy', 0x2a => 'Altitude', 0x2b => 'AltitudeAccuracy', 0x2c => 'LocationUri', 0x2d => 'InstanceId', ), /* SETTINGS */ 0x12 => array( 0x05 => 'Settings', 0x06 => 'Status', 0x07 => 'Get', 0x08 => 'Set', 0x09 => 'Oof', 0x0A => 'OofState', 0x0B => 'StartTime', 0x0C => 'EndTime', 0x0D => 'OofMessage', 0x0E => 'AppliesToInternal', 0x0F => 'AppliesToExternalKnown', 0x10 => 'AppliesToExternalUnknown', 0x11 => 'Enabled', 0x12 => 'ReplyMessage', 0x13 => 'BodyType', 0x14 => 'DevicePassword', 0x15 => 'Password', 0x16 => 'DeviceInformation', 0x17 => 'Model', 0x18 => 'IMEI', 0x19 => 'FriendlyName', 0x1A => 'OS', 0x1B => 'OSLanguage', 0x1C => 'PhoneNumber', 0x1D => 'UserInformation', 0x1E => 'EmailAddresses', 0x1F => 'SmtpAddress', // EAS 12.1 0x20 => 'UserAgent', // EAS 14.0 0x21 => 'EnableOutboundSMS', 0x22 => 'MobileOperator', // EAS 14.1 0x23 => 'PrimarySmtpAddress', 0x24 => 'Accounts', 0x25 => 'Account', 0x26 => 'AccountId', 0x27 => 'AccountName', 0x28 => 'UserDisplayName', 0x29 => 'SendDisabled', 0x2b => 'RightsManagementInformation', ), /* Document Library */ 0x13 => array( 0x05 => 'LinkId', 0x06 => 'DisplayName', 0x07 => 'IsFolder', 0x08 => 'CreationDate', 0x09 => 'LastModifiedDate', 0x0A => 'IsHidden', 0x0B => 'ContentLength', 0x0C => 'ContentType' ), /* ITEMOPERATIONS */ 0x14 => array( 0x05 => 'ItemOperations', 0x06 => 'Fetch', 0x07 => 'Store', 0x08 => 'Options', 0x09 => 'Range', 0x0A => 'Total', 0x0B => 'Properties', 0x0C => 'Data', 0x0D => 'Status', 0x0E => 'Response', 0x0F => 'Version', 0x10 => 'Schema', 0x11 => 'Part', 0x12 => 'EmptyFolderContent', 0x13 => 'DeleteSubFolders', // EAS 12.1 0x14 => 'UserName', 0x15 => 'Password', // EAS 14.0 0x16 => 'Move', 0x17 => 'DstFldId', 0x18 => 'ConversationId', 0x19 => 'MoveAlways', ), /* COMPOSEMAIL (14.0) */ 0x15 => array( 0x05 => 'SendMail', 0x06 => 'SmartForward', 0x07 => 'SmartReply', 0x08 => 'SaveInSentItems', 0x09 => 'ReplaceMime', 0x0A => 'Type', 0x0B => 'Source', 0x0C => 'FolderId', 0x0D => 'ItemId', 0x0E => 'LongId', 0x0F => 'InstanceId', 0x10 => 'MIME', 0x11 => 'ClientId', 0x12 => 'Status', // 14.1 0x13 => 'AccountId', // EAS 16.0 0x15 => 'Forwardees', 0x16 => 'Forwardee', 0x17 => 'ForwardeeName', 0x18 => 'ForwardeeEmail' ), /* POOMMAIL2 (14.0) */ 0x16 => array( 0x05 => 'UmCallerId', 0x06 => 'UmUserNotes', 0x07 => 'UmAttDuration', 0x08 => 'UmAttOrder', 0x09 => 'ConversationId', 0x0A => 'ConversationIndex', 0x0B => 'LastVerbExecuted', 0x0C => 'LastVerbExecutionTime', 0x0D => 'ReceivedAsBcc', 0x0E => 'Sender', 0x0F => 'CalendarType', 0x10 => 'IsLeapMonth', // 14.1 0x11 => 'AccountId', 0x12 => 'FirstDayOfWeek', 0x13 => 'MeetingMessageType', // EAS 16.0 0x15 => 'IsDraft', 0x16 => 'Bcc', 0x17 => 'Send' ), /* Notes (14.0) */ 0x17 => array( 0x05 => 'Subject', 0x06 => 'MessageClass', 0x07 => 'LastModifiedDate', 0x08 => 'Categories', 0x09 => 'Category', ), /* Rights Management (14.1) */ // Included here to decode without errors. // Functionality not implemented. 0x18 => array( 0x05 => 'RightsManagementSupport', 0x06 => 'RightsManagementTemplates', 0x07 => 'RightsManagementTemplate', 0x08 => 'RightsManagementLicense', 0x09 => 'EditAllowed', 0x0A => 'ReplyAllowed', 0x0B => 'ReplyAllAllowed', 0x0C => 'ForwardAllowed', 0x0D => 'ModifyRecipientsAllowed', 0x0E => 'ExtractAllowed', 0x0F => 'PrintAllowed', 0x10 => 'ExportAllowed', 0x11 => 'ProgrammaticAccessAllowed', 0x12 => 'Owner', 0x13 => 'ContentExpiryDate', 0x14 => 'TemplateID', 0x15 => 'TemplateName', 0x16 => 'TemplateDescription', 0x17 => 'ContentOwner', 0x18 => 'RemoveRightsManagementDistribution' ), // Windows Live 0xFE => array( 0x05 => 'Annotations', 0x06 => 'Annotation', 0x07 => 'Name', 0x08 => 'Value' ) ), 'namespaces' => array( 1 => 'POOMCONTACTS', 2 => 'POOMMAIL', 4 => 'POOMCAL', 5 => 'Move', 6 => 'GetItemEstimate', 7 => 'FolderHierarchy', 8 => 'MeetingResponse', 9 => 'POOMTASKS', 0xA => 'ResolveRecipients', 0xB => 'ValidateCert', 0xC => 'POOMCONTACTS2', 0xD => 'Ping', 0xE => 'Provision', 0xF => 'Search', 0x10 => 'GAL', // EAS 12.0 0x11 => 'AirSyncBase', 0x12 => 'Settings', 0x13 => 'DocumentLibrary', 0x14 => 'ItemOperations', // EAS 14 0x15 => 'ComposeMail', 0x16 => 'POOMMAIL2', 0x17 => 'Notes', 0x18 => 'RightsManagement', // Hotmail/Outlook.com WBXML extension. 0xFE => 'WindowsLive' ) ); /** * Track the codepage for the currently output tag so we know when to * switch codepages. * * @var integer */ protected $_tagcp = 0; /** * Used to hold log entries for each tag so we can only output the log * entries for the tags that are actually sent (@see $_stack). * * @var array */ protected $_logStack = array(); /** * Logger * * @var Horde_Log_Logger */ protected $_logger; /** * Input or Output stream * * @var Horde_Stream */ protected $_stream; /** * The current procid * * @var integer */ protected $_procid; /** * Logging level. * * @param integer */ protected $_logLevel; /** * * @param stream $stream The [input|output] stream. */ public function __construct($stream, $log_level = self::LOG_PROTOCOL) { $this->_stream = new Horde_Stream_Existing(array('stream' => $stream)); $this->_logger = new Horde_Log_Logger(new Horde_Log_Handler_Null()); $this->_procid = getmypid(); $this->_logLevel = $log_level; } public function getStream() { return $this->_stream; } /** * Set the logger instance * * @param Horde_Log_Logger $logger The logger. */ public function setLogger(Horde_Log_Logger $logger) { $this->_logger = $logger; } } Horde_ActiveSync-2.31.1/lib/Horde/ActiveSync.php0000664000076500000240000013374212654565405016325 0ustar * @package ActiveSync */ /** * The Horde ActiveSync server. Entry point for performing all ActiveSync * operations. * * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @copyright 2009-2016 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync * * @property-read Horde_ActiveSync_Wbxml_Encoder $encoder The Wbxml encoder. * @property-read Horde_ActiveSync_Wbxml_Decoder $decoder The Wbxml decoder. * @property-read Horde_ActiveSync_State_Base $state The state object. * @property-read Horde_Controller_Reqeust_Http $request The HTTP request object. * @property-read Horde_ActiveSync_Driver_Base $driver The backend driver object. * @property-read boolean|string $provisioning Provisioning support: True, False, or 'loose' * @property-read boolean $multipart Indicate this is a multipart request. * @property-read string $certPath Local path to the certificate bundle. * @property-read Horde_ActiveSync_Device $device The current device object. * @property-read Horde_Log_Logger $logger The logger object. */ class Horde_ActiveSync { /* Conflict resolution */ const CONFLICT_OVERWRITE_SERVER = 0; const CONFLICT_OVERWRITE_PIM = 1; /* TRUNCATION Constants */ const TRUNCATION_ALL = 0; const TRUNCATION_1 = 1; const TRUNCATION_2 = 2; const TRUNCATION_3 = 3; const TRUNCATION_4 = 4; const TRUNCATION_5 = 5; const TRUNCATION_6 = 6; const TRUNCATION_7 = 7; const TRUNCATION_8 = 8; const TRUNCATION_9 = 9; const TRUNCATION_NONE = 9; // @deprecated /* FOLDERHIERARCHY */ const FOLDERHIERARCHY_FOLDERS = 'FolderHierarchy:Folders'; const FOLDERHIERARCHY_FOLDER = 'FolderHierarchy:Folder'; const FOLDERHIERARCHY_DISPLAYNAME = 'FolderHierarchy:DisplayName'; const FOLDERHIERARCHY_SERVERENTRYID = 'FolderHierarchy:ServerEntryId'; const FOLDERHIERARCHY_PARENTID = 'FolderHierarchy:ParentId'; const FOLDERHIERARCHY_TYPE = 'FolderHierarchy:Type'; const FOLDERHIERARCHY_RESPONSE = 'FolderHierarchy:Response'; const FOLDERHIERARCHY_STATUS = 'FolderHierarchy:Status'; const FOLDERHIERARCHY_CONTENTCLASS = 'FolderHierarchy:ContentClass'; const FOLDERHIERARCHY_CHANGES = 'FolderHierarchy:Changes'; const FOLDERHIERARCHY_SYNCKEY = 'FolderHierarchy:SyncKey'; const FOLDERHIERARCHY_FOLDERSYNC = 'FolderHierarchy:FolderSync'; const FOLDERHIERARCHY_COUNT = 'FolderHierarchy:Count'; const FOLDERHIERARCHY_VERSION = 'FolderHierarchy:Version'; /* SYNC */ const SYNC_SYNCHRONIZE = 'Synchronize'; const SYNC_REPLIES = 'Replies'; const SYNC_ADD = 'Add'; const SYNC_MODIFY = 'Modify'; const SYNC_REMOVE = 'Remove'; const SYNC_FETCH = 'Fetch'; const SYNC_SYNCKEY = 'SyncKey'; const SYNC_CLIENTENTRYID = 'ClientEntryId'; const SYNC_SERVERENTRYID = 'ServerEntryId'; const SYNC_STATUS = 'Status'; const SYNC_FOLDER = 'Folder'; const SYNC_FOLDERTYPE = 'FolderType'; const SYNC_VERSION = 'Version'; const SYNC_FOLDERID = 'FolderId'; const SYNC_GETCHANGES = 'GetChanges'; const SYNC_MOREAVAILABLE = 'MoreAvailable'; const SYNC_WINDOWSIZE = 'WindowSize'; const SYNC_COMMANDS = 'Commands'; const SYNC_OPTIONS = 'Options'; const SYNC_FILTERTYPE = 'FilterType'; const SYNC_TRUNCATION = 'Truncation'; const SYNC_RTFTRUNCATION = 'RtfTruncation'; const SYNC_CONFLICT = 'Conflict'; const SYNC_FOLDERS = 'Folders'; const SYNC_DATA = 'Data'; const SYNC_DELETESASMOVES = 'DeletesAsMoves'; const SYNC_NOTIFYGUID = 'NotifyGUID'; const SYNC_SUPPORTED = 'Supported'; const SYNC_SOFTDELETE = 'SoftDelete'; const SYNC_MIMESUPPORT = 'MIMESupport'; const SYNC_MIMETRUNCATION = 'MIMETruncation'; const SYNC_NEWMESSAGE = 'NewMessage'; const SYNC_PARTIAL = 'Partial'; const SYNC_WAIT = 'Wait'; const SYNC_LIMIT = 'Limit'; // 14 const SYNC_HEARTBEATINTERVAL = 'HeartbeatInterval'; const SYNC_CONVERSATIONMODE = 'ConversationMode'; const SYNC_MAXITEMS = 'MaxItems'; /* Document library */ const SYNC_DOCUMENTLIBRARY_LINKID = 'DocumentLibrary:LinkId'; const SYNC_DOCUMENTLIBRARY_DISPLAYNAME = 'DocumentLibrary:DisplayName'; const SYNC_DOCUMENTLIBRARY_ISFOLDER = 'DocumentLibrary:IsFolder'; const SYNC_DOCUMENTLIBRARY_CREATIONDATE = 'DocumentLibrary:CreationDate'; const SYNC_DOCUMENTLIBRARY_LASTMODIFIEDDATE = 'DocumentLibrary:LastModifiedDate'; const SYNC_DOCUMENTLIBRARY_ISHIDDEN = 'DocumentLibrary:IsHidden'; const SYNC_DOCUMENTLIBRARY_CONTENTLENGTH = 'DocumentLibrary:ContentLength'; const SYNC_DOCUMENTLIBRARY_CONTENTTYPE = 'DocumentLibrary:ContentType'; /* AIRSYNCBASE */ const AIRSYNCBASE_BODYPREFERENCE = 'AirSyncBase:BodyPreference'; const AIRSYNCBASE_TYPE = 'AirSyncBase:Type'; const AIRSYNCBASE_TRUNCATIONSIZE = 'AirSyncBase:TruncationSize'; const AIRSYNCBASE_ALLORNONE = 'AirSyncBase:AllOrNone'; const AIRSYNCBASE_BODY = 'AirSyncBase:Body'; const AIRSYNCBASE_DATA = 'AirSyncBase:Data'; const AIRSYNCBASE_ESTIMATEDDATASIZE = 'AirSyncBase:EstimatedDataSize'; const AIRSYNCBASE_TRUNCATED = 'AirSyncBase:Truncated'; const AIRSYNCBASE_ATTACHMENTS = 'AirSyncBase:Attachments'; const AIRSYNCBASE_ATTACHMENT = 'AirSyncBase:Attachment'; const AIRSYNCBASE_DISPLAYNAME = 'AirSyncBase:DisplayName'; const AIRSYNCBASE_FILEREFERENCE = 'AirSyncBase:FileReference'; const AIRSYNCBASE_METHOD = 'AirSyncBase:Method'; const AIRSYNCBASE_CONTENTID = 'AirSyncBase:ContentId'; const AIRSYNCBASE_CONTENTLOCATION = 'AirSyncBase:ContentLocation'; const AIRSYNCBASE_ISINLINE = 'AirSyncBase:IsInline'; const AIRSYNCBASE_NATIVEBODYTYPE = 'AirSyncBase:NativeBodyType'; const AIRSYNCBASE_CONTENTTYPE = 'AirSyncBase:ContentType'; const AIRSYNCBASE_LOCATION = 'AirSyncBase:Location'; // 14.0 const AIRSYNCBASE_PREVIEW = 'AirSyncBase:Preview'; // 14.1 const AIRSYNCBASE_BODYPARTPREFERENCE = 'AirSyncBase:BodyPartPreference'; const AIRSYNCBASE_BODYPART = 'AirSyncBase:BodyPart'; const AIRSYNCBASE_STATUS = 'AirSyncBase:Status'; // 16.0 const AIRSYNCBASE_ADD = 'AirSyncBase:Add'; const AIRSYNCBASE_DELETE = 'AirSyncBase:Delete'; const AIRSYNCBASE_CLIENTID = 'AirSyncBase:ClientId'; const AIRSYNCBASE_CONTENT = 'AirSyncBase:Content'; const AIRSYNCBASE_ANNOTATION = 'AirSyncBase:Annotation'; const AIRSYNCBASE_STREET = 'AirSyncBase:Street'; const AIRSYNCBASE_CITY = 'AirSyncBase:City'; const AIRSYNCBASE_STATE = 'AirSyncBase:State'; const AIRSYNCBASE_COUNTRY = 'AirSyncBase:Country'; const AIRSYNCBASE_POSTALCODE = 'AirSyncBase:PostalCode'; const AIRSYNCBASE_LATITUDE = 'AirSyncBase:Latitude'; const AIRSYNCBASE_LONGITUDE = 'AirSyncBase:Longitude'; const AIRSYNCBASE_ACCURACY = 'AirSyncBase:Accuracy'; const AIRSYNCBASE_ALTITUDE = 'AirSyncBase:Altitude'; const AIRSYNCBASE_ALTITUDEACCURACY = 'AirSyncBase:AltitudeAccuracy'; const AIRSYNCBASE_LOCATIONURI = 'AirSyncBase:LocationUri'; const AIRSYNCBASE_INSTANCEID = 'AirSyncBase:InstanceId'; /* Body type prefs */ const BODYPREF_TYPE_PLAIN = 1; const BODYPREF_TYPE_HTML = 2; const BODYPREF_TYPE_RTF = 3; const BODYPREF_TYPE_MIME = 4; /* PROVISION */ const PROVISION_PROVISION = 'Provision:Provision'; const PROVISION_POLICIES = 'Provision:Policies'; const PROVISION_POLICY = 'Provision:Policy'; const PROVISION_POLICYTYPE = 'Provision:PolicyType'; const PROVISION_POLICYKEY = 'Provision:PolicyKey'; const PROVISION_DATA = 'Provision:Data'; const PROVISION_STATUS = 'Provision:Status'; const PROVISION_REMOTEWIPE = 'Provision:RemoteWipe'; const PROVISION_EASPROVISIONDOC = 'Provision:EASProvisionDoc'; /* Policy types */ const POLICYTYPE_XML = 'MS-WAP-Provisioning-XML'; const POLICYTYPE_WBXML = 'MS-EAS-Provisioning-WBXML'; /* Flags */ // @TODO: H6 Change this to CHANGE_TYPE_NEW const FLAG_NEWMESSAGE = 'NewMessage'; /* Folder types */ const FOLDER_TYPE_OTHER = 1; const FOLDER_TYPE_INBOX = 2; const FOLDER_TYPE_DRAFTS = 3; const FOLDER_TYPE_WASTEBASKET = 4; const FOLDER_TYPE_SENTMAIL = 5; const FOLDER_TYPE_OUTBOX = 6; const FOLDER_TYPE_TASK = 7; const FOLDER_TYPE_APPOINTMENT = 8; const FOLDER_TYPE_CONTACT = 9; const FOLDER_TYPE_NOTE = 10; const FOLDER_TYPE_JOURNAL = 11; const FOLDER_TYPE_USER_MAIL = 12; const FOLDER_TYPE_USER_APPOINTMENT = 13; const FOLDER_TYPE_USER_CONTACT = 14; const FOLDER_TYPE_USER_TASK = 15; const FOLDER_TYPE_USER_JOURNAL = 16; const FOLDER_TYPE_USER_NOTE = 17; const FOLDER_TYPE_UNKNOWN = 18; const FOLDER_TYPE_RECIPIENT_CACHE = 19; // @TODO, remove const definition in H6, not used anymore. const FOLDER_TYPE_DUMMY = 999999; /* Origin of changes **/ const CHANGE_ORIGIN_PIM = 0; const CHANGE_ORIGIN_SERVER = 1; const CHANGE_ORIGIN_NA = 3; /* Remote wipe **/ const RWSTATUS_NA = 0; const RWSTATUS_OK = 1; const RWSTATUS_PENDING = 2; const RWSTATUS_WIPED = 3; /* GAL **/ const GAL_DISPLAYNAME = 'GAL:DisplayName'; const GAL_PHONE = 'GAL:Phone'; const GAL_OFFICE = 'GAL:Office'; const GAL_TITLE = 'GAL:Title'; const GAL_COMPANY = 'GAL:Company'; const GAL_ALIAS = 'GAL:Alias'; const GAL_FIRSTNAME = 'GAL:FirstName'; const GAL_LASTNAME = 'GAL:LastName'; const GAL_HOMEPHONE = 'GAL:HomePhone'; const GAL_MOBILEPHONE = 'GAL:MobilePhone'; const GAL_EMAILADDRESS = 'GAL:EmailAddress'; // 14.1 const GAL_PICTURE = 'GAL:Picture'; const GAL_STATUS = 'GAL:Status'; const GAL_DATA = 'GAL:Data'; /* Request Type */ const REQUEST_TYPE_SYNC = 'sync'; const REQUEST_TYPE_FOLDERSYNC = 'foldersync'; /* Change Type */ const CHANGE_TYPE_CHANGE = 'change'; const CHANGE_TYPE_DELETE = 'delete'; const CHANGE_TYPE_FLAGS = 'flags'; const CHANGE_TYPE_MOVE = 'move'; const CHANGE_TYPE_FOLDERSYNC = 'foldersync'; const CHANGE_TYPE_SOFTDELETE = 'softdelete'; /* Internal flags to indicate change is a change in reply/forward state */ const CHANGE_REPLY_STATE = '@--reply--@'; const CHANGE_REPLYALL_STATE = '@--replyall--@'; const CHANGE_FORWARD_STATE = '@--forward--@'; /* RM */ const RM_SUPPORT = 'RightsManagement:RightsManagementSupport'; const RM_TEMPLATEID = 'RightsManagement:TemplateId'; /* Collection Classes */ const CLASS_EMAIL = 'Email'; const CLASS_CONTACTS = 'Contacts'; const CLASS_CALENDAR = 'Calendar'; const CLASS_TASKS = 'Tasks'; const CLASS_NOTES = 'Notes'; const CLASS_SMS = 'SMS'; /* Filtertype constants */ const FILTERTYPE_ALL = 0; const FILTERTYPE_1DAY = 1; const FILTERTYPE_3DAYS = 2; const FILTERTYPE_1WEEK = 3; const FILTERTYPE_2WEEKS = 4; const FILTERTYPE_1MONTH = 5; const FILTERTYPE_3MONTHS = 6; const FILTERTYPE_6MONTHS = 7; const FILTERTYPE_INCOMPLETETASKS = 8; // @todo normalize to string values. const PROVISIONING_FORCE = true; const PROVISIONING_LOOSE = 'loose'; const PROVISIONING_NONE = false; const FOLDER_ROOT = 0; const VERSION_TWOFIVE = '2.5'; const VERSION_TWELVE = '12.0'; const VERSION_TWELVEONE = '12.1'; const VERSION_FOURTEEN = '14.0'; const VERSION_FOURTEENONE = '14.1'; const VERSION_SIXTEEN = '16.0'; const MIME_SUPPORT_NONE = 0; const MIME_SUPPORT_SMIME = 1; const MIME_SUPPORT_ALL = 2; const IMAP_FLAG_REPLY = 'reply'; const IMAP_FLAG_FORWARD = 'forward'; /* Result Type */ const RESOLVE_RESULT_GAL = 1; const RESOLVE_RESULT_ADDRESSBOOK = 2; /* Auth failure reasons */ const AUTH_REASON_USER_DENIED = 'user'; const AUTH_REASON_DEVICE_DENIED = 'device'; /* Internal flag indicates all possible fields are ghosted */ const ALL_GHOSTED = 'allghosted'; const LIBRARY_VERSION = '2.31.1'; /** * Logger * * @var Horde_ActiveSync_Interface_LoggerFactory */ protected $_loggerFactory; /** * The logger for this class. * * @var Horde_Log_Logger */ protected static $_logger; /** * Provisioning support * * @var string */ protected $_provisioning; /** * Highest version to support. * * @var float */ protected $_maxVersion = self::VERSION_SIXTEEN; /** * The actual version we are supporting. * * @var float */ protected static $_version; /** * Multipart support? * * @var boolean */ protected $_multipart = false; /** * Support gzip compression of certain data parts? * * @var boolean */ protected $_compression = false; /** * Local cache of Get variables/decoded base64 uri * * @var array */ protected $_get = array(); /** * Path to root certificate bundle * * @var string */ protected $_certPath; /** * * @var Horde_ActiveSync_Device */ protected static $_device; /** * Wbxml encoder * * @var Horde_ActiveSync_Wbxml_Encoder */ protected $_encoder; /** * Wbxml decoder * * @var Horde_ActiveSync_Wbxml_Decoder */ protected $_decoder; /** * The singleton collections handler. * * @var Horde_ActiveSync_Collections */ protected $_collectionsObj; /** * Global error flag. * * @var boolean */ protected $_globalError = false; /** * Process id (used in logging). * * @var integer */ protected $_procid; /** * Flag to indicate we need to update the device version. * * @var boolean */ protected $_needMsRp = false; /** * Supported EAS versions. * * @var array */ protected static $_supportedVersions = array( self::VERSION_TWOFIVE, self::VERSION_TWELVE, self::VERSION_TWELVEONE, self::VERSION_FOURTEEN, self::VERSION_FOURTEENONE, self::VERSION_SIXTEEN ); /** * Factory method for creating Horde_ActiveSync_Message objects. * * @param string $message The message type. * @since 2.4.0 * * @return Horde_ActiveSync_Message_Base The concrete message object. * @todo For H6, move to Horde_ActiveSync_Message_Base::factory() */ public static function messageFactory($message) { $class = 'Horde_ActiveSync_Message_' . $message; if (!class_exists($class)) { throw new InvalidArgumentException(sprintf('Class %s does not exist.', $class)); } return new $class(array( 'logger' => self::$_logger, 'protocolversion' => self::$_version, 'device' => self::$_device)); } /** * Const'r * * @param Horde_ActiveSync_Driver_Base $driver The backend driver. * @param Horde_ActiveSync_Wbxml_Decoder $decoder The Wbxml decoder. * @param Horde_ActiveSync_Wbxml_Endcoder $encoder The Wbxml encoder. * @param Horde_ActiveSync_State_Base $state The state driver. * @param Horde_Controller_Request_Http $request The HTTP request object. * * @return Horde_ActiveSync The ActiveSync server object. */ public function __construct( Horde_ActiveSync_Driver_Base $driver, Horde_ActiveSync_Wbxml_Decoder $decoder, Horde_ActiveSync_Wbxml_Encoder $encoder, Horde_ActiveSync_State_Base $state, Horde_Controller_Request_Http $request) { // The http request $this->_request = $request; // Backend driver $this->_driver = $driver; $this->_driver->setProtocolVersion($this->getProtocolVersion()); // Device state manager $this->_state = $state; // Wbxml handlers $this->_encoder = $encoder; $this->_decoder = $decoder; $this->_procid = getmypid(); } /** * Return a collections singleton. * * @return Horde_ActiveSync_Collections * @since 2.4.0 */ public function getCollectionsObject() { if (empty($this->_collectionsObj)) { $this->_collectionsObj = new Horde_ActiveSync_Collections($this->getSyncCache(), $this); } return $this->_collectionsObj; } /** * Return a new, fully configured SyncCache. * * @return Horde_ActiveSync_SyncCache * @since 2.4.0 */ public function getSyncCache() { return new Horde_ActiveSync_SyncCache( $this->_state, self::$_device->id, self::$_device->user, self::$_logger ); } /** * Return an Importer object. * * @return Horde_ActiveSync_Connector_Importer * @since 2.4.0 */ public function getImporter() { $importer = new Horde_ActiveSync_Connector_Importer($this); $importer->setLogger(self::$_logger); return $importer; } /** * Authenticate to the backend. * * @param Horde_ActiveSync_Credentials $credentials The credentials object. * * @return boolean True on successful authentication to the backend. * @throws Horde_ActiveSync_Exception */ public function authenticate(Horde_ActiveSync_Credentials $credentials) { if (!$credentials->username) { // No provided username or Authorization header. self::$_logger->notice(sprintf( '[%s] Client did not provide authentication data.', $this->_procid) ); return false; } $user = $this->_driver->getUsernameFromEmail($credentials->username); $pos = strrpos($user, '\\'); if ($pos !== false) { $domain = substr($user, 0, $pos); $user = substr($user, $pos + 1); } else { $domain = null; } // Authenticate if ($result = $this->_driver->authenticate($user, $credentials->password, $domain)) { if ($result === self::AUTH_REASON_USER_DENIED) { $this->_globalError = Horde_ActiveSync_Status::SYNC_NOT_ALLOWED; } elseif ($result === self::AUTH_REASON_DEVICE_DENIED) { $this->_globalError = Horde_ActiveSync_Status::DEVICE_BLOCKED_FOR_USER; } elseif ($result !== true) { $this->_globalError = Horde_ActiveSync_Status::DENIED; } } else { return false; } if (!$this->_driver->setup($user)) { return false; } return true; } /** * Allow to force the highest version to support. * * @param float $version The highest version */ public function setSupportedVersion($version) { $this->_maxVersion = $version; } /** * Set the local path to the root certificate bundle. * * @param string $path The local path to the bundle. */ public function setRootCertificatePath($path) { $this->_certPath = $path; } /** * Getter * * @param string $property The property to return. * * @return mixed The value of the requested property. */ public function __get($property) { switch ($property) { case 'encoder': case 'decoder': case 'state': case 'request': case 'driver': case 'provisioning': case 'multipart': case 'certPath': $property = '_' . $property; return $this->$property; case 'logger': return self::$_logger; case 'device': return self::$_device; default: throw new InvalidArgumentException(sprintf( 'The property %s does not exist', $property) ); } } /** * Setter for the logger * * @param Horde_Log_Logger $logger The logger object. * * @return void */ public function setLogger(Horde_ActiveSync_Interface_LoggerFactory $logger) { $this->_loggerFactory = $logger; } protected function _setLogger(array $options) { if (!empty($this->_loggerFactory)) { self::$_logger = $this->_loggerFactory->create($options); $this->_encoder->setLogger(self::$_logger); $this->_decoder->setLogger(self::$_logger); $this->_driver->setLogger(self::$_logger); $this->_state->setLogger(self::$_logger); } } /** * Setter for provisioning support * */ public function setProvisioning($provision) { $this->_provisioning = $provision; } /** * Send the headers indicating that provisioning is required. */ public function provisioningRequired() { $this->provisionHeader(); $this->activeSyncHeader(); $this->versionHeader(); $this->commandsHeader(); header('Cache-Control: private'); } /** * The heart of the server. Dispatch a request to the appropriate request * handler. * * @param string $cmd The command we are requesting. * @param string $devId The device id making the request. @deprecated * * @return string|boolean false if failed, true if succeeded and response * content is wbxml, otherwise the * content-type string to send in the response. * @throws Horde_ActiveSync_Exception * @throws Horde_ActiveSync_Exception_InvalidRequest * @throws Horde_ActiveSync_PermissionDenied */ public function handleRequest($cmd, $devId) { $get = $this->getGetVars(); if (empty($cmd)) { $cmd = $get['Cmd']; } if (empty($devId)) { $devId = !empty($get['DeviceId']) ? Horde_String::upper($get['DeviceId']) : null; } else { $devId = Horde_String::upper($devId); } $this->_setLogger($get); // @TODO: Remove is_callable check for H6. // Callback to give the backend the option to limit EAS version based // on user/device/etc... if (is_callable(array($this->_driver, 'versionCallback'))) { $this->_driver->versionCallback($this); } // Autodiscovery handles authentication on it's own. if ($cmd == 'Autodiscover') { $request = new Horde_ActiveSync_Request_Autodiscover($this, new Horde_ActiveSync_Device($this->_state)); if (!empty(self::$_logger)) { $request->setLogger(self::$_logger); } $result = $request->handle($this->_request); $this->_driver->clearAuthentication(); return $result; } if (!$this->authenticate(new Horde_ActiveSync_Credentials($this))) { $this->activeSyncHeader(); $this->versionHeader(); $this->commandsHeader(); throw new Horde_Exception_AuthenticationFailure(); } self::$_logger->info(sprintf( '[%s] %s request received for user %s', $this->_procid, Horde_String::upper($cmd), $this->_driver->getUser()) ); // These are all handled in the same class. if ($cmd == 'FolderDelete' || $cmd == 'FolderUpdate') { $cmd = 'FolderCreate'; } // Device id is REQUIRED if (empty($devId)) { if ($cmd == 'Options') { $this->_doOptionsRequest(); $this->_driver->clearAuthentication(); return true; } $this->_driver->clearAuthentication(); throw new Horde_ActiveSync_Exception_InvalidRequest('Device failed to send device id.'); } // EAS Version $version = $this->getProtocolVersion(); // Device. Even though versions of EAS > 12.1 are supposed to send // EAS status codes back to indicate various errors in allowing a client // to connect, we just throw an exception (thus causing a HTTP error // code to be sent as in versions 12.1 and below). Until we refactor for // Horde 6, we don't know the response type to wrap the status code in // until we load the request handler, which requires we start to parse // the WBXML stream and device information etc... This saves resources // as well as keeps things cleaner until we refactor. $device_result = $this->_handleDevice($devId); // Don't bother with everything else if all we want are Options if ($cmd == 'Options') { $this->_doOptionsRequest(); $this->_driver->clearAuthentication(); return true; } // Set provisioning support now that we are authenticated. $this->setProvisioning($this->_driver->getProvisioning(self::$_device)); // Read the initial Wbxml header $this->_decoder->readWbxmlHeader(); // Support Multipart response for ITEMOPERATIONS requests? $headers = $this->_request->getHeaders(); if ((!empty($headers['ms-asacceptmultipart']) && $headers['ms-asacceptmultipart'] == 'T') || !empty($get['AcceptMultiPart'])) { $this->_multipart = true; self::$_logger->info(sprintf( '[%s] Requesting multipart data.', $this->_procid) ); } // Load the request handler to handle the request // We must send the EAS header here, since some requests may start // output and be large enough to flush the buffer (e.g., GetAttachment) // See Bug: 12486 $this->activeSyncHeader(); if ($cmd != 'GetAttachment') { $this->contentTypeHeader(); } // Should we announce a new version is available to the client? if (!empty($this->_needMsRp)) { self::$_logger->info(sprintf( '[%s] Announcing X-MS-RP to client.', $this->_procid) ); header("X-MS-RP: ". $this->getSupportedVersions()); } // @TODO: Look at getting rid of having to set the version in the driver // and get it from the device object for H6. $this->_driver->setDevice(self::$_device); $class = 'Horde_ActiveSync_Request_' . basename($cmd); if (class_exists($class)) { $request = new $class($this); $request->setLogger(self::$_logger); $result = $request->handle(); self::$_logger->info(sprintf( '[%s] Maximum memory usage for ActiveSync request: %d bytes.', $this->_procid, memory_get_peak_usage(true)) ); return $result; } $this->_driver->clearAuthentication(); throw new Horde_ActiveSync_Exception_InvalidRequest(basename($cmd) . ' not supported.'); } /** * Handle device checks. Takes into account permissions and restrictions * via various callback methods. * * @param string $devId The client provided device id. * * @return boolean If EAS version is > 12.1 returns false on any type of * failure in allowing the device to connect. Sets * appropriate internal variables to indicate the type of * error to return to the client. Failure on EAS version * < 12.1 results in throwing exceptions. Otherwise, return * true. * @throws Horde_ActiveSync_Exception, Horde_Exception_AuthenticationFailure */ protected function _handleDevice($devId) { $get = $this->getGetVars(); $version = $this->getProtocolVersion(); // Does device exist AND does the user have an account on the device? if (!$this->_state->deviceExists($devId, $this->_driver->getUser())) { // Device might exist, but with a new (additional) user account if ($this->_state->deviceExists($devId)) { self::$_device = $this->_state->loadDeviceInfo($devId); } else { self::$_device = new Horde_ActiveSync_Device($this->_state); } self::$_device->policykey = 0; self::$_device->userAgent = $this->_request->getHeader('User-Agent'); self::$_device->deviceType = !empty($get['DeviceType']) ? $get['DeviceType'] : ''; self::$_device->rwstatus = self::RWSTATUS_NA; self::$_device->user = $this->_driver->getUser(); self::$_device->id = $devId; self::$_device->needsVersionUpdate($this->getSupportedVersions()); self::$_device->version = $version; // @TODO: Remove is_callable check for H6. // Combine this with the modifyDevice callback? Allow $device // to be modified here? if (is_callable(array($this->_driver, 'createDeviceCallback'))) { $callback_ret = $this->_driver->createDeviceCallback(self::$_device); if ($callback_ret !== true) { $msg = sprintf( 'The device %s was disallowed for user %s per policy settings.', self::$_device->id, self::$_device->user); self::$_logger->err($msg); // Always throw exception in place of status code since we // won't have a version number before the device is created. throw new Horde_Exception_AuthenticationFailure($msg, $callback_ret); } else { // Give the driver a chance to modify device properties. if (is_callable(array($this->_driver, 'modifyDeviceCallback'))) { self::$_device = $this->_driver->modifyDeviceCallback(self::$_device); } } } } else { self::$_device = $this->_state->loadDeviceInfo($devId, $this->_driver->getUser()); // If the device state was removed from storage, we may lose the // device properties, so try to repopulate what we can. userAgent // is ALWAYS available, so if it's missing, the state is gone. if (empty(self::$_device->userAgent)) { self::$_device->userAgent = $this->_request->getHeader('User-Agent'); self::$_device->deviceType = !empty($get['DeviceType']) ? $get['DeviceType'] : ''; self::$_device->user = $this->_driver->getUser(); } if (empty(self::$_device->version)) { self::$_device->version = $version; } if (self::$_device->version < $this->_maxVersion && self::$_device->needsVersionUpdate($this->getSupportedVersions())) { $this->_needMsRp = true; } // Give the driver a chance to modify device properties. if (is_callable(array($this->_driver, 'modifyDeviceCallback'))) { self::$_device = $this->_driver->modifyDeviceCallback(self::$_device); } } // Save the device now that we know it is at least allowed to connect, // or it has connected successfully at least once in the past. self::$_device->save(); if (is_callable(array($this->_driver, 'deviceCallback'))) { $callback_ret = $this->_driver->deviceCallback(self::$_device); if ($callback_ret !== true) { $msg = sprintf( 'The device %s was disallowed for user %s per policy settings.', self::$_device->id, self::$_device->user); self::$_logger->err($msg); if ($version > self::VERSION_TWELVEONE) { // Use a status code here, since the device has already // connected. $this->_globalError = $callback_ret; return false; } else { throw new Horde_Exception_AuthenticationFailure($msg, $callback_ret); } } } // Lastly, check if the device has been set to blocked. if (self::$_device->blocked) { $msg = sprintf( 'The device %s was blocked.', self::$_device->id); self::$_logger->err($msg); if ($version > self::VERSION_TWELVEONE) { $this->_globalError = Horde_ActiveSync_Status::DEVICE_BLOCKED_FOR_USER; return false; } else { throw new Horde_ActiveSync_Exception($msg); } } return true; } /** * Send the MS_Server-ActiveSync header. */ public function activeSyncHeader() { header('Allow: OPTIONS,POST'); header('Server: Horde_ActiveSync Library v' . self::LIBRARY_VERSION); header('Public: OPTIONS,POST'); switch ($this->_maxVersion) { case self::VERSION_TWOFIVE: header('MS-Server-ActiveSync: 6.5.7638.1'); break; case self::VERSION_TWELVE: header('MS-Server-ActiveSync: 12.0'); break; case self::VERSION_TWELVEONE: header('MS-Server-ActiveSync: 12.1'); break; case self::VERSION_FOURTEEN: header('MS-Server-ActiveSync: 14.0'); break; case self::VERSION_FOURTEENONE: header('MS-Server-ActiveSync: 14.1'); break; case self::VERSION_SIXTEEN: header('MS-Server-ActiveSync: 16.0'); } } /** * Send the protocol versions header. */ public function versionHeader() { header('MS-ASProtocolVersions: ' . $this->getSupportedVersions()); } /** * Return supported versions in a comma delimited string suitable for * sending as the MS-ASProtocolVersions header. * * @return string */ public function getSupportedVersions() { return implode(',', array_slice(self::$_supportedVersions, 0, (array_search($this->_maxVersion, self::$_supportedVersions) + 1))); } /** * Send protocol commands header. */ public function commandsHeader() { header('MS-ASProtocolCommands: ' . $this->getSupportedCommands()); } /** * Return the supported commands in a comma delimited string suitable for * sending as the MS-ASProtocolCommands header. * * @return string */ public function getSupportedCommands() { switch ($this->_maxVersion) { case self::VERSION_TWOFIVE: return 'Sync,SendMail,SmartForward,SmartReply,GetAttachment,GetHierarchy,CreateCollection,DeleteCollection,MoveCollection,FolderSync,FolderCreate,FolderDelete,FolderUpdate,MoveItems,GetItemEstimate,MeetingResponse,ResolveRecipients,ValidateCert,Provision,Search,Ping'; case self::VERSION_TWELVE: case self::VERSION_TWELVEONE: case self::VERSION_FOURTEEN: case self::VERSION_FOURTEENONE: case self::VERSION_SIXTEEN: return 'Sync,SendMail,SmartForward,SmartReply,GetAttachment,GetHierarchy,CreateCollection,DeleteCollection,MoveCollection,FolderSync,FolderCreate,FolderDelete,FolderUpdate,MoveItems,GetItemEstimate,MeetingResponse,Search,Settings,Ping,ItemOperations,Provision,ResolveRecipients,ValidateCert'; } } /** * Send provision header */ public function provisionHeader() { header('HTTP/1.1 449 Retry after sending a PROVISION command'); } /** * Obtain the policy key header from the request. * * @return integer The policy key or '0' if not set. */ public function getPolicyKey() { // Policy key can come from header or encoded request parameters. $this->_policykey = $this->_request->getHeader('X-MS-PolicyKey'); if (empty($this->_policykey)) { $get = $this->getGetVars(); if (!empty($get['PolicyKey'])) { $this->_policykey = $get['PolicyKey']; } else { $this->_policykey = 0; } } return $this->_policykey; } /** * Obtain the ActiveSync protocol version requested by the client headers. * * @return string The EAS version requested by the client. */ public function getProtocolVersion() { if (!isset(self::$_version)) { self::$_version = $this->_request->getHeader('MS-ASProtocolVersion'); if (empty(self::$_version)) { $get = $this->getGetVars(); self::$_version = empty($get['ProtVer']) ? '1.0' : $get['ProtVer']; } } return self::$_version; } /** * Return the GET variables passed from the device, decoding from * base64 if needed. * * @return array A hash of get variables => values. */ public function getGetVars() { if (!empty($this->_get)) { return $this->_get; } $results = array(); $get = $this->_request->getGetVars(); // Do we need to decode the request parameters? if (!isset($get['Cmd']) && !isset($get['DeviceId']) && !isset($get['DeviceType'])) { $serverVars = $this->_request->getServerVars(); if (isset($serverVars['QUERY_STRING']) && strlen($serverVars['QUERY_STRING']) >= 10) { $results = Horde_ActiveSync_Utils::decodeBase64($serverVars['QUERY_STRING']); // Normalize values. switch ($results['DeviceType']) { case 'PPC': $results['DeviceType'] = 'PocketPC'; break; case 'SP': $results['DeviceType'] = 'SmartPhone'; break; case 'WP': case 'WP8': $results['DeviceType'] = 'WindowsPhone'; break; case 'android': case 'android40': $results['DeviceType'] = 'android'; } $this->_get = $results; } } else { $this->_get = $get; } return $this->_get; } /** * Return any global errors that occured during initial connection. * * @since 2.4.0 * @return mixed A Horde_ActiveSync_Status:: constant of boolean false if * no errors. */ public function checkGlobalError() { return $this->_globalError; } /** * Send the content type header. * */ public function contentTypeHeader($content_type = null) { if (!empty($content_type)) { header('Content-Type: ' . $content_type); return; } if ($this->_multipart) { header('Content-Type: application/vnd.ms-sync.multipart'); } else { header('Content-Type: application/vnd.ms-sync.wbxml'); } } /** * Send the OPTIONS request response headers. */ protected function _doOptionsRequest() { $this->activeSyncHeader(); $this->versionHeader(); $this->commandsHeader(); } /** * Return the number of bytes corresponding to the requested trunction * constant. This applies to MIMETRUNCATION only. * * @param integer $truncation The constant. * * @return integer|boolean Either the size, in bytes, to truncate or * falso if no truncation. * @since 2.20.0 */ public static function getMIMETruncSize($truncation) { switch($truncation) { case Horde_ActiveSync::TRUNCATION_ALL: return 0; case Horde_ActiveSync::TRUNCATION_1: return 4096; case Horde_ActiveSync::TRUNCATION_2: return 5120; case Horde_ActiveSync::TRUNCATION_3: return 7168; case Horde_ActiveSync::TRUNCATION_4: return 10240; case Horde_ActiveSync::TRUNCATION_5: return 20480; case Horde_ActiveSync::TRUNCATION_6: return 51200; case Horde_ActiveSync::TRUNCATION_7: return 102400; case Horde_ActiveSync::TRUNCATION_8: return false; default: return 1024; // Default to 1Kb } } /** * Return the number of bytes corresponding to the requested trunction * constant. * * @param integer $truncation The constant. * * @return integer|boolean Either the size, in bytes, to truncate or * falso if no truncation. * */ public function getTruncSize($truncation) { switch($truncation) { case Horde_ActiveSync::TRUNCATION_ALL: return 0; case Horde_ActiveSync::TRUNCATION_1: return 512; case Horde_ActiveSync::TRUNCATION_2: return 1024; case Horde_ActiveSync::TRUNCATION_3: return 2048; case Horde_ActiveSync::TRUNCATION_4: return 5120; case Horde_ActiveSync::TRUNCATION_5: return 10240; case Horde_ActiveSync::TRUNCATION_6: return 20480; case Horde_ActiveSync::TRUNCATION_7: return 51200; case Horde_ActiveSync::TRUNCATION_8: return 102400; case Horde_ActiveSync::TRUNCATION_9: case Horde_ActiveSync::TRUNCATION_NONE: // @deprecated return false; default: return 1024; // Default to 1Kb } } } Horde_ActiveSync-2.31.1/locale/da/LC_MESSAGES/Horde_ActiveSync.mo0000664000076500000240000000262012654565405021061 0ustar Þ•ä%¬@A IT Wc ht”™ œ §´ºÉ Ì Ø åðù    Œ$±¸ÀÃÓ Øä   " (69 BN]f ku y †   %s partAudio partCcCommon NameDateEAS VersionForced Multiplexed BitmaskFromIMEIIdImage partMessage partModelMultipart partOSOS LanguagePhone NumberPolicy KeyReply-ToSubjectText partToUser AgentVideo partProject-Id-Version: Horde_ActiveSync Report-Msgid-Bugs-To: dev@lists.horde.org POT-Creation-Date: 2014-03-18 15:58+0100 PO-Revision-Date: 2014-03-19 19:41+0100 Last-Translator: Erling Preben Hansen Language-Team: i18n@lists.horde.org Language: da MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n != 1); %s delLyd delCcAlmindelge navnDatoEAS VersionTvungen Multiplexed BitmaskFraIMEIIdBilled delBesked delModelMultipart delOSOS SprogTelefon nr.Politik nøgleSvar tilEmneTekst delTilBruger agentVideo delHorde_ActiveSync-2.31.1/locale/da/LC_MESSAGES/Horde_ActiveSync.po0000664000076500000240000000506412654565405021071 0ustar # Danish translations for Horde_ActiveSync package. # Copyright (C) 2014 Horde LLC (http://www.horde.org/) # This file is distributed under the same license as the Horde_ActiveSync package. # Erling Preben Hansen , 2013-2014. # msgid "" msgstr "" "Project-Id-Version: Horde_ActiveSync\n" "Report-Msgid-Bugs-To: dev@lists.horde.org\n" "POT-Creation-Date: 2014-03-18 15:58+0100\n" "PO-Revision-Date: 2014-03-19 19:41+0100\n" "Last-Translator: Erling Preben Hansen \n" "Language-Team: i18n@lists.horde.org\n" "Language: da\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: lib/Horde/ActiveSync/Imap/Message.php:614 #, php-format msgid "%s part" msgstr "%s del" #: lib/Horde/ActiveSync/Imap/Message.php:593 msgid "Audio part" msgstr "Lyd del" #: lib/Horde/ActiveSync/Imap/Message.php:139 msgid "Cc" msgstr "Cc" #: lib/Horde/ActiveSync/Device.php:295 msgid "Common Name" msgstr "Almindelge navn" #: lib/Horde/ActiveSync/Imap/Message.php:119 msgid "Date" msgstr "Dato" #: lib/Horde/ActiveSync/Device.php:307 msgid "EAS Version" msgstr "EAS Version" #: lib/Horde/ActiveSync/Device.php:310 msgid "Forced Multiplexed Bitmask" msgstr "Tvungen Multiplexed Bitmask" #: lib/Horde/ActiveSync/Imap/Message.php:123 msgid "From" msgstr "Fra" #: lib/Horde/ActiveSync/Device.php:292 msgid "IMEI" msgstr "IMEI" #: lib/Horde/ActiveSync/Device.php:283 msgid "Id" msgstr "Id" #: lib/Horde/ActiveSync/Imap/Message.php:596 msgid "Image part" msgstr "Billed del" #: lib/Horde/ActiveSync/Imap/Message.php:600 msgid "Message part" msgstr "Besked del" #: lib/Horde/ActiveSync/Device.php:289 msgid "Model" msgstr "Model" #: lib/Horde/ActiveSync/Imap/Message.php:603 msgid "Multipart part" msgstr "Multipart del" #: lib/Horde/ActiveSync/Device.php:298 msgid "OS" msgstr "OS" #: lib/Horde/ActiveSync/Device.php:301 msgid "OS Language" msgstr "OS Sprog" #: lib/Horde/ActiveSync/Device.php:304 msgid "Phone Number" msgstr "Telefon nr." #: lib/Horde/ActiveSync/Device.php:284 msgid "Policy Key" msgstr "Politik nøgle" #: lib/Horde/ActiveSync/Imap/Message.php:127 msgid "Reply-To" msgstr "Svar til" #: lib/Horde/ActiveSync/Imap/Message.php:131 msgid "Subject" msgstr "Emne" #: lib/Horde/ActiveSync/Imap/Message.php:606 msgid "Text part" msgstr "Tekst del" #: lib/Horde/ActiveSync/Imap/Message.php:135 msgid "To" msgstr "Til" #: lib/Horde/ActiveSync/Device.php:285 msgid "User Agent" msgstr "Bruger agent" #: lib/Horde/ActiveSync/Imap/Message.php:609 msgid "Video part" msgstr "Video del" Horde_ActiveSync-2.31.1/locale/de/LC_MESSAGES/Horde_ActiveSync.mo0000664000076500000240000000271212654565405021067 0ustar Þ•ä%¬@A IT Wc hty~  Œ™©¯¾ Á Í Úåî ö  €š­ÃÆ× Ýéíòõ ,3M P [i €‹“¨«´     %s partAudio partCcCommon NameDateEAS VersionFromIMEIIdImage partMessage partMobile OperatorModelMultipart partOSOS LanguagePhone NumberPolicy KeyReply-ToSubjectText partToUser AgentVideo partProject-Id-Version: Horde_ActiveSync Report-Msgid-Bugs-To: dev@lists.horde.org POT-Creation-Date: 2015-12-28 17:25+0100 PO-Revision-Date: 2015-01-08 11:27+0100 Last-Translator: Automatically generated Language-Team: i18n@lists.horde.org Language: de MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n != 1); %s-NachrichtenteilAudio-NachrichtenteilCcEindeutiger NameDatumEAS-VersionVonIMEIIdBild-NachrichtenteilNachrichtenteilMobilfunkanbieterModellMultipart-NachrichtenteilOSOS-SpracheTelefonnummerRichtlinien-SchlüsselAntwort anBetreffText-NachrichtenteilAnProgrammVideo-NachrichtenteilHorde_ActiveSync-2.31.1/locale/de/LC_MESSAGES/Horde_ActiveSync.po0000664000076500000240000000527512654565405021101 0ustar # German translations for Horde_ActiveSync package. # Copyright 2012-2016 Horde LLC (http://www.horde.org/) # This file is distributed under the same license as the Horde_ActiveSync package. # Jan Schneider , 2012-2015. # msgid "" msgstr "" "Project-Id-Version: Horde_ActiveSync \n" "Report-Msgid-Bugs-To: dev@lists.horde.org\n" "POT-Creation-Date: 2015-12-28 17:25+0100\n" "PO-Revision-Date: 2015-01-08 11:27+0100\n" "Last-Translator: Automatically generated\n" "Language-Team: i18n@lists.horde.org\n" "Language: de\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: lib/Horde/ActiveSync/Imap/Message.php:516 #, php-format msgid "%s part" msgstr "%s-Nachrichtenteil" #: lib/Horde/ActiveSync/Imap/Message.php:495 msgid "Audio part" msgstr "Audio-Nachrichtenteil" #: lib/Horde/ActiveSync/Imap/Message.php:167 msgid "Cc" msgstr "Cc" #: lib/Horde/ActiveSync/Device.php:330 msgid "Common Name" msgstr "Eindeutiger Name" #: lib/Horde/ActiveSync/Imap/Message.php:147 msgid "Date" msgstr "Datum" #: lib/Horde/ActiveSync/Device.php:345 msgid "EAS Version" msgstr "EAS-Version" #: lib/Horde/ActiveSync/Device.php:348 msgid "Forced Multiplexed Bitmask" msgstr "" #: lib/Horde/ActiveSync/Imap/Message.php:151 msgid "From" msgstr "Von" #: lib/Horde/ActiveSync/Device.php:327 msgid "IMEI" msgstr "IMEI" #: lib/Horde/ActiveSync/Device.php:318 msgid "Id" msgstr "Id" #: lib/Horde/ActiveSync/Imap/Message.php:498 msgid "Image part" msgstr "Bild-Nachrichtenteil" #: lib/Horde/ActiveSync/Imap/Message.php:502 msgid "Message part" msgstr "Nachrichtenteil" #: lib/Horde/ActiveSync/Device.php:333 msgid "Mobile Operator" msgstr "Mobilfunkanbieter" #: lib/Horde/ActiveSync/Device.php:324 msgid "Model" msgstr "Modell" #: lib/Horde/ActiveSync/Imap/Message.php:505 msgid "Multipart part" msgstr "Multipart-Nachrichtenteil" #: lib/Horde/ActiveSync/Device.php:336 msgid "OS" msgstr "OS" #: lib/Horde/ActiveSync/Device.php:339 msgid "OS Language" msgstr "OS-Sprache" #: lib/Horde/ActiveSync/Device.php:342 msgid "Phone Number" msgstr "Telefonnummer" #: lib/Horde/ActiveSync/Device.php:319 msgid "Policy Key" msgstr "Richtlinien-Schlüssel" #: lib/Horde/ActiveSync/Imap/Message.php:155 msgid "Reply-To" msgstr "Antwort an" #: lib/Horde/ActiveSync/Imap/Message.php:159 msgid "Subject" msgstr "Betreff" #: lib/Horde/ActiveSync/Imap/Message.php:508 msgid "Text part" msgstr "Text-Nachrichtenteil" #: lib/Horde/ActiveSync/Imap/Message.php:163 msgid "To" msgstr "An" #: lib/Horde/ActiveSync/Device.php:320 msgid "User Agent" msgstr "Programm" #: lib/Horde/ActiveSync/Imap/Message.php:511 msgid "Video part" msgstr "Video-Nachrichtenteil" Horde_ActiveSync-2.31.1/locale/es/LC_MESSAGES/Horde_ActiveSync.mo0000664000076500000240000000270312654565405021106 0ustar Þ•ä%¬@A IT Wc ht”™ œ §´ºÉ Ì Ø åðù    ­$ÒÛâ åó ù$+.36=E LW Zh} Œ˜Ÿ¥ª¼   %s partAudio partCcCommon NameDateEAS VersionForced Multiplexed BitmaskFromIMEIIdImage partMessage partModelMultipart partOSOS LanguagePhone NumberPolicy KeyReply-ToSubjectText partToUser AgentVideo partProject-Id-Version: Horde_ActiveSync Report-Msgid-Bugs-To: dev@lists.horde.org POT-Creation-Date: 2014-03-25 18:08+0100 PO-Revision-Date: 2014-06-16 09:05+0100 Last-Translator: Manuel P. Ayala , Juan C. Blanco Language-Team: i18n@lists.horde.org Language: es MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n != 1); parte %sSonidoCcNombre comúnFechaVersión EASMáscara de bit multiplexada forzadaDeIMEIIdImagenMensajeModeloMultiparteSOIdioma del SONúmero de teléfonoComportamientoResponder-AAsuntoTextoParaAgente de usuarioVídeoHorde_ActiveSync-2.31.1/locale/es/LC_MESSAGES/Horde_ActiveSync.po0000664000076500000240000000513112654565405021107 0ustar # Spanish translations for Horde_ActiveSync package. # Copyright (C) 2014 Horde LLC (http://www.horde.org/) # This file is distributed under the same license as the Horde_ActiveSync package. # Automatically generated, 2014. # msgid "" msgstr "" "Project-Id-Version: Horde_ActiveSync\n" "Report-Msgid-Bugs-To: dev@lists.horde.org\n" "POT-Creation-Date: 2014-03-25 18:08+0100\n" "PO-Revision-Date: 2014-06-16 09:05+0100\n" "Last-Translator: Manuel P. Ayala , Juan C. Blanco " "\n" "Language-Team: i18n@lists.horde.org\n" "Language: es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: lib/Horde/ActiveSync/Imap/Message.php:614 #, php-format msgid "%s part" msgstr "parte %s" #: lib/Horde/ActiveSync/Imap/Message.php:593 msgid "Audio part" msgstr "Sonido" #: lib/Horde/ActiveSync/Imap/Message.php:139 msgid "Cc" msgstr "Cc" #: lib/Horde/ActiveSync/Device.php:295 msgid "Common Name" msgstr "Nombre común" #: lib/Horde/ActiveSync/Imap/Message.php:119 msgid "Date" msgstr "Fecha" #: lib/Horde/ActiveSync/Device.php:307 msgid "EAS Version" msgstr "Versión EAS" #: lib/Horde/ActiveSync/Device.php:310 msgid "Forced Multiplexed Bitmask" msgstr "Máscara de bit multiplexada forzada" #: lib/Horde/ActiveSync/Imap/Message.php:123 msgid "From" msgstr "De" #: lib/Horde/ActiveSync/Device.php:292 msgid "IMEI" msgstr "IMEI" #: lib/Horde/ActiveSync/Device.php:283 msgid "Id" msgstr "Id" #: lib/Horde/ActiveSync/Imap/Message.php:596 msgid "Image part" msgstr "Imagen" #: lib/Horde/ActiveSync/Imap/Message.php:600 msgid "Message part" msgstr "Mensaje" #: lib/Horde/ActiveSync/Device.php:289 msgid "Model" msgstr "Modelo" #: lib/Horde/ActiveSync/Imap/Message.php:603 msgid "Multipart part" msgstr "Multiparte" #: lib/Horde/ActiveSync/Device.php:298 msgid "OS" msgstr "SO" #: lib/Horde/ActiveSync/Device.php:301 msgid "OS Language" msgstr "Idioma del SO" #: lib/Horde/ActiveSync/Device.php:304 msgid "Phone Number" msgstr "Número de teléfono" #: lib/Horde/ActiveSync/Device.php:284 msgid "Policy Key" msgstr "Comportamiento" #: lib/Horde/ActiveSync/Imap/Message.php:127 msgid "Reply-To" msgstr "Responder-A" #: lib/Horde/ActiveSync/Imap/Message.php:131 msgid "Subject" msgstr "Asunto" #: lib/Horde/ActiveSync/Imap/Message.php:606 msgid "Text part" msgstr "Texto" #: lib/Horde/ActiveSync/Imap/Message.php:135 msgid "To" msgstr "Para" #: lib/Horde/ActiveSync/Device.php:285 msgid "User Agent" msgstr "Agente de usuario" #: lib/Horde/ActiveSync/Imap/Message.php:609 msgid "Video part" msgstr "Vídeo" Horde_ActiveSync-2.31.1/locale/eu/LC_MESSAGES/Horde_ActiveSync.mo0000664000076500000240000000172512654565405021113 0ustar Þ•ŒüHI Q\_d i t™ ¡« ®¢¹\ eqty ~ Š• ¥³ ¸Ä É   %s partAudio partCcDateFromImage partMessage partMultipart partReply-ToSubjectText partToVideo partProject-Id-Version: Horde_ActiveSync Report-Msgid-Bugs-To: dev@lists.horde.org POT-Creation-Date: 2012-10-12 19:05+0200 PO-Revision-Date: 2013-01-17 14:31+0100 Last-Translator: Ibon Igartua Language-Team: i18n@lists.horde.org Language: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n != 1); X-Poedit-Language: Basque %s zatiaAudio-zatiaCcDataNorkIrudi-zatiaMezu-zatiaMultipart zatiaErantzun honiGaiaTestu-zatiaNoriBideo-zatiaHorde_ActiveSync-2.31.1/locale/eu/LC_MESSAGES/Horde_ActiveSync.po0000664000076500000240000000335012654565405021112 0ustar # Basque translations for bcwhord package. # Copyright 2012-2016 Horde LLC (http://www.horde.org/) # This file is distributed under the same license as the Horde_ActiveSync package. # Automatically generated, 2012. # msgid "" msgstr "" "Project-Id-Version: Horde_ActiveSync\n" "Report-Msgid-Bugs-To: dev@lists.horde.org\n" "POT-Creation-Date: 2012-10-12 19:05+0200\n" "PO-Revision-Date: 2013-01-17 14:31+0100\n" "Last-Translator: Ibon Igartua \n" "Language-Team: i18n@lists.horde.org\n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Poedit-Language: Basque\n" #: lib/Horde/ActiveSync/Imap/Message.php:476 #, php-format msgid "%s part" msgstr "%s zatia" #: lib/Horde/ActiveSync/Imap/Message.php:455 msgid "Audio part" msgstr "Audio-zatia" #: lib/Horde/ActiveSync/Imap/Message.php:135 msgid "Cc" msgstr "Cc" #: lib/Horde/ActiveSync/Imap/Message.php:115 msgid "Date" msgstr "Data" #: lib/Horde/ActiveSync/Imap/Message.php:119 msgid "From" msgstr "Nork" #: lib/Horde/ActiveSync/Imap/Message.php:458 msgid "Image part" msgstr "Irudi-zatia" #: lib/Horde/ActiveSync/Imap/Message.php:462 msgid "Message part" msgstr "Mezu-zatia" #: lib/Horde/ActiveSync/Imap/Message.php:465 msgid "Multipart part" msgstr "Multipart zatia" #: lib/Horde/ActiveSync/Imap/Message.php:123 msgid "Reply-To" msgstr "Erantzun honi" #: lib/Horde/ActiveSync/Imap/Message.php:127 msgid "Subject" msgstr "Gaia" #: lib/Horde/ActiveSync/Imap/Message.php:468 msgid "Text part" msgstr "Testu-zatia" #: lib/Horde/ActiveSync/Imap/Message.php:131 msgid "To" msgstr "Nori" #: lib/Horde/ActiveSync/Imap/Message.php:471 msgid "Video part" msgstr "Bideo-zatia" Horde_ActiveSync-2.31.1/locale/fr/LC_MESSAGES/Horde_ActiveSync.mo0000664000076500000240000000200212654565405021076 0ustar Þ•ŒüHI Q\_d i t™ ¡« ®Æ¹ € Š—šŸ ¢¯Á ÑÞ äñ ô   %s partAudio partCcDateFromImage partMessage partMultipart partReply-ToSubjectText partToVideo partProject-Id-Version: Horde_ActiveSync Report-Msgid-Bugs-To: dev@lists.horde.org POT-Creation-Date: 2013-01-14 09:19+0100 PO-Revision-Date: 2013-01-14 10:13+0100 Last-Translator: Paul De Vlieger Language-Team: French Language: fr MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n > 1); X-Generator: Lokalize 1.4 %s partiePartie AudioCcDateDePartie imagePartie du messagePartie MultipleRépondre àObjetPartie TexteÀPartie vidéoHorde_ActiveSync-2.31.1/locale/fr/LC_MESSAGES/Horde_ActiveSync.po0000664000076500000240000000350312654565405021110 0ustar # French translations for Horde_ActiveSync package. # Copyright (C) 2013 Horde LLC (http://www.horde.org/) # This file is distributed under the same license as the Horde_ActiveSync package. # # Paul De Vlieger , 2013 msgid "" msgstr "" "Project-Id-Version: Horde_ActiveSync \n" "Report-Msgid-Bugs-To: dev@lists.horde.org\n" "POT-Creation-Date: 2013-01-14 09:19+0100\n" "PO-Revision-Date: 2013-01-14 10:13+0100\n" "Last-Translator: Paul De Vlieger \n" "Language-Team: French \n" "Language: fr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" "X-Generator: Lokalize 1.4\n" #: lib/Horde/ActiveSync/Imap/Message.php:532 #, php-format msgid "%s part" msgstr "%s partie" #: lib/Horde/ActiveSync/Imap/Message.php:511 msgid "Audio part" msgstr "Partie Audio" #: lib/Horde/ActiveSync/Imap/Message.php:137 msgid "Cc" msgstr "Cc" #: lib/Horde/ActiveSync/Imap/Message.php:117 msgid "Date" msgstr "Date" #: lib/Horde/ActiveSync/Imap/Message.php:121 msgid "From" msgstr "De" #: lib/Horde/ActiveSync/Imap/Message.php:514 msgid "Image part" msgstr "Partie image" #: lib/Horde/ActiveSync/Imap/Message.php:518 msgid "Message part" msgstr "Partie du message" #: lib/Horde/ActiveSync/Imap/Message.php:521 msgid "Multipart part" msgstr "Partie Multiple" #: lib/Horde/ActiveSync/Imap/Message.php:125 msgid "Reply-To" msgstr "Répondre à" #: lib/Horde/ActiveSync/Imap/Message.php:129 msgid "Subject" msgstr "Objet" #: lib/Horde/ActiveSync/Imap/Message.php:524 msgid "Text part" msgstr "Partie Texte" #: lib/Horde/ActiveSync/Imap/Message.php:133 msgid "To" msgstr "À" #: lib/Horde/ActiveSync/Imap/Message.php:527 msgid "Video part" msgstr "Partie vidéo" Horde_ActiveSync-2.31.1/locale/hu/LC_MESSAGES/Horde_ActiveSync.mo0000664000076500000240000000271012654565405021111 0ustar Þ•ä%¬@A IT Wc ht”™ œ §´ºÉ Ì Ø åðù    `$…–ª³Ä Ë×òú ÿ ,2LO X es‚‰  ©´   %s partAudio partCcCommon NameDateEAS VersionForced Multiplexed BitmaskFromIMEIIdImage partMessage partModelMultipart partOSOS LanguagePhone NumberPolicy KeyReply-ToSubjectText partToUser AgentVideo partProject-Id-Version: Horde_ActiveSync Report-Msgid-Bugs-To: dev@lists.horde.org POT-Creation-Date: 2014-07-14 11:26+0200 PO-Revision-Date: 2014-07-14 11:35+0200 Last-Translator: Andras Galos Language-Team: i18n@lists.horde.org Language: hu MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Üzenetrész: %sAudió üzenetrészMásolatÃltalános névDátumEAS verzióForced Multiplexed BitmaskFeladóIMEIAzonosítóKép üzenetrészÜzenetszövegModelTöbbrészes üzenetrészOSOS nyelvTelefonszámSzabálykulcsViszontválaszTárgySzöveges üzenetrészCímzettUser AgentVideó üzenetrészHorde_ActiveSync-2.31.1/locale/hu/LC_MESSAGES/Horde_ActiveSync.po0000664000076500000240000000506712654565405021124 0ustar # Hungarian translations for Horde_ActiveSync module. # Copyright (C) YEAR Horde LLC (http://www.horde.org/) # This file is distributed under the same license as the Horde_ActiveSync package. # msgid "" msgstr "" "Project-Id-Version: Horde_ActiveSync \n" "Report-Msgid-Bugs-To: dev@lists.horde.org\n" "POT-Creation-Date: 2014-07-14 11:26+0200\n" "PO-Revision-Date: 2014-07-14 11:35+0200\n" "Last-Translator: Andras Galos \n" "Language-Team: i18n@lists.horde.org\n" "Language: hu\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: lib/Horde/ActiveSync/Imap/Message.php:618 #, php-format msgid "%s part" msgstr "Üzenetrész: %s" #: lib/Horde/ActiveSync/Imap/Message.php:597 msgid "Audio part" msgstr "Audió üzenetrész" #: lib/Horde/ActiveSync/Imap/Message.php:139 msgid "Cc" msgstr "Másolat" #: lib/Horde/ActiveSync/Device.php:295 msgid "Common Name" msgstr "Ãltalános név" #: lib/Horde/ActiveSync/Imap/Message.php:119 msgid "Date" msgstr "Dátum" #: lib/Horde/ActiveSync/Device.php:307 msgid "EAS Version" msgstr "EAS verzió" #: lib/Horde/ActiveSync/Device.php:310 msgid "Forced Multiplexed Bitmask" msgstr "Forced Multiplexed Bitmask" #: lib/Horde/ActiveSync/Imap/Message.php:123 msgid "From" msgstr "Feladó" #: lib/Horde/ActiveSync/Device.php:292 msgid "IMEI" msgstr "IMEI" #: lib/Horde/ActiveSync/Device.php:283 msgid "Id" msgstr "Azonosító" #: lib/Horde/ActiveSync/Imap/Message.php:600 msgid "Image part" msgstr "Kép üzenetrész" #: lib/Horde/ActiveSync/Imap/Message.php:604 msgid "Message part" msgstr "Üzenetszöveg" #: lib/Horde/ActiveSync/Device.php:289 msgid "Model" msgstr "Model" #: lib/Horde/ActiveSync/Imap/Message.php:607 msgid "Multipart part" msgstr "Többrészes üzenetrész" #: lib/Horde/ActiveSync/Device.php:298 msgid "OS" msgstr "OS" #: lib/Horde/ActiveSync/Device.php:301 msgid "OS Language" msgstr "OS nyelv" #: lib/Horde/ActiveSync/Device.php:304 msgid "Phone Number" msgstr "Telefonszám" #: lib/Horde/ActiveSync/Device.php:284 msgid "Policy Key" msgstr "Szabálykulcs" #: lib/Horde/ActiveSync/Imap/Message.php:127 msgid "Reply-To" msgstr "Viszontválasz" #: lib/Horde/ActiveSync/Imap/Message.php:131 msgid "Subject" msgstr "Tárgy" #: lib/Horde/ActiveSync/Imap/Message.php:610 msgid "Text part" msgstr "Szöveges üzenetrész" #: lib/Horde/ActiveSync/Imap/Message.php:135 msgid "To" msgstr "Címzett" #: lib/Horde/ActiveSync/Device.php:285 msgid "User Agent" msgstr "User Agent" #: lib/Horde/ActiveSync/Imap/Message.php:613 msgid "Video part" msgstr "Videó üzenetrész" Horde_ActiveSync-2.31.1/locale/ja/LC_MESSAGES/Horde_ActiveSync.mo0000664000076500000240000000301612654565405021067 0ustar Þ•ä%¬@A IT Wc ht”™ œ §´ºÉ Ì Ø åðù    £$ È Õâ åïö' 16;>Ngn ™¦¶¿ÆÜßû   %s partAudio partCcCommon NameDateEAS VersionForced Multiplexed BitmaskFromIMEIIdImage partMessage partModelMultipart partOSOS LanguagePhone NumberPolicy KeyReply-ToSubjectText partToUser AgentVideo partProject-Id-Version: Horde_ActiveSync Report-Msgid-Bugs-To: dev@lists.horde.org POT-Creation-Date: 2014-03-25 18:08+0100 PO-Revision-Date: 2014-05-10 10:59+0900 Last-Translator: Hiromi Kimura Language-Team: i18n@lists.horde.org Language: ja MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=1; plural=0; X-Generator: Poedit 1.6.4 %s パート音パートCcä¸€èˆ¬åæ—¥ä»˜EASãƒãƒ¼ã‚¸ãƒ§ãƒ³ãƒ“ットマスクã®å¤šé‡åŒ–を強制FromIMEIIdç”»åƒãƒ‘ートメッセージパート型番マルãƒãƒ‘ート・パートOSOS言語電話番å·ãƒãƒªã‚·ãƒ¼éµReply-Toä»¶åテキストパートToユーザエージェントビデオパートHorde_ActiveSync-2.31.1/locale/ja/LC_MESSAGES/Horde_ActiveSync.po0000664000076500000240000000524112654565405021074 0ustar # Japanese translation for Horde. # Copyright (C) 2012-2013 Horde LLC (http://www.horde.org/) # This file is distributed under the same license as the Horde_ActiveSync package. # Hiromi Kimura # msgid "" msgstr "" "Project-Id-Version: Horde_ActiveSync\n" "Report-Msgid-Bugs-To: dev@lists.horde.org\n" "POT-Creation-Date: 2014-03-25 18:08+0100\n" "PO-Revision-Date: 2014-05-10 10:59+0900\n" "Last-Translator: Hiromi Kimura \n" "Language-Team: i18n@lists.horde.org\n" "Language: ja\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: Poedit 1.6.4\n" #: lib/Horde/ActiveSync/Imap/Message.php:614 #, php-format msgid "%s part" msgstr "%s パート" #: lib/Horde/ActiveSync/Imap/Message.php:593 msgid "Audio part" msgstr "音パート" #: lib/Horde/ActiveSync/Imap/Message.php:139 msgid "Cc" msgstr "Cc" #: lib/Horde/ActiveSync/Device.php:295 msgid "Common Name" msgstr "一般å" #: lib/Horde/ActiveSync/Imap/Message.php:119 msgid "Date" msgstr "日付" #: lib/Horde/ActiveSync/Device.php:307 msgid "EAS Version" msgstr "EASãƒãƒ¼ã‚¸ãƒ§ãƒ³" #: lib/Horde/ActiveSync/Device.php:310 msgid "Forced Multiplexed Bitmask" msgstr "ビットマスクã®å¤šé‡åŒ–を強制" #: lib/Horde/ActiveSync/Imap/Message.php:123 msgid "From" msgstr "From" #: lib/Horde/ActiveSync/Device.php:292 msgid "IMEI" msgstr "IMEI" #: lib/Horde/ActiveSync/Device.php:283 msgid "Id" msgstr "Id" #: lib/Horde/ActiveSync/Imap/Message.php:596 msgid "Image part" msgstr "ç”»åƒãƒ‘ート" #: lib/Horde/ActiveSync/Imap/Message.php:600 msgid "Message part" msgstr "メッセージパート" #: lib/Horde/ActiveSync/Device.php:289 msgid "Model" msgstr "型番" #: lib/Horde/ActiveSync/Imap/Message.php:603 msgid "Multipart part" msgstr "マルãƒãƒ‘ート・パート" #: lib/Horde/ActiveSync/Device.php:298 msgid "OS" msgstr "OS" #: lib/Horde/ActiveSync/Device.php:301 msgid "OS Language" msgstr "OS言語" #: lib/Horde/ActiveSync/Device.php:304 msgid "Phone Number" msgstr "電話番å·" #: lib/Horde/ActiveSync/Device.php:284 msgid "Policy Key" msgstr "ãƒãƒªã‚·ãƒ¼éµ" #: lib/Horde/ActiveSync/Imap/Message.php:127 msgid "Reply-To" msgstr "Reply-To" #: lib/Horde/ActiveSync/Imap/Message.php:131 msgid "Subject" msgstr "ä»¶å" #: lib/Horde/ActiveSync/Imap/Message.php:606 msgid "Text part" msgstr "テキストパート" #: lib/Horde/ActiveSync/Imap/Message.php:135 msgid "To" msgstr "To" #: lib/Horde/ActiveSync/Device.php:285 msgid "User Agent" msgstr "ユーザエージェント" #: lib/Horde/ActiveSync/Imap/Message.php:609 msgid "Video part" msgstr "ビデオパート" Horde_ActiveSync-2.31.1/locale/nl/LC_MESSAGES/Horde_ActiveSync.mo0000664000076500000240000000264712654565405021117 0ustar Þ•Ü%œ01 9D GS Xdin q |‰ž ¡ ­ ºÅÎ Öà ã î¾ù¸ÀÑ Ôâ èó÷ü ÿ !03;JY hr‰ ˜    %s partAudio partCcCommon NameDateEAS VersionFromIMEIIdImage partMessage partModelMultipart partOSOS LanguagePhone NumberPolicy KeyReply-ToSubjectText partToUser AgentVideo partProject-Id-Version: Horde_ActiveSync Report-Msgid-Bugs-To: dev@lists.horde.org POT-Creation-Date: 2013-06-04 11:48+0200 PO-Revision-Date: 2013-06-07 14:31+0200 Last-Translator: Arjen de Korte Language-Team: American English Language: nl MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n != 1); X-Generator: Lokalize 1.5 %s deelGeluids fragmentCcAlgemene naamDatumEAS versieVanIMEIIdAfbeeldingBerichtonderdeelModelMultipart deelOSOS taalTelefoonnummerBeleidssleutelAntwoorden aanOnderwerpPlatte Tekst onderdeelAanUser AgentVideo fragmentHorde_ActiveSync-2.31.1/locale/nl/LC_MESSAGES/Horde_ActiveSync.po0000664000076500000240000000511512654565405021113 0ustar # Dutch translations for Horde_ActiveSync package. # Copyright 2012-2016 Horde LLC (http://www.horde.org/) # This file is distributed under the same license as the Horde_ActiveSync package. # # Automatically generated, 2012. # Arjen de Korte , 2012, 2013. msgid "" msgstr "" "Project-Id-Version: Horde_ActiveSync\n" "Report-Msgid-Bugs-To: dev@lists.horde.org\n" "POT-Creation-Date: 2013-06-04 11:48+0200\n" "PO-Revision-Date: 2013-06-07 14:31+0200\n" "Last-Translator: Arjen de Korte \n" "Language-Team: American English \n" "Language: nl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Lokalize 1.5\n" #: lib/Horde/ActiveSync/Imap/Message.php:520 #, php-format msgid "%s part" msgstr "%s deel" #: lib/Horde/ActiveSync/Imap/Message.php:499 msgid "Audio part" msgstr "Geluids fragment" #: lib/Horde/ActiveSync/Imap/Message.php:139 msgid "Cc" msgstr "Cc" #: lib/Horde/ActiveSync/Device.php:171 msgid "Common Name" msgstr "Algemene naam" #: lib/Horde/ActiveSync/Imap/Message.php:119 msgid "Date" msgstr "Datum" #: lib/Horde/ActiveSync/Device.php:183 msgid "EAS Version" msgstr "EAS versie" #: lib/Horde/ActiveSync/Imap/Message.php:123 msgid "From" msgstr "Van" #: lib/Horde/ActiveSync/Device.php:168 msgid "IMEI" msgstr "IMEI" #: lib/Horde/ActiveSync/Device.php:159 msgid "Id" msgstr "Id" #: lib/Horde/ActiveSync/Imap/Message.php:502 msgid "Image part" msgstr "Afbeelding" #: lib/Horde/ActiveSync/Imap/Message.php:506 msgid "Message part" msgstr "Berichtonderdeel" #: lib/Horde/ActiveSync/Device.php:165 msgid "Model" msgstr "Model" #: lib/Horde/ActiveSync/Imap/Message.php:509 msgid "Multipart part" msgstr "Multipart deel" #: lib/Horde/ActiveSync/Device.php:174 msgid "OS" msgstr "OS" #: lib/Horde/ActiveSync/Device.php:177 msgid "OS Language" msgstr "OS taal" #: lib/Horde/ActiveSync/Device.php:180 msgid "Phone Number" msgstr "Telefoonnummer" #: lib/Horde/ActiveSync/Device.php:160 msgid "Policy Key" msgstr "Beleidssleutel" #: lib/Horde/ActiveSync/Imap/Message.php:127 msgid "Reply-To" msgstr "Antwoorden aan" #: lib/Horde/ActiveSync/Imap/Message.php:131 msgid "Subject" msgstr "Onderwerp" #: lib/Horde/ActiveSync/Imap/Message.php:512 msgid "Text part" msgstr "Platte Tekst onderdeel" #: lib/Horde/ActiveSync/Imap/Message.php:135 msgid "To" msgstr "Aan" #: lib/Horde/ActiveSync/Device.php:161 msgid "User Agent" msgstr "User Agent" #: lib/Horde/ActiveSync/Imap/Message.php:515 msgid "Video part" msgstr "Video fragment" Horde_ActiveSync-2.31.1/locale/Horde_ActiveSync.pot0000664000076500000240000000453712654565405017110 0ustar # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR Horde LLC (http://www.horde.org/) # This file is distributed under the same license as the Horde_ActiveSync package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: Horde_ActiveSync\n" "Report-Msgid-Bugs-To: dev@lists.horde.org\n" "POT-Creation-Date: 2015-12-28 17:25+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=CHARSET\n" "Content-Transfer-Encoding: 8bit\n" #: lib/Horde/ActiveSync/Imap/Message.php:516 #, php-format msgid "%s part" msgstr "" #: lib/Horde/ActiveSync/Imap/Message.php:495 msgid "Audio part" msgstr "" #: lib/Horde/ActiveSync/Imap/Message.php:167 msgid "Cc" msgstr "" #: lib/Horde/ActiveSync/Device.php:330 msgid "Common Name" msgstr "" #: lib/Horde/ActiveSync/Imap/Message.php:147 msgid "Date" msgstr "" #: lib/Horde/ActiveSync/Device.php:345 msgid "EAS Version" msgstr "" #: lib/Horde/ActiveSync/Device.php:348 msgid "Forced Multiplexed Bitmask" msgstr "" #: lib/Horde/ActiveSync/Imap/Message.php:151 msgid "From" msgstr "" #: lib/Horde/ActiveSync/Device.php:327 msgid "IMEI" msgstr "" #: lib/Horde/ActiveSync/Device.php:318 msgid "Id" msgstr "" #: lib/Horde/ActiveSync/Imap/Message.php:498 msgid "Image part" msgstr "" #: lib/Horde/ActiveSync/Imap/Message.php:502 msgid "Message part" msgstr "" #: lib/Horde/ActiveSync/Device.php:333 msgid "Mobile Operator" msgstr "" #: lib/Horde/ActiveSync/Device.php:324 msgid "Model" msgstr "" #: lib/Horde/ActiveSync/Imap/Message.php:505 msgid "Multipart part" msgstr "" #: lib/Horde/ActiveSync/Device.php:336 msgid "OS" msgstr "" #: lib/Horde/ActiveSync/Device.php:339 msgid "OS Language" msgstr "" #: lib/Horde/ActiveSync/Device.php:342 msgid "Phone Number" msgstr "" #: lib/Horde/ActiveSync/Device.php:319 msgid "Policy Key" msgstr "" #: lib/Horde/ActiveSync/Imap/Message.php:155 msgid "Reply-To" msgstr "" #: lib/Horde/ActiveSync/Imap/Message.php:159 msgid "Subject" msgstr "" #: lib/Horde/ActiveSync/Imap/Message.php:508 msgid "Text part" msgstr "" #: lib/Horde/ActiveSync/Imap/Message.php:163 msgid "To" msgstr "" #: lib/Horde/ActiveSync/Device.php:320 msgid "User Agent" msgstr "" #: lib/Horde/ActiveSync/Imap/Message.php:511 msgid "Video part" msgstr "" Horde_ActiveSync-2.31.1/migration/Horde/ActiveSync/1_horde_activesync_base_tables.php0000664000076500000240000000641312654565405025637 0ustar tables())) { $t = $this->createTable('horde_activesync_state', array('autoincrementKey' => array('sync_key'))); $t->column('sync_time', 'integer'); $t->column('sync_key', 'string', array('limit' => 255, 'null' => false)); $t->column('sync_data', 'text'); $t->column('sync_devid', 'string', array('limit' => 255)); $t->column('sync_folderid', 'string', array('limit' => 255)); $t->column('sync_user', 'string', array('limit' => 255)); $t->end(); $this->addIndex('horde_activesync_state', array('sync_folderid')); $this->addIndex('horde_activesync_state', array('sync_devid')); } if (!in_array('horde_activesync_map', $this->tables())) { $t = $this->createTable('horde_activesync_map', array('autoincrementKey' => false)); $t->column('message_uid', 'string', array('limit' => 255, 'null' => false)); $t->column('sync_modtime', 'integer'); $t->column('sync_key', 'string', array('limit' => 255, 'null' => false)); $t->column('sync_devid', 'string', array('limit' => 255, 'null' => false)); $t->column('sync_folderid', 'string', array('limit' => 255, 'null' => false)); $t->column('sync_user', 'string', array('limit' => 255)); $t->end(); $this->addIndex('horde_activesync_map', array('sync_devid')); $this->addIndex('horde_activesync_map', array('message_uid')); $this->addIndex('horde_activesync_map', array('sync_user')); } if (!in_array('horde_activesync_device', $this->tables())) { $t = $this->createTable('horde_activesync_device', array('autoincrementKey' => array('device_id'))); $t->column('device_id', 'string', array('limit' => 255, 'null' => false)); $t->column('device_type', 'string', array('limit' => 255, 'null' => false)); $t->column('device_agent', 'string', array('limit' => 255, 'null' => false)); $t->column('device_supported', 'text'); $t->column('device_policykey', 'bigint', array('default' => 0)); $t->column('device_rwstatus', 'integer'); $t->end(); } if (!in_array('horde_activesync_device_users', $this->tables())) { $t = $this->createTable('horde_activesync_device_users', array('autoincrementKey' => false)); $t->column('device_id', 'string', array('limit' => 255, 'null' => false)); $t->column('device_user', 'string', array('limit' => 255, 'null' => false)); $t->column('device_ping', 'text'); $t->column('device_folders', 'text'); $t->end(); $this->addIndex('horde_activesync_device_users', array('device_user')); $this->addIndex('horde_activesync_device_users', array('device_id')); } } public function down() { $this->dropTable('horde_activesync_device_users'); $this->dropTable('horde_activesync_device'); $this->dropTable('horde_activesync_map'); $this->dropTable('horde_activesync_state'); } } Horde_ActiveSync-2.31.1/migration/Horde/ActiveSync/2_horde_activesync_peruserpolicykey.php0000664000076500000240000000117012654565405027005 0ustar addColumn( 'horde_activesync_device_users', 'device_policykey', 'bigint', array('default' => 0)); $this->removeColumn('horde_activesync_device', 'device_policykey'); } public function down() { $this->addColumn( 'horde_activesync_device', 'device_policykey', 'bigint', array('default' => 0)); $this->removeColumn('horde_activesync_device_users', 'device_policykey'); } }Horde_ActiveSync-2.31.1/migration/Horde/ActiveSync/3_horde_activesync_clientidmap.php0000664000076500000240000000056312654565405025666 0ustar addColumn( 'horde_activesync_map', 'sync_clientid', 'string', array('limit' => 255)); } public function down() { $this->removeColumn('horde_activesync_map', 'sync_clientid'); } }Horde_ActiveSync-2.31.1/migration/Horde/ActiveSync/4_horde_activesync_longtextstatefield.php0000664000076500000240000000060712654565405027306 0ustar changeColumn( 'horde_activesync_state', 'sync_data', 'mediumtext'); } public function down() { $this->changeColumn( 'horde_activesync_state', 'sync_data', 'text'); } }Horde_ActiveSync-2.31.1/migration/Horde/ActiveSync/5_horde_activesync_addpendingfield.php0000664000076500000240000000053212654565405026474 0ustar addColumn( 'horde_activesync_state', 'sync_pending', 'mediumtext'); } public function down() { $this->removeColumn('horde_activesync_state', 'sync_pending'); } }Horde_ActiveSync-2.31.1/migration/Horde/ActiveSync/6_horde_activesync_addmailmap.php0000664000076500000240000000202312654565405025462 0ustar createTable('horde_activesync_mailmap', array('autoincrementKey' => false)); $t->column('message_uid', 'string', array('limit' => 255, 'null' => false)); $t->column('sync_key', 'string', array('limit' => 255, 'null' => false)); $t->column('sync_devid', 'string', array('limit' => 255, 'null' => false)); $t->column('sync_folderid', 'string', array('limit' => 255, 'null' => false)); $t->column('sync_user', 'string', array('limit' => 255)); $t->column('sync_read', 'integer'); $t->column('sync_deleted', 'integer'); $t->end(); $this->addIndex('horde_activesync_mailmap', array('message_uid')); $this->addIndex('horde_activesync_mailmap', array('sync_devid')); $this->addIndex('horde_activesync_mailmap', array('sync_folderid')); } public function down() { $this->dropTable('horde_activesync_mailmap'); } }Horde_ActiveSync-2.31.1/migration/Horde/ActiveSync/7_horde_activesync_clearstate.php0000664000076500000240000000073712654565405025533 0ustar delete('DELETE from horde_activesync_state'); $this->delete('DELETE from horde_activesync_map'); } public function down() { $this->delete('DELETE from horde_activesync_state'); $this->delete('DELETE from horde_activesync_map'); } }Horde_ActiveSync-2.31.1/migration/Horde/ActiveSync/8_horde_activesync_addmailflagged.php0000664000076500000240000000053212654565405026303 0ustar addColumn( 'horde_activesync_mailmap', 'sync_flagged', 'integer'); } public function down() { $this->removeColumn('horde_activesync_mailmap', 'sync_flagged'); } }Horde_ActiveSync-2.31.1/migration/Horde/ActiveSync/9_horde_activesync_add_cache.php0000664000076500000240000000117712654565405025260 0ustar createTable('horde_activesync_cache', array('autoincrementKey' => false)); $t->column('cache_devid', 'string', array('limit' => 255)); $t->column('cache_user', 'string', array('limit' => 255)); $t->column('cache_data', 'text'); $t->end(); $this->addIndex('horde_activesync_cache', array('cache_devid')); $this->addIndex('horde_activesync_cache', array('cache_user')); } public function down() { $this->dropTable('horde_activesync_cache'); } } Horde_ActiveSync-2.31.1/migration/Horde/ActiveSync/10_horde_activesync_add_deviceproperties.php0000664000076500000240000000054512654565405027637 0ustar addColumn( 'horde_activesync_device', 'device_properties', 'text'); } public function down() { $this->removeColumn('horde_activesync_device', 'device_properties'); } } Horde_ActiveSync-2.31.1/migration/Horde/ActiveSync/11_horde_activesync_removepingstate.php0000664000076500000240000000105312654565405026663 0ustar removeColumn('horde_activesync_device_users', 'device_ping'); $this->removeColumn('horde_activesync_device_users', 'device_folders'); } public function down() { $this->addColumn( 'horde_activesync_device_users', 'device_ping', 'text'); $this->addColumn( 'horde_activesync_device_users', 'device_folders', 'text'); } } Horde_ActiveSync-2.31.1/migration/Horde/ActiveSync/12_horde_activesync_longtextcachefield.php0000664000076500000240000000061112654565405027303 0ustar changeColumn( 'horde_activesync_cache', 'cache_data', 'mediumtext'); } public function down() { $this->changeColumn( 'horde_activesync_cache', 'cache_data', 'text'); } }Horde_ActiveSync-2.31.1/migration/Horde/ActiveSync/13_horde_activesync_booleanfields.php0000664000076500000240000000164012654565405026261 0ustar changeColumn( 'horde_activesync_mailmap', 'sync_deleted', 'boolean' ); $this->changeColumn( 'horde_activesync_mailmap', 'sync_flagged', 'boolean' ); $this->changeColumn( 'horde_activesync_mailmap', 'sync_read', 'boolean' ); } public function down() { $this->changeColumn( 'horde_activesync_mailmap', 'sync_deleted', 'integer' ); $this->changeColumn( 'horde_activesync_mailmap', 'sync_flagged', 'integer' ); $this->changeColumn( 'horde_activesync_mailmap', 'sync_read', 'integer' ); } }Horde_ActiveSync-2.31.1/migration/Horde/ActiveSync/14_horde_activesync_binarystatefield.php0000664000076500000240000000060712654565405027007 0ustar changeColumn( 'horde_activesync_state', 'sync_data', 'binary'); } public function down() { $this->changeColumn( 'horde_activesync_state', 'sync_data', 'mediumtext'); } }Horde_ActiveSync-2.31.1/migration/Horde/ActiveSync/15_horde_activesync_integerimapuidfield.php0000664000076500000240000000100012654565405027455 0ustar changeColumn( 'horde_activesync_mailmap', 'message_uid', 'integer', array('null' => false, 'default' => 0)); } public function down() { $this->changeColumn( 'horde_activesync_mailmap', 'message_uid', 'string', array('limit' => 255, 'null' => false) ); } }Horde_ActiveSync-2.31.1/migration/Horde/ActiveSync/16_horde_activesync_fix_blob_length.php0000664000076500000240000000035012654565405026600 0ustar changeColumn('horde_activesync_state', 'sync_data', 'binary'); } public function down() { } } Horde_ActiveSync-2.31.1/migration/Horde/ActiveSync/17_horde_activesync_clearallstate.php0000664000076500000240000000133412654565405026277 0ustar delete('DELETE FROM horde_activesync_state'); $this->delete('DELETE FROM horde_activesync_map'); $this->delete('DELETE FROM horde_activesync_mailmap'); $this->delete('DELETE FROM horde_activesync_cache'); } public function down() { $this->delete('DELETE FROM horde_activesync_state'); $this->delete('DELETE FROM horde_activesync_map'); $this->delete('DELETE FROM horde_activesync_mailmap'); $this->delete('DELETE FROM horde_activesync_cache'); } }Horde_ActiveSync-2.31.1/migration/Horde/ActiveSync/18_horde_activesync_addmapdeleteflag.php0000664000076500000240000000052512654565405026724 0ustar addColumn( 'horde_activesync_map', 'sync_deleted', 'boolean'); } public function down() { $this->removeColumn('horde_activesync_map', 'sync_deleted'); } } Horde_ActiveSync-2.31.1/migration/Horde/ActiveSync/19_horde_activesync_addtimestamp.php0000664000076500000240000000072412654565405026137 0ustar renameColumn('horde_activesync_state', 'sync_time', 'sync_mod'); $this->addColumn('horde_activesync_state', 'sync_timestamp', 'integer'); } public function down() { $this->removeColumn('horde_activesync_state', 'sync_timestamp'); $this->renameColumn('horde_activesync_state', 'sync_mod', 'sync_time'); } } Horde_ActiveSync-2.31.1/migration/Horde/ActiveSync/20_horde_activesync_removesynccounters.php0000664000076500000240000000143412654565405027427 0ustar announce('Removing SyncKeyCounter data from cache.', 'cli.message'); $sql = 'SELECT * FROM horde_activesync_cache'; $rows = $this->_connection->selectAll($sql); $insert_sql = 'UPDATE horde_activesync_cache SET cache_data = ? WHERE cache_devid = ? AND cache_user = ?'; foreach ($rows as $row) { $data = unserialize($row['cache_data']); unset($data['synckeycounter']); $row['cache_data'] = serialize($data); $this->_connection->update($insert_sql, array($row['cache_data'], $row['cache_devid'], $row['cache_user'])); } } public function down() { // noop } } Horde_ActiveSync-2.31.1/migration/Horde/ActiveSync/21_horde_activesync_addmailmapchangeflag.php0000664000076500000240000000054112654565405027542 0ustar addColumn( 'horde_activesync_mailmap', 'sync_changed', 'boolean'); } public function down() { $this->removeColumn('horde_activesync_mailmap', 'sync_changed'); } } Horde_ActiveSync-2.31.1/test/Horde/ActiveSync/Factory/TestServer.php0000664000076500000240000000445512654565405022251 0ustar * @category Horde * @copyright 2014-2016 Horde LLC * @ignore * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @package Horde_ActiveSync * @subpackage UnitTests */ class Horde_ActiveSync_Factory_TestServer extends Horde_Test_Case { public $server; public $driver; public $input; public $output; public $request; public function __construct($params = array()) { $this->driver = $this->getMockSkipConstructor('Horde_ActiveSync_Driver_Base'); $this->input = fopen('php://memory', 'wb+'); $decoder = new Horde_ActiveSync_Wbxml_Decoder($this->input); $this->output = fopen('php://memory', 'wb+'); $encoder = new Horde_ActiveSync_Wbxml_Encoder($this->output); $state = $this->getMockSkipConstructor('Horde_ActiveSync_State_Base'); $this->request = $this->getMockSkipConstructor('Horde_Controller_Request_Http'); $this->request->expects($this->any()) ->method('getHeader') ->will($this->returnValue('14.1')); $this->request->expects($this->any()) ->method('getServerVars') ->will($this->returnValue(array('PHP_AUTH_USER' => 'mike', 'PHP_AUTH_PW' => 'password'))); $this->server = new Horde_ActiveSync($this->driver, $decoder, $encoder, $state, $this->request); } }Horde_ActiveSync-2.31.1/test/Horde/ActiveSync/fixtures/appointment.wbxml0000664000076500000240000000057012654565405023277 0ustar ]ELAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsAAAABAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAACAAIAAAAAAAAAxP///w==g20111201T200000ZR20111201T210000ZfEvent TitleWPhiladelphia, PAM2e1Q20111201T200000ZX0KEvent DescriptionL0Horde_ActiveSync-2.31.1/test/Horde/ActiveSync/fixtures/default_policies.wbxml0000664000076500000240000000003612654565405024251 0ustar MN0P0S1W5000000Horde_ActiveSync-2.31.1/test/Horde/ActiveSync/fixtures/default_policies.xml0000664000076500000240000000020112654565405023712 0ustar Horde_ActiveSync-2.31.1/test/Horde/ActiveSync/fixtures/dst.wbxml0000664000076500000240000000060512654565405021532 0ustar ]ELAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsAAAABAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAACAAIAAAAAAAAAxP///w==Q20111001T190000Zg20111001T190000ZfEvent TitleWPhiladelphia, PAR20111001T200000Z[\1_2`64e1M2KEvent DescriptionL0Horde_ActiveSync-2.31.1/test/Horde/ActiveSync/fixtures/email_plain.eml0000664000076500000240000000561712654565405022646 0ustar Return-Path: X-Original-To: mike@theupstairsroom.com Delivered-To: mike@theupstairsroom.com Received: from localhost (localhost [127.0.0.1]) by door.theupstairsroom.com (Postfix) with ESMTP id 44C2F5C1856 for ; Mon, 8 Sep 2014 10:27:04 -0400 (EDT) Received: from door.theupstairsroom.com ([127.0.0.1]) by localhost (door.theupstairsroom.com [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id 8b8ipjifmJda for ; Mon, 8 Sep 2014 10:27:00 -0400 (EDT) Received: from lists.horde.org (lists.horde.org [200.46.208.138]) by door.theupstairsroom.com (Postfix) with ESMTP id 627185C0848 for ; Mon, 8 Sep 2014 10:26:59 -0400 (EDT) Received: by lists.horde.org (Postfix) id 0A16F9ED39C; Mon, 8 Sep 2014 14:26:59 +0000 (UTC) Delivered-To: mrubinsk@horde.org Received: from maia.hub.org (unknown [200.46.151.188]) by lists.horde.org (Postfix) with ESMTP id 89F189ED39D for ; Mon, 8 Sep 2014 14:26:58 +0000 (UTC) Received: from lists.horde.org ([200.46.208.138]) by maia.hub.org (mx1.hub.org [200.46.151.188]) (amavisd-maia, port 10024) with ESMTP id 84126-04 for ; Mon, 8 Sep 2014 14:26:57 +0000 (UTC) X-Greylist: from auto-whitelisted by SQLgrey-1.8.0 Received: from door.theupstairsroom.com (door.theupstairsroom.com [50.248.141.100]) by lists.horde.org (Postfix) with ESMTP id 1DFDE9ED39C for ; Mon, 8 Sep 2014 14:26:55 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by door.theupstairsroom.com (Postfix) with ESMTP id 56BBB5C1804 for ; Mon, 8 Sep 2014 10:26:55 -0400 (EDT) Received: from door.theupstairsroom.com ([127.0.0.1]) by localhost (door.theupstairsroom.com [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id D6eD9fr0Rxpi for ; Mon, 8 Sep 2014 10:26:50 -0400 (EDT) Received: from door (localhost [127.0.0.1]) (using TLSv1 with cipher ECDHE-RSA-AES256-SHA (256/256 bits)) (No client certificate requested) by door.theupstairsroom.com (Postfix) with ESMTPS id 97FE05C0848 for ; Mon, 8 Sep 2014 10:26:50 -0400 (EDT) Received: from coffee.theupstairsroom.com (coffee.theupstairsroom.com [50.248.141.99]) by h4.theupstairsroom.com (Horde Framework) with HTTP; Mon, 08 Sep 2014 10:26:50 -0400 Date: Mon, 08 Sep 2014 10:26:50 -0400 Message-ID: <20140908102650.Horde.1eE5viMIP-X-AwWHvBqf6A9@h4.theupstairsroom.com> From: Michael J Rubinsky To: mrubinsk@horde.org Subject: Subject Reply-to: mrubinsk@horde.org User-Agent: Internet Messaging Program (IMP) H5 (6.3.0-git) Content-Type: text/plain; charset=UTF-8; format=flowed; DelSp=Yes MIME-Version: 1.0 Content-Disposition: inline Foo -- mike The Horde Project http://www.horde.org https://www.facebook.com/hordeproject https://www.twitter.com/hordeproject Horde_ActiveSync-2.31.1/test/Horde/ActiveSync/fixtures/email_signed.eml0000664000076500000240000002677312654565405023022 0ustar Return-Path: X-Original-To: mike@theupstairsroom.com Delivered-To: mike@theupstairsroom.com Received: from localhost (localhost [127.0.0.1]) by door.theupstairsroom.com (Postfix) with ESMTP id DCC0C5C182F for ; Mon, 8 Sep 2014 10:27:27 -0400 (EDT) X-Virus-Scanned: Debian amavisd-new at mail.theupstairsroom.com X-Spam-Flag: NO X-Spam-Score: -4.271 X-Spam-Level: X-Spam-Status: No, score=-4.271 required=5 tests=[RCVD_IN_MSPIKE_H2=-1.772, RP_MATCHES_RCVD=-2.499, SPF_HELO_PASS=-0.001, SPF_PASS=-0.001, TVD_SPACE_RATIO=0.001, URIBL_BLOCKED=0.001] autolearn=unavailable autolearn_force=no Received: from door.theupstairsroom.com ([127.0.0.1]) by localhost (door.theupstairsroom.com [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id taRzuyxOLiuY for ; Mon, 8 Sep 2014 10:27:24 -0400 (EDT) Received: from lists.horde.org (lists.horde.org [200.46.208.138]) by door.theupstairsroom.com (Postfix) with ESMTP id EF9935C0848 for ; Mon, 8 Sep 2014 10:27:23 -0400 (EDT) Received: by lists.horde.org (Postfix) id 5F7AE9ED39E; Mon, 8 Sep 2014 14:27:22 +0000 (UTC) Delivered-To: mrubinsk@horde.org Received: from maia.hub.org (unknown [200.46.151.188]) by lists.horde.org (Postfix) with ESMTP id 3F86D9ED39D for ; Mon, 8 Sep 2014 14:27:22 +0000 (UTC) Received: from lists.horde.org ([200.46.208.138]) by maia.hub.org (mx1.hub.org [200.46.151.188]) (amavisd-maia, port 10024) with ESMTP id 83451-10 for ; Mon, 8 Sep 2014 14:27:21 +0000 (UTC) X-Greylist: from auto-whitelisted by SQLgrey-1.8.0 Received: from door.theupstairsroom.com (door.theupstairsroom.com [50.248.141.100]) by lists.horde.org (Postfix) with ESMTP id 5C87E9ED39C for ; Mon, 8 Sep 2014 14:27:21 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by door.theupstairsroom.com (Postfix) with ESMTP id BE20A5C182F for ; Mon, 8 Sep 2014 10:27:20 -0400 (EDT) X-Virus-Scanned: Debian amavisd-new at mail.theupstairsroom.com Received: from door.theupstairsroom.com ([127.0.0.1]) by localhost (door.theupstairsroom.com [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id Fliipi8flrW4 for ; Mon, 8 Sep 2014 10:27:16 -0400 (EDT) Received: from door (localhost [127.0.0.1]) (using TLSv1 with cipher ECDHE-RSA-AES256-SHA (256/256 bits)) (No client certificate requested) by door.theupstairsroom.com (Postfix) with ESMTPS id 06BA25C0848 for ; Mon, 8 Sep 2014 10:27:15 -0400 (EDT) Received: from coffee.theupstairsroom.com (coffee.theupstairsroom.com [50.248.141.99]) by h4.theupstairsroom.com (Horde Framework) with HTTP; Mon, 08 Sep 2014 10:27:15 -0400 Date: Mon, 08 Sep 2014 10:27:15 -0400 Message-ID: <20140908102715.Horde.gX8OObXnqFO_O93n9Z5Xeg6@h4.theupstairsroom.com> From: Michael J Rubinsky To: mrubinsk@horde.org Subject: Subject Reply-to: mrubinsk@horde.org User-Agent: Internet Messaging Program (IMP) H5 (6.3.0-git) Content-Type: multipart/signed; boundary="=_qB1DuKNhitDqp4_HYjkEVg2"; protocol="application/pkcs7-signature"; micalg=sha-1 MIME-Version: 1.0 This is a cryptographically signed message in MIME format. --=_qB1DuKNhitDqp4_HYjkEVg2 Content-Type: text/plain; charset=UTF-8; format=flowed; DelSp=Yes Content-Disposition: inline S/MIME Signed. -- mike The Horde Project http://www.horde.org https://www.facebook.com/hordeproject https://www.twitter.com/hordeproject --=_qB1DuKNhitDqp4_HYjkEVg2 Content-Type: application/pkcs7-signature; name=smime.p7s Content-Description: S/MIME Signature Content-Disposition: attachment; filename=smime.p7s Content-Transfer-Encoding: base64 MIIW6QYJKoZIhvcNAQcCoIIW2jCCFtYCAQExDzANBglghkgBZQMEAgEFADALBgkqhkiG9w0BBwGg ghQGMIIGPzCCBSegAwIBAgIDCub6MA0GCSqGSIb3DQEBBQUAMIGMMQswCQYDVQQGEwJJTDEWMBQG A1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUg U2lnbmluZzE4MDYGA1UEAxMvU3RhcnRDb20gQ2xhc3MgMSBQcmltYXJ5IEludGVybWVkaWF0ZSBD bGllbnQgQ0EwHhcNMTQwODIwMDAzMDMzWhcNMTUwODIwMTkyODE4WjBbMRkwFwYDVQQNExA3MENO ODVGZU9jVzdVaGM2MRswGQYDVQQDDBJtcnViaW5za0Bob3JkZS5vcmcxITAfBgkqhkiG9w0BCQEW Em1ydWJpbnNrQGhvcmRlLm9yZzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALhwAuB3 QSKeGGkaj76NDCNZ/9Rmv67dm9CISZBtMjN3LYWVSFUDQ4HCbYtZvmiXHW2sHse6f9yelEbey1oI n9ZycAG/EEEozTUZ3u/DmWbXFRF/HFmvd4QkVZ0CfCjvsM23HjJicXxF5Gdd5FPePp7YB4qg1wAu DGkDj34B0pODNb1hIkBt57bYc8wMMorhvl4+VeKJ5XeXnb90Wuc7yukxGXikpCZy3CL4UVC1Rk65 7ISodSYyhP6s9li49CYANFVagqLz+9SgXOEGTkPwtkb3Qr9d+nUEhRJdOlD3M/NQvnsnDoyu767z H1a4Jc/mIZ9xZxOOHdsomxUtp1hHw+sCAwEAAaOCAtgwggLUMAkGA1UdEwQCMAAwCwYDVR0PBAQD AgSwMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDBDAdBgNVHQ4EFgQUzm+FuTXGfuSxT8K5 3NRmsMo/KE8wHwYDVR0jBBgwFoAUU3Ltkpzg2ssBXHx+ljVO8tS4UYIwHQYDVR0RBBYwFIESbXJ1 Ymluc2tAaG9yZGUub3JnMIIBTAYDVR0gBIIBQzCCAT8wggE7BgsrBgEEAYG1NwECAzCCASowLgYI KwYBBQUHAgEWImh0dHA6Ly93d3cuc3RhcnRzc2wuY29tL3BvbGljeS5wZGYwgfcGCCsGAQUFBwIC MIHqMCcWIFN0YXJ0Q29tIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MAMCAQEagb5UaGlzIGNlcnRp ZmljYXRlIHdhcyBpc3N1ZWQgYWNjb3JkaW5nIHRvIHRoZSBDbGFzcyAxIFZhbGlkYXRpb24gcmVx dWlyZW1lbnRzIG9mIHRoZSBTdGFydENvbSBDQSBwb2xpY3ksIHJlbGlhbmNlIG9ubHkgZm9yIHRo ZSBpbnRlbmRlZCBwdXJwb3NlIGluIGNvbXBsaWFuY2Ugb2YgdGhlIHJlbHlpbmcgcGFydHkgb2Js aWdhdGlvbnMuMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwuc3RhcnRzc2wuY29tL2NydHUx LWNybC5jcmwwgY4GCCsGAQUFBwEBBIGBMH8wOQYIKwYBBQUHMAGGLWh0dHA6Ly9vY3NwLnN0YXJ0 c3NsLmNvbS9zdWIvY2xhc3MxL2NsaWVudC9jYTBCBggrBgEFBQcwAoY2aHR0cDovL2FpYS5zdGFy dHNzbC5jb20vY2VydHMvc3ViLmNsYXNzMS5jbGllbnQuY2EuY3J0MCMGA1UdEgQcMBqGGGh0dHA6 Ly93d3cuc3RhcnRzc2wuY29tLzANBgkqhkiG9w0BAQUFAAOCAQEAXlCyZJlNzWD42T/Pw9NUhSGX 3rS8d+PiYpaM4ahs1+K0E98HcjPzdpVzjiQ7FNmQuafnIP73GRxkQ1POKxv0LNtBs8uddtUVCP5j oOaiLY9NSCI/L7ZgkZKJF535oSUMEyroKvmnQYGNOIpoRH5k+/6UbpThGrffLCA3GA3eSPR1CsSp Slf9awZcOhSRPIHdxCT00kxSkzJzme4CeXxBW1Xm1ncgeVNNH41s0BnOFwzIiBaYfSJk2DuafX6q n16u8JmuY7a+kxY03OEEknfe+nC+vZOiMi5JS2n/XGrt63MVTJMQjkrNffNoHp+9RGJ7q7IKaG5t G0h/bSqhh2MMOjCCBjQwggQcoAMCAQICAR4wDQYJKoZIhvcNAQEFBQAwfTELMAkGA1UEBhMCSUwx FjAUBgNVBAoTDVN0YXJ0Q29tIEx0ZC4xKzApBgNVBAsTIlNlY3VyZSBEaWdpdGFsIENlcnRpZmlj YXRlIFNpZ25pbmcxKTAnBgNVBAMTIFN0YXJ0Q29tIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4X DTA3MTAyNDIxMDE1NVoXDTE3MTAyNDIxMDE1NVowgYwxCzAJBgNVBAYTAklMMRYwFAYDVQQKEw1T dGFydENvbSBMdGQuMSswKQYDVQQLEyJTZWN1cmUgRGlnaXRhbCBDZXJ0aWZpY2F0ZSBTaWduaW5n MTgwNgYDVQQDEy9TdGFydENvbSBDbGFzcyAxIFByaW1hcnkgSW50ZXJtZWRpYXRlIENsaWVudCBD QTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMcJg8zOLdgasSmkLhOrlr6KMoOMpohB llVHrdRvEg/q6r8jR+EK75xCGhR8ToREoqe7zM9/UnC6TS2y9UKTpT1v7RSMzR0t6ndl0TWBuUr/ UXBhPk+Kmy7bI4yW4urC+y7P3/1/X7U8ocb8VpH/Clt+4iq7nirMcNh6qJR+xjOhV+VHzQMALuGY n5KZmc1NbJQYclsGkDxDz2UbFqE2+6vIZoL+jb9x4Pa5gNf1TwSDkOkikZB1xtB4ZqtXThaABSON dfmv/Z1pua3FYxnCFmdr/+N2JLKutIxMYqQOJebr/f/h5t95m4JgrM3Y/w7YX9d7YAL9jvN4SydH sU6n65cCAwEAAaOCAa0wggGpMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1Ud DgQWBBRTcu2SnODaywFcfH6WNU7y1LhRgjAfBgNVHSMEGDAWgBROC+8apEBbpRdphzDKNGhD0EGu 8jBmBggrBgEFBQcBAQRaMFgwJwYIKwYBBQUHMAGGG2h0dHA6Ly9vY3NwLnN0YXJ0c3NsLmNvbS9j YTAtBggrBgEFBQcwAoYhaHR0cDovL3d3dy5zdGFydHNzbC5jb20vc2ZzY2EuY3J0MFsGA1UdHwRU MFIwJ6AloCOGIWh0dHA6Ly93d3cuc3RhcnRzc2wuY29tL3Nmc2NhLmNybDAnoCWgI4YhaHR0cDov L2NybC5zdGFydHNzbC5jb20vc2ZzY2EuY3JsMIGABgNVHSAEeTB3MHUGCysGAQQBgbU3AQIBMGYw LgYIKwYBBQUHAgEWImh0dHA6Ly93d3cuc3RhcnRzc2wuY29tL3BvbGljeS5wZGYwNAYIKwYBBQUH AgEWKGh0dHA6Ly93d3cuc3RhcnRzc2wuY29tL2ludGVybWVkaWF0ZS5wZGYwDQYJKoZIhvcNAQEF BQADggIBAAqDCH14qywGXLhjjF6uHLkjd02hcdh9hrw+VUsv+q1eeQWB21jWj3kJ96AUlPCoEGZ/ ynJNScWy6QMVQjbbMXltUfO4n4bGGdKo3awPWp61tjAFgraLJgDk+DsSvUD6EowjMTNx25GQgyYJ 5RPIzKKR9tQW8gGK+2+RHxkUCTbYFnL6kl8Ch507rUdPPipJ9CgJFws3kDS3gOS5WFMxcjO5DwKf KSETEPrHh7p5shuuNktvsv6hxHTLhiMKX893gxdT3XLS9OKmCv87vkINQcNEcIIoFWbP9HORz9v3 vQwR4e3ksLc2JZOAFK+ssS5XMEoznzpihEP0PLc4dCBYjbvSD7kxgDwZ+Aj8Q9PkbvE9sIPP7ON0 fz095HdThKjiVJe6vofq+n6b1NBc8XdrQvBmunwxD5nvtTW4vtN6VY7mUCmxsCieuoBJ9OlqmsVW QvifIYf40dJPZkk9YgGTzWLpXDSfLSplbY2LL9C9U0ptvjcDjefLTvqSFc7tw1sEhF0n/qpA2r0G pvkLRDmcSwVyPvmjFBGqUp/pNy8ZuPGQmHwFi2/14+xeSUDG2bwnsYJQG2EdJCB6luQ57GEnTA/y KZSTKI8dDQa8Sd3zfXb19mOgSF0bBdXbuKhEpuP9wirslFe6fQ1t5j5R0xi72MZ8ikMu1RQZKCyD bMwazlHiMIIHhzCCBW+gAwIBAgIBLTANBgkqhkiG9w0BAQsFADB9MQswCQYDVQQGEwJJTDEWMBQG A1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUg U2lnbmluZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDYw OTE3MTk0NjM3WhcNMzYwOTE3MTk0NjM2WjB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRD b20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcG A1UEAxMgU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUA A4ICDwAwggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZkpMyONvg45iPw bm2xPN1yo4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rfOQVSWff0G0ZDpNKFhdLDcfN1 YjS6LIp/Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/CJi/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+ 7bWgiA/deMotHweXMAEtcnn6RtYTKqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfk K+F2PrRt2PZE4XNiHzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb 6kMMAv+Z6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w+2OqqGwa VLRcJXrJosmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+Gkhpi8KWTRoSsmkXwQqQ 1vp5Iki/untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ +xjGtrVcUjyJthkqcwEKDwOzEmDyei+B26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbj M4xdCUsT37uMdBNSSwIDAQABo4ICEDCCAgwwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC AQYwHQYDVR0OBBYEFE4L7xqkQFulF2mHMMo0aEPQQa7yMB8GA1UdIwQYMBaAFE4L7xqkQFulF2mH MMo0aEPQQa7yMIIBWgYDVR0gBIIBUTCCAU0wggFJBgsrBgEEAYG1NwEBATCCATgwLgYIKwYBBQUH AgEWImh0dHA6Ly93d3cuc3RhcnRzc2wuY29tL3BvbGljeS5wZGYwNAYIKwYBBQUHAgEWKGh0dHA6 Ly93d3cuc3RhcnRzc2wuY29tL2ludGVybWVkaWF0ZS5wZGYwgc8GCCsGAQUFBwICMIHCMCcWIFN0 YXJ0IENvbW1lcmNpYWwgKFN0YXJ0Q29tKSBMdGQuMAMCAQEagZZMaW1pdGVkIExpYWJpbGl0eSwg cmVhZCB0aGUgc2VjdGlvbiAqTGVnYWwgTGltaXRhdGlvbnMqIG9mIHRoZSBTdGFydENvbSBDZXJ0 aWZpY2F0aW9uIEF1dGhvcml0eSBQb2xpY3kgYXZhaWxhYmxlIGF0IGh0dHA6Ly93d3cuc3RhcnRz c2wuY29tL3BvbGljeS5wZGYwEQYJYIZIAYb4QgEBBAQDAgAHMDgGCWCGSAGG+EIBDQQrFilTdGFy dENvbSBGcmVlIFNTTCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTANBgkqhkiG9w0BAQsFAAOCAgEA jo/n3JR5fPGFf59Jb2vKXfuM/gTFwWLRfUKKvFO3lANmMD+x5wqnUCBVJX92ehQN6wQOQOY+2Iir ByeDqXWmN3PH/UvSTa0XQMhGvjt/UfzDtgUx3M2FIk5xt/JxXrAaxrqTi3iSSoX4eA+D/i+tLPfk pLst0OcNOrg+zvZ49q5HJMqjNTbOx8aHmNrs++myziebiMMEofYLWWivydsQD032ZGNcpRJvkrKT lMeIFw6Ttn5ii5B/q06f/ON1FE8qMt9bDeD1e5MNq6HPh+GlBEXoPBKlCcWw0bdT82AUuoVpaiF8 H3VhFyAXe2w7QSlc4axa0c2Mm+tgHRns9+Ww2vl5GKVFP0lDV9LdJNUso/2RjSe15esUBppMeyG7 Oq0wBhjA2MFrLH9ZXF2RsXAiV+uKa0hK1Q8p7MZAwC+ITGgBF3f0JBlPvfrhsiAhS90a2Cl9qrje VOwhVYBsHvUwyKMQ5bLmKhQxw4UtjJixhlpPiVktucf3HMiKf8CdBUrmQk9io20ppB+Fq9vlgcit Kj1MXVuEJnHEhV5xJMqlG2zYYdMa4FTbzrqpMrUi9nNBCV24F10OD5mQ1kfabwo6YigUZ4LZ8dCA WZvLMdibD4x3TrVoivJs9iQOLWxwxXPR3hTQcY+203sC9uO41Alua551hDnmfyWl8kgAwKQB2j8x ggKnMIICowIBATCBlDCBjDELMAkGA1UEBhMCSUwxFjAUBgNVBAoTDVN0YXJ0Q29tIEx0ZC4xKzAp BgNVBAsTIlNlY3VyZSBEaWdpdGFsIENlcnRpZmljYXRlIFNpZ25pbmcxODA2BgNVBAMTL1N0YXJ0 Q29tIENsYXNzIDEgUHJpbWFyeSBJbnRlcm1lZGlhdGUgQ2xpZW50IENBAgMK5vowDQYJYIZIAWUD BAIBBQCggeQwGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMTQwOTA4 MTQyNzE1WjAvBgkqhkiG9w0BCQQxIgQgPR5UbtBahh7iXUHcNwow2EfvFbZQ2cjIvcsaHHxXF4Ew eQYJKoZIhvcNAQkPMWwwajALBglghkgBZQMEASowCwYJYIZIAWUDBAEWMAsGCWCGSAFlAwQBAjAK BggqhkiG9w0DBzAOBggqhkiG9w0DAgICAIAwDQYIKoZIhvcNAwICAUAwBwYFKw4DAgcwDQYIKoZI hvcNAwICASgwDQYJKoZIhvcNAQEBBQAEggEAPD+BI+qIbNcRJH9iNFP2DBudRMHopX3iqSSkXvSK tJGGyudofmW+ib1Q+Wkk8whqk5I2qNO+ccWJlseCbo/wHsAI7ZGNKSo8CemOs0l7TVvWXent6YiY TOCncU8gUKqr098LkreraDuExd5f9TydYRmLWZW6a20nQvmH8XCbrpfawDpnvTNZ0Q8Z4nkQo02w g8LKMOO3mcubHwsIen6Eak8klb8y7JfnsFeX7zhhPYFz3Kvn2qerNmBX3+w8Jk+zVg8PhG/9ejsz yYH71HlhAFQ1R0Uh5Cb9Db4j5udt5JPw51S61J/JxZYnbjnaXUl+b8JgWtPD1XcM1MLFHLhQxQ== --=_qB1DuKNhitDqp4_HYjkEVg2-- Horde_ActiveSync-2.31.1/test/Horde/ActiveSync/fixtures/encrypted.eml0000664000076500000240000003170112654565405022362 0ustar Return-Path: X-Original-To: mike@theupstairsroom.com Delivered-To: mike@theupstairsroom.com Received: by prod.theupstairsroom.com (Postfix, from userid 5001) id A3D305C07BF; Mon, 16 Jun 2014 15:00:15 -0400 (EDT) X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on prod.theupstairsroom.com X-Spam-Level: ** X-Spam-Status: No, score=2.0 required=5.0 tests=BASE64_LENGTH_79_INF, RCVD_IN_MSPIKE_H3,RCVD_IN_MSPIKE_WL,SPF_HELO_PASS,T_DKIM_INVALID autolearn=no autolearn_force=no version=3.4.0 Received: from lists.horde.org (lists.horde.org [200.46.208.138]) by prod.theupstairsroom.com (Postfix) with ESMTP id 233F35C05DF for ; Mon, 16 Jun 2014 15:00:11 -0400 (EDT) Received: by lists.horde.org (Postfix) id 0E68217A1AD3; Mon, 16 Jun 2014 19:00:12 +0000 (UTC) Delivered-To: mrubinsk@horde.org Received: from maia.hub.org (unknown [200.46.151.188]) by lists.horde.org (Postfix) with ESMTP id AC3A917A1AD4 for ; Mon, 16 Jun 2014 19:00:11 +0000 (UTC) Received: from lists.horde.org ([200.46.208.138]) by maia.hub.org (mx1.hub.org [200.46.151.188]) (amavisd-maia, port 10024) with ESMTP id 55076-06 for ; Mon, 16 Jun 2014 19:00:13 +0000 (UTC) X-Greylist: delayed 01:08:48.361142 by SQLgrey-1.8.0 Received: from delta.interfasys.net (delta.interfasys.net [144.76.174.150]) by lists.horde.org (Postfix) with ESMTP id 1B69017A1AD3 for ; Mon, 16 Jun 2014 19:00:09 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=swisscontractors.co.uk; s=dkim; h=To:From:Subject:Date:Message-ID:Content-Transfer-Encoding:MIME-Version:Content-Type; bh=AUlwJvH8jxiSCAuv65B/TOPq61BUAZQb87Mx54zK6Ng=; b=XzjoSJzJMPr/qtxklsPA56DUJqh5P1I/warn9NEtfA6wnrH8P3y6j2TkT6lJS9luxLwLanOMzLRQ7QnjlBEJ7L/UWzPYBtkHP9aMeDVU1thOvXrMQaS4rjzPcTZkuY03MfyOypT/a4JFFoYdjU0eimUUrZpgavpqcW930CRtiUA=; Received: from delta.interfasys.net ([2a01:4f8:200:5295::2]:64783) by delta.interfasys.net with esmtpsa (TLSv1:DHE-RSA-AES256-SHA:256) (Exim 4.82) (envelope-from ) id 1Wwb3t-000Aj4-Lc for mrubinsk@horde.org; Mon, 16 Jun 2014 19:51:17 +0200 Content-Type: application/x-pkcs7-mime; name=smime.p7m; smime-type=enveloped-data MIME-Version: 1.0 Content-Transfer-Encoding: base64 Content-Disposition: attachment; filename=smime.p7m X-Client-ID: 1907 X-Mailer: BlackBerry Email (10.2.1.3175) Message-ID: <20140616175116.6574230.97436.1907@swisscontractors.co.uk> Date: Mon, 16 Jun 2014 19:51:16 +0200 Subject: Encrypted message from BB 10.2.1 via Horde 5.1 From: olivier.paroz@swisscontractors.co.uk To: Michael J Rubinsky User-Agent: Horde Application Framework 5 X-Antivirus-Scanner: Seems clean. You should still use an Antivirus Scanner MIAGCSqGSIb3DQEHA6CAMIACAQAxggNiMIIBrQIBADCBlDCBjDELMAkGA1UEBhMCSUwxFjAUBgNVBAoTDVN0YXJ0Q29tIEx0ZC4xKzApBgNVBAsTIlNlY3VyZSBEaWdpdGFsIENlcnRpZmljYXRlIFNpZ25pbmcxODA2BgNVBAMTL1N0YXJ0Q29tIENsYXNzIDEgUHJpbWFyeSBJbnRlcm1lZGlhdGUgQ2xpZW50IENBAgMHcRYwDQYJKoZIhvcNAQEBBQAEggEAcdV083VbKRn2zw0SGM3WJq9jw7rR0aPeN4n2MgISvBrs0EsMxaTMSwQ5x2EmYcZlJTQw/+YRFYUqS4HXk6zg6JzbBieyBwNv5OyvYYqvIEhpnlbOfH4SheYoTKePGNgEN8NvVba9Ofu+t5+b6839hYo5/SIwGcpu1PazzvowBz7OK6EXjevUc/UUQ9L7A2Sey2QNI6U0w2B3O9hDMHwUyz61ecngXrNHiJfBhIs0ZUyHww/sarRFdX7v7Ei+8uUSxEP4M6aZq3Uk8sL2aqNzJQXWV8UHbyJHZ2ELPQhr6Dl86HksndgMr2zXn0UNmaE71MIg3BceC9GjET5qGGhrUjCCAa0CAQAwgZQwgYwxCzAJBgNVBAYTAklMMRYwFAYDVQQKEw1TdGFydENvbSBMdGQuMSswKQYDVQQLEyJTZWN1cmUgRGlnaXRhbCBDZXJ0aWZpY2F0ZSBTaWduaW5nMTgwNgYDVQQDEy9TdGFydENvbSBDbGFzcyAxIFByaW1hcnkgSW50ZXJtZWRpYXRlIENsaWVudCBDQQIDB+8LMA0GCSqGSIb3DQEBAQUABIIBAD9apmqov3153y8E1kb8yNz1iyHLl76BNgVtVzfqVVZko5Y1RE04Vg6FBmo6SGIYv1Cm+pp7XDWWrc0JxVTCS+MdQt1izT/RjvXUgKV0VvYiPAsyVXM91w95GNaP6Mu6fngTKjp0jXb6U8PKn7KKW3o1JA OytmnizrDtncXAnG6iw7POsl5uPso90xOSyvEbz4bg+XFGoY3iMsoha0Q8T0IxScXrA/J2cDJ7LxpR1GJDj2d9vvsCjjuhfcgzSd13hr3+4g3wshMUKAdfJFBN353NcfnZWzP0WnyN1O1G73NAJUYThfhRtG/3MKLJprdG5kBHbkYHbmX99huJyH3s+QUwgAYJKoZIhvcNAQcBMBQGCCqGSIb3DQMHBAhYvHuAbgticqCABIIaiB0zjcQvTUJeA+nmJHTxl++zevK+pYjyA3MejJgcMGRPCugSXtudMzYZNvh1xIlOSCi5q+WyzVsiVR12xP2/fI50GowEEfoVTPzYik2Vgbo73IxAd1uCa15fvCokx4Id9n70umfjFcAmoqMFbdKhkEDEYAiZ5aJQxUkjkVXwXRq+E3DiEEGIN0yBl97kuV4yKFzxLf+9pH0UOg/rBJSxTypYqiZzM+EE1OKyt+yf+l6lIVL5df1TRgttzkiFAQdstTuWUIibioyAgwto4ycYowXNhwK82NYiHdo+6EGqdfZi0PXmTH5Y2ZhrKQ6UUz9PT2pLbEnVYEkjFo2QLiITIo+TP4e9KNo2oh4sDngnsX34+mGFHzfQj1VabAw3jqzLIdL4lT6Od/C42Rf07VCjErckInbwLJQeth8Yckvm0k4xoNBY0+hiGZUfHCpCsqdO8wAR50+7YN+TvqjLScAKxl+86qOFUfy8UssNh/ktUwf2csrTHyP9y9aAbXY3T0oFVUZbbp0CSHP4KUh0HTGAzyuRf39RfAOuBLV05mPHAswfTmzZMNs9LktZ4WqsAeMdw2uDnR++HwLRxMe2/hDczqD5ErE+4vQ6RJUxYRCNIGn5PvSxyq8Et59p+FLVdKriL4So6oOKD5RfwNuXsSwTuWYxuE7YJFKsMUdQ3AvsV7ZATiE/TSGLxu1e6QblvipyU1xOVyKMNXv7JEvQjNc2Y/qn4E3v756emmQAstGSgAtzDXZq5m0 rYAKlu9Y2Gl8GFdegHv7zGsnX8UGIeT6hNNIyNb3br0BuAkUoB8Cjw672glKCXupr41GfPVdR3VS/jU3ThQ1bAlysfxnYeP2H9JsRVEVnIz6evUqf3jpVy8K2BC9X6JI1sTpXdNOg12ha1HviUdfxSom+7fnkgMMwTPsMCWCost/6sfSn2mFpbS81889p8DWivX2JByPqt8cWFphPpCZ6i7yrUo1+smfs3gB1AJdKJR+3Hkbu/a6QfS1SpgQ0dAvpF07r+PE4YgwvuRMEYt+uYsdUsO1tCIYyOmYvE9ZOsspXok+ccMOkHhBwwbWC0GN7qZVURE7OayBMbfYMmmcPkB8O/tt5CjcpY1/vMD94E8bzQSgjQUiGFI6NhEdAoJLXAgxoTYPce4ec318tsQIDFdDhySvTUzNSPGbIFtQO/beT8fkyDoaEbA+J9qw8ap0AHe8nFpIaMMgs3gYpFhzAYYkR9O5JWFTMfva+xGKjZuBPjQIDAmK2tFOs996b3BkXl+WngJzQ+9yKTGfnEPUFjvuRcnD50f6H8BuZQ+dNLx6rnSZHMOTOOLNT/KjknLcxhERHfa0zeiO3Df82seJMvkH/y2p2yKqIDc/HTPzbo21yzzfLh/XhRu2HkHl2SAaFJOXmr/XYMplZQEPNZsE6rtqge0/On3mVBlqrSHIEX98gr2Eh/SypAmD9wVAk00U+VX+CNig3eKu/6xVrjb1mZALO6Eh3bFyNLM7/8nUZwyWjYi34C2ov8TSGiQ65X7hagiRIOx7GVkm19sgl49ia/d2DonNKpbCjRqnXEWLJrS23TVD6lNz49JB4sYBoLqLoJmFxAt7RMP6BbWNk+ig8LAhD5UdoapWqK25pQZq3uFXwN1KZV8mH4iSsuEz4402tZzcwNYcTnOVD0lTsfTtKAEeXYUVlgJ2z7z9gWLoxUJL5slOLyJ1oD+XpBJ1nx2W3qCmqPhAfIgMOvt4w6FWa7rm1D3/zpht+fWKr eYhOaC+i1L1NtzmGmU2MXfVXofRvRCFNKFrzsTxG5FGtNDrx6O1Y9xqM9jsy8BSFS/RMQiZBugrt489DRWT/WVOmmNXGnR+jS72UGSSpQEMqQ7Ul2yqNjpaF81/vBvFq7F7luGKUOPm8pkPyN8LITUssWblgFQldrPXljHgs/czPKN/XuqWfGoCvLvA4LBbp4dwknUEHo/eyrHhuDS642N0gYyGgjJ4Uxt5YGlbDkXbfh/cZ6hkpm4/hDM8O6nSse/nlCndDLjp3Hj6ey08FXSilu8pwdkVYm8pVHL/4Mh9WBm1uSIX/872zcqTg5aCb2DM/NS7lRSljANiWKChRCt+V4mf7D/zFc58/K5ZZMolNux8ohld3w/7ea5WdxsGx6nnBt1t4u66sprZ79GhzB7wI8Wk4nhKixWM1pfcQeMUBJdqkMLo13um9nZg/azfNbtPdrX/LqdxiByTSR21EII5IhoX2ArxgBdYWzyDk8QIYJGjaxFcjKc3a6nAEjWSnazdGcY1gnTaz4NCYIDPkL4sXLuVjkujA00cUA0liLDu23RXxjFM++UIoAjFMy06UAlCo9cl3wa9yfjxWGPjxR4wJ1NRVu2jAf2QStT+I3taP4VGKr9JkzdbvvOlHo9UbXb6Bup6+BtPbohXqklOYvizRYKjcIRnf9CKb8NJmwosXDcFav4M9qXeAL1iufDVtS+MsZXWo4p8xhD1PYRzBbPJf7C18RHvY5OSOFLrid7Z63QPsLUT4dkavYKe/m7SZ/Qckm4PYwb18jEaSXS4SSSIkz9kImOtMduQQqb+bvKTiAP3sW3O/FxYeQVLPZOtHIBtL/SI3zSb3Vvvp+iJaiNvJHxRBWaxdFkLevJNUu4TIfyGFxA2xkryq4GWnABsN9KgctROLfko+pu8YtU/lnmXFecspAMjf0xyg6rUbVaudKgseyaKF7BOzmiAJrGhEPUiAJRKs6PfLJCLTFt3688OEgyXxpwCy2wLO9 ZO58ETk6Y2gLeI6HHzFPx5djdB+Vl4yN86ELxGqmO83TWmx2r+DVn1SbQRnav2Hfypxe6ndTtOowH/E3wyKnSGNY8bLk8x3dJfUc23Ifs9EaLiMEMk3WNVmSS0eXKtdXINHzSmmXq378oIiihvDMnWc1mVW0MDjpp6i9ESaepl6h6tY0wkfvK/k2pY6TMh0df865dvggkop7TBdcyigoffUXHzqKpOL19Yc2dFxFpEF78eKqEnUUmmnOplSDDAi7BeUV7zEzmy87eDtKPg69x+pB9kpz4TxtMQwN6IQT7vhI3hFtNA3HS0bKyyrCPDM401wbzanFnUPskZUEarl4vPjYUz+qjbTh7SQfn8HVts0tOIQ2ZhJOxuKu70j7xbMX+Pv9gtjdiTveSye9C/1YVp1MwpWVHAG9srxhjyVm4w8PwzLT/NH9qJJ19nn1kPvQpj5W2+qbPkfTkYVJodK0rm6Sdgb3foDhiYl7zSSRP4fPDBHApvCGPf+oP+QUkxv1Ydx3ulw3rjL0KtjbMaSKmof/0d98DfQ7UZtGzjM+aOXlrLtelV/GqPlIiQ41lCyeWSwMTPOAlEsuBmisZTzhxm4+7WXwnyz6gT3cMhBJw13ssLej633X7fctUn+P48s2LHFOPMtvXwlpYYU2YyHTgPt+Wh+m6aeqj+3INQJfe7VsL6pdaj7Jn81Gg438TeV/+Yk2sTWssJ9KpGIcFYSlsnbkqrNgrR20YC4Ervsaa6UoBUkhFpTISRK2GFGflg9ziI+NwLQbXop0mF4/tXzPRmnsLsrmjHET8TqRh+w5mrbDvRtoT5XVFiKeadBu0dx1D4hIaBOpfKfJ3+75g+jlZ4kSjUag3phSW6Cf8D4YwPbzj0ZSJT3HjfWXyfJfTmHpxXtshCV2DiFXPuIROnRKgO2sCZw7ryRq/fKS1nlrRb1WtSi4K45vzRUkMRKpLgjTyeUj5fJE86S2DH6Y4wlKAgxwPsPqDOqCpN/og kerWVPjj6d7lWc28cMcleXgDK/b0lXCyli69K6NPqkgRr8uMOzpvtIZScZfXvDljf1pRB8FTsztEkf6zOqk+WQGV9MIyl9rJ/XSTG22Bw1HkRwKFm+Oz2W7h2n9rpLGN2i8jagiiNU0b6AsR09Omp1vJSl1s115MjTjICOrtxZXh97UZXtcHayQBM1rlLAxGvxnc7JQ5C0d5IlApiuZX4RVXBYxcNEQxOs7CVXQspqjSMc92L86nYc11TPuT1r8gi8G2cJbcRwLOUibRvoFKeUM1xThGll1mvZ1A1PgJOuTnVSZElFv/yAAIgF2s2AVKjb+s/n+8AbdmU1n2SD+JyqMhdWNn81e31FrMkt4ItjeARgP1wcmiKHpMbPVrzgFWeeNErw8D4jRBvSI1GbRAfObdIIRsYrMS1xOABjERwk6HmJlA4fvce4eipcEdUu9RfaGSHmnCaEhpOpBtvbC4f+KsoWgnkC1v55UdoLz7S0ddHDC6yf3RYvS4rlO/oL0CM/hys9xFn/UVtKlUeHS9kxOH0yLUOt4tYdjdwaY78/Us1ryuyO1cpZA6sayZE1jf4GwlwdDVYsLedydpnCZ00EpDV/Y2OvVq8bWpTgGe25D/lBvfZFA+qHCpM77Y3V8ct7/c2yLpeQsBw8Gd6piTwdM3wpX/3bJfvzSE6ToJ11G8w4NZWiHpY9lXFTIlrs82eijZSEzcpLb6iKTkfmfJcwrAiwaxRgu14WFrNlyT+ah352tHJJRozDW2AccERyFBsmAibMrdxiYdWSxrvijjidH+EQ0rWBsoERbVN949tN0ilqd+RmHltwZRQOJ4St4OC17AcX49AAtB1tgyQWYNQCFn3pVadDj9fZ2cJ0sqCbNqk87z5/71tW6hkugzYc+bFv/wLkAbLdys7Ygh/3eIUDamgQTw6rh1dDj49BwUEznan+xDAH6zu5PrxLI+mfLd+tECqkiBfsrqtr9mf6LiYqrFaaCE47m8EDsTt jMAvn0eIzlTtadb/grgiPt7vzPtna5kFdtDIGfWyVR43eIRHe3F3wCKFsFu444fc66yrzCpwbUDcQaILt5TaiuDd5QV/epyGE5FcG4unPEqk/yIjKy4AZLMGNam6Ck+HO30WGHrwwEqBMLf5F7WsWFmGDG6EtyCoFf7+6ZW5W+XiuDVdWgpmqKx8/q1pc9VBpV19mNm8BvXn12Bg2gVVlzRx27cizUvIWZRt5Zx/f3DU8hQsxXSPGv1ism8PfPwSl3RMC3m1I4viX8JcrqHisDsfGqoPJbuDeT4tqoa/+FQfm4hDazY2nXDdaSHEYV/PgKahaCPKY7idVMgZmrATcSPvDTZ5WIfDkTk3oDZCCOLstSiiqHqCtQ5hy9AEmTr+yinEJkNxf4z7cwZrDqcj75tC0b82U3ZTdQfFDMzVLfWdF9SHlR8FbwGqQTmdAj8Cm6o5i2vmLAjZRjKYxXvPRtGOkjOGa+8U6g4kgMN98KF2GwiP85eclt+s6wM/7wFts1nT2NT0ig4a4rYSFeQHfLL7fndghSNwdBHkCZfsYoHmAJOP4QiPvHQRWCRBxPvUAdIg7LMcSEk6praKDHplwPrbJ6SFeuvkDI3dKtbvBc9/nprm0v8RySDXh5Oi3kiVFr3i1ezMq7CM4ubVvZOLxKCnPFVwb2q7ml2Gt1rLOOElNPDVZm0qqpBJcCS/0KuVZsx8wQ2XHFfENU/QK39QlHW21ltJhXK+VILZUZh/ahLark/8GxiUnj2BcetVEsdSHHGuzwP//HlC9GyneIsC2aXfg/ACqJmCJtaBo8qDGTou1WhR5ASgOJaaqI34bsHgdkErnHUbf15VS2WM3FkGpurp4xSAKLs8O0HWZC3+cULLPV2uYgggMWEt75dMfXtvMdUoNeQXiCRZ2hbDHgFuKRpmEuQXLfeItL9Sd5X+ZUEMDXMLNG2stmWtA0zaannZEl58YAFQMbu/OXSJO274epUh2n6DpNZaJzVjG xeY9FHo3E9RpDobzKB8UbI422wg7m6H+rLVgJLF5FByCzML7JN0bgdj2QWUd/4Nq7EY1MNkLf+6BTKTO14p5kSZyDJptilyGKREIx5apDILWHXF1qU7JpCtZDW8VXRE3Q9IufkN71iFASSU175Lv5CoXp0eVj6xaQHlcR2dg/+UytvzKXw8HFMVTf8tAL2251TEkHMKi81V2t0iFs4FMsFUpI8mWl9ELw0FFKoCkuUzsHZXni9VdTNB4mx7yGsMJGx0n5/ivu4zdWqn56KNnkK8rP3nDEZZ/aszRqh4L8C3yvbd/YuLt68JVSs/o04y1qxeh204fxbOaM9LRGbU1ZHJ90TUSMvyNEgALRXMNTXp0rZcT/Ma2c9znNhaR+gW8JQoPeoWMeC9ks27jFaDHVFIHZ6A9oIgBYriy0hS4op7qMkta+fXV4mKmajKusp16Aw6qfr6KwaCQNIWHEtvWzQqW+XoPBb/st2Kor2UosuqcT9mzisfE8ujw/OglepqymgNPF06LDnLeLcd0XeRkIbgvZFMI6zLd2dtyN1cgfEZ1Nd07eMXs0aKyf5z8668R/m2M39grIAiOpQ5vhrBzpJMIDHX/xrJWx6agwjv8m2+rAW8FOoD4UUYTaEKoQomD+dn3PNHF2cAv4y7VV86WxfU2z3ARmC2EMr/c8wKHuxj/HdYVlfxdylP5yNLJjRuFaBSQdiWn+Iq0GLHbZYxjyMZpqysrZ5dcH4t0faHbPpng2t18xNRsWNFvi22ggs7NSooXVz1Mvfu/y9cKwdMiOOCS+IBxC81oTqetb3Zp0sMIVzW4lSnejUg9Sv/sffrjPUE57LQ9tVrLld8UPayqDWoSllOPJl5dD3sr/DTtp3bz+3/MVFs8wXLhAE7kF9cqd9eKPC8XVjMM35DAElJILX8W43hzj6lXSP7oSTEqdMsOtrJ9r86dcJ/aWeJLwAn+NNDUUoTH7mkNYiwcm2/VH3SkmERubaSQZGrNh S0aa4b4Z7MoaWciJX1IRrZQcqWP4g7vsh1sdKzMm0x2759Lxjt/Ue6M3be4R0KoyiCAEg5kTZgmjsFH62O1Lyxz3ySTEy3vo8te+Zx6s+JD17E0mZNDn6lwUasFS6xv/O/ZHkJ8OF2Bjyw7wuILIAwW+KRdHecGJrW9DraA6xOVuVY/oiQMOVDXSU3lI9tOm5vQzkh1wVWZZfcd6RGeIhls6lbDRjb/qtQde9X+bF6Uf3z6yaAD1Y92PJzVznEn82h6dqa3cy/jD695GBrjD8H0dxuW9l/fbcJXmIadYp9NTJDmMMlc1LXKUQJyTwGDhT1v+c3fnPnLrgn+rQUM5W2CVaan8ZNhoOpyIt7qpGmIKqZb0v7SeT2S0C2z86D/TY6sOmc5Mf7i2amQ9ptN+YZgAkKlIv42t5Kxg/NeA4Vlk4D2t1TejuRY+RaEsynvz/D4WFdko7E8S/t5WiyeVhQNqAV5GzDDtGT1+qFqGWfsYY1r5AvqSrSubPShxzIQPbpImzYQ6+nNZ3zttI6t64Qhark3gq1jCwfJl1mmApc4SRmkSRZQLLwgMGc5i43qjyJ5nyA4yuhErRXyMTC6v5VY3qZ/CAcWf2TG32MYlxBcW8b/+K/V1whBcdb6xvdY7flaRf403XtHh4Zh9FufyaIQx/AAG3K4E3qGuZixCS55ldbv2RkJOi7u5j8kC+MlZTrUIAztj+DqFrpQmOOcTrVf5zWokhzd0BIxR7FY9uM4OPRh9NOdBAsNWqjhXoaGjZ3lsouZ53kEOEtU1MdBH9JzM+RP6oFTAl4rRfmypNiUzFLosQHPRib3M6q1t/WCqIN8KFZZPmJoOUVDoHB3iXTh1tOGF5tpRwJjutKBwG9yAfwFDU1UNlQvp7Dn4UzQ75d+DDKm5EIkhufCypOsbyZapX5e9nS7bF2V0yu9ZXHSeuvAYEuJuEQT3W7RcJlsUc58mAQuGnRYunh2IzLt93VCYgRIZr8LnLc4+b wUO3tucm269Hl9Ifrrt8NEWVl353Ca7NMPZ5WCzMUmtGbvAw3/BWXRuMstt601ijkIFXnX1Ocl6EtQf15uOk6DuM7u6Zb08Cxb2ro+MIvluBqZi0USeP4x4d1ojue7x3sRDQaHhNVdMJ45P2VII1ZksdfW1U8DFXYPa1Jp1qdZTR4QG2i97mdVW+56mM43QBft5NM3kpyypToUq/qEN4hVsBwku7IpRiCnrjZgZ/370eN+SU/n7VYEQIA+WUmXtHDfPOIDK4GJaTO2AIUM95wX0A0CHgkT2XfJBt6C0bINKMzND4QVqNSFupdUZphzczk/3WyQX5f/FEPPUje4oVA8wlCTy+uHqMJFXzonDVC2ofhWn4uwhKcfdtWhCX1ZeDPBLYVH//GGxRZfUVm0FbvHDjDFc8h/uEJtnA8X84KEZMVcZVKxu3uHFYRHobkXbD88XC/OEPqq6z5wbeY22ppgYaZeyY0R8F3d1tIuaGBEOscrQRP+unqaUB22TbGEIgMnc6ggHwBUw8AB5fEDNF7osaZ0QZNW1Cd6M7ucaZuSwfj+zyCUP9+UfrKhCVo7QSp9pJOzd2se8wjUVUNN1xKnH+gRC2gxnm40fsB71ZKZAOQ4QjEd67ZBzDNUyFiocyryh3089jUuWuqGHwDiBfqolM1yMiTvYdj89DJ7iHGW1s+N3nd4/qaYulC0xsA5iP+WPCTFStrIbDOqai5a9BP9ODF5yqvJVQQhtjTUdQXTgSWXng2PgqdEuJIKq7rV1buiDQz78k/h2j+gmRH42TncmsOs8s94ZeXc5K2qtwNWmhdJsl1iq4PxiNAwVPjmEQMXMw3FHlQc2LsWFXjXRsUEbVvxeB0QJv7Jy7tW+lk2ugQPQ6xKMRBmg11D8hfN0m/XHCqSxhhy3LgNA463B9vLldAQSxcZr1U/2CrSmDsK7gwHc46eKZn5oEg8+g3JaOX3s0emPyVMN8wSo0OpVWV9sMHX1ecEfM5T9dy wmiCzHprMn9bNymlsz3VcY91V3vUCtNVl1xy1rsTtilP0HEfG24JBXyri1ZDTcQ39YEG2JhfM1G815JT211KI5jny6+LrH6QqiEBwXbn6hPkIN3X/E/l1otq0fV1FSffO+4j1+E/u83I0IPxwZmHzJUjUQig1s8IGVrkauttP2kOBzo2truwhvfvbc5xXww11gVlWbn5gKG0FTXDRDs4oVTl60GTsHHPS1QS5skdFcBJXXxiw5Sii/44AmbwVN/eTOQZ7rs2Y8eZYAjcfImT7+zUAcMqEcXlpy3+a4Nai8AIF0dtYBWltJSRzyhgldNMHBKFVawQIcmg0+dIupsMAAAAAAAAAAAAA Horde_ActiveSync-2.31.1/test/Horde/ActiveSync/fixtures/fixture_fetch0000664000076500000240000010657012654565405022457 0ustar TzozMToiSG9yZGVfSW1hcF9DbGllbnRfRmV0Y2hfUmVzdWx0cyI6Mzp7czo4OiIAKgBfZGF0YSI7YToxOntpOjI2NDAxMjtPOjI4OiJIb3JkZV9JbWFwX0NsaWVudF9EYXRhX0ZldGNoIjoxOntzOjg6IgAqAF9kYXRhIjthOjQ6e2k6MTQ7aToxNTtpOjEzO2k6MjY0MDEyO2k6NzthOjE6e2k6MTtpOjI2ODQ0O31pOjY7YToxOntpOjE7YToyOntzOjE6ImQiO3M6NDoiOGJpdCI7czoxOiJ0IjtzOjI2ODQ0OiI8IURPQ1RZUEUgaHRtbCBQVUJMSUMgIi0vL1czQy8vRFREIFhIVE1MIDEuMCBUcmFuc2l0aW9uYWwvL0VOIiANCiJodHRwOi8vd3d3LnczLm9yZy9UUi94aHRtbDEvRFREL3hodG1sMS10cmFuc2l0aW9uYWwuZHRkIj4NCjxodG1sIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIj4NCjxoZWFkPg0KPG1ldGEgaHR0cC1lcXVpdj0iQ29udGVudC1UeXBlIiBjb250ZW50PSJ0ZXh0L2h0bWw7IGNoYXJzZXQ9VVRGLTgiIC8+DQo8bWV0YSBuYW1lPSJ2aWV3cG9ydCIgY29udGVudD0id2lkdGg9ZGV2aWNlLXdpZHRoLCBpbml0aWFsLXNjYWxlPTEsIA0KbWF4aW11bS1zY2FsZT0xIiAvPg0KPHRpdGxlPjwvdGl0bGU+DQo8L2hlYWQ+DQoNCjxib2R5IGJnY29sb3I9IiNlOGVhZWEiPg0KPHRhYmxlIGNlbGxzcGFjaW5nPSIwIiBhbGlnbj0iY2VudGVyIiBiZ2NvbG9yPSIjZThlYWVhIiBib3JkZXI9IjAiIA0KY2VsbHBhZGRpbmc9IjAiIHdpZHRoPSIxMDAlIj4NCiAgPHRib2R5Pjx0cj4NCiAgICA8dGQgYWxpZ249ImNlbnRlciI+PHRhYmxlIGNlbGxzcGFjaW5nPSIwIiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiANCndpZHRoPSI2NjAiPg0KICAgICAgPHRib2R5Pjx0cj4NCiAgICAgICAgPHRkIGFsaWduPSJsZWZ0Ij48YSBocmVmPSIjIj48aW1nIA0Kc3JjPSJodHRwOi8vZi5lbWFpbC5zb255ZW50ZXJ0YWlubWVudG5ldHdvcmsuY29tL2kvMzgvMjA4OTA5MjE4Ny8yMDE0MDIyN19zb255X3RyYW5zYWN0aW9uYWxfc2VuX2xvZ28uZ2lmIiANCmFsdD0iU29ueSBFbnRlcnRhaW5tZW50IA0KTmV0d29yayIgc3R5bGU9ImRpc3BsYXk6YmxvY2s7IiBoZWlnaHQ9IjExMSIgYm9yZGVyPSIwIiANCndpZHRoPSIyMjAiPjwvYT48L3RkPg0KICAgICAgPC90cj4NCiAgICA8L3Rib2R5PjwvdGFibGU+DQogICAgICA8dGFibGUgY2VsbHNwYWNpbmc9IjAiIGJnY29sb3I9IiMzMDcxYTMiIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIA0Kd2lkdGg9IjY2MCI+DQogICAgICAgIDx0Ym9keT48dHI+DQogICAgICAgICAgPHRkIGhlaWdodD0iNDAiIHdpZHRoPSIyMCI+Jm5ic3A7PC90ZD4NCiAgICAgICAgICA8dGQgYWxpZ249ImxlZnQiPjxmb250IHN0eWxlPSJmb250LWZhbWlseTpBcmlhbCwgSGVsdmV0aWNhLCANCnNhbnMtc2VyaWY7IGZvbnQtc2l6ZToxOHB4OyANCmNvbG9yOiNmZmZmZmY7Ij5UcmFuc2FjdGlvbiBSZWNlaXB0PC9mb250PjwvdGQ+DQogICAgICAgIDwvdHI+DQogICAgPC90Ym9keT48L3RhYmxlPg0KICAgICAgPHRhYmxlIGNlbGxzcGFjaW5nPSIwIiBiZ2NvbG9yPSIjZmZmZmZmIiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiANCndpZHRoPSI2NjAiPg0KICAgICAgICA8dGJvZHk+PHRyPg0KICAgICAgICAgIDx0ZCB3aWR0aD0iMTkiPiZuYnNwOzwvdGQ+DQogICAgICAgICAgPHRkIGFsaWduPSJsZWZ0Ij48dGFibGUgY2VsbHNwYWNpbmc9IjAiIGJvcmRlcj0iMCIgDQpjZWxscGFkZGluZz0iMCI+DQogICAgICAgICAgICA8dGJvZHk+PHRyPg0KICAgICAgICAgICAgICA8dGQgaGVpZ2h0PSIxOSI+Jm5ic3A7PC90ZD4NCiAgICAgICAgICAgIDwvdHI+DQogICAgICAgICAgICA8dHI+DQogICAgICAgICAgICAgIDx0ZCBhbGlnbj0ibGVmdCI+DQo8Zm9udCBzdHlsZT0iZm9udC1mYW1pbHk6QXJpYWwsIEhlbHZldGljYSwgc2Fucy1zZXJpZjsgZm9udC1zaXplOjEzcHg7IA0KY29sb3I6IzU1NTU1NTsiPg0KDQpEZWFyIEpvcmR5biwgPGJyPjxicj5UaGFuayB5b3UgZm9yIHlvdXIgUGxheVN0YXRpb24mIzE3NDtTdG9yZSBwdXJjaGFzZS48QlI+PEJSPkEgcmVjZWlwdCBvZiB5b3VyIHB1cmNoYXNlIGlzIGJlbG93LiBCZSBzdXJlIHRvIGtlZXAgaXQgaW4gYSBzYWZlIHBsYWNlIGZvciBmdXR1cmUgcmVmZXJlbmNlLjxicj48YnI+VGhhbmsgeW91LDxicj48Zm9udCBjb2xvcj0iIzAwNzE5ZiIgc3R5bGU9ImZvbnQtd2VpZ2h0OmJvbGQiPlRoZSBTb255IEVudGVydGFpbm1lbnQgTmV0d29yayBUZWFtIDwvZm9udD48L2ZvbnQ+PGJyPjwvdGQ+DQogICAgICAgICAgICA8L3RyPg0KICAgICAgICAgICAgPHRyPg0KICAgICAgICAgICAgICA8dGQgaGVpZ2h0PSIxOSI+Jm5ic3A7PC90ZD4NCiAgICAgICAgICAgIDwvdHI+DQogICAgICAgICAgPC90Ym9keT48L3RhYmxlPjwvdGQ+DQogICAgICAgICAgPHRkIHdpZHRoPSIxOSI+Jm5ic3A7PC90ZD4NCiAgICAgICAgPC90cj4NCiAgICA8L3Rib2R5PjwvdGFibGU+DQogICAgIA0KICAgICAgPHRhYmxlIGNlbGxzcGFjaW5nPSIwIiBiZ2NvbG9yPSIjZmZmZmZmIiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiANCndpZHRoPSI2NjAiPg0KICAgICAgICA8dGJvZHk+PHRyPg0KICAgICAgICAgIDx0ZCB3aWR0aD0iMjAiPiZuYnNwOzwvdGQ+DQogICAgICAgICAgPHRkPg0KICAgICAgICAgIDxicj48dGFibGUgd2lkdGg9IjYyMiIgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiPjx0cj48dGQgYWxpZ249ImxlZnQiIGJnY29sb3I9IiNFMUUxRTEiPjx0YWJsZSB3aWR0aD0iNjIyIiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCI+PHRyPjx0ZD48aW1nIHNyYz0iaHR0cDovL2YuZW1haWwuc29ueWVudGVydGFpbm1lbnRuZXR3b3JrLmNvbS9pLzM4LzIwODkwOTIxODcvMDQxOTEyX1NFTl9zcGFjZXI2LmdpZiIgd2lkdGg9IjEiIGhlaWdodD0iMTAiIGhzcGFjZT0iMCIgdnNwYWNlPSIwIiBib3JkZXI9IjAiIHN0eWxlPSJkaXNwbGF5OmJsb2NrIj48L3RkPjwvdHI+PC90YWJsZT48dGFibGUgd2lkdGg9IjYyMiIgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiPjx0cj48dGQgd2lkdGg9IjEwIiBiZ2NvbG9yPSIjRTFFMUUxIj48aW1nIHNyYz0iaHR0cDovL2YuZW1haWwuc29ueWVudGVydGFpbm1lbnRuZXR3b3JrLmNvbS9pLzM4LzIwODkwOTIxODcvMDQxOTEyX1NFTl9zcGFjZXI2LmdpZiIgd2lkdGg9IjEwIiBoZWlnaHQ9IjIiIGhzcGFjZT0iMCIgdnNwYWNlPSIwIiBib3JkZXI9IjAiIHN0eWxlPSJkaXNwbGF5OmJsb2NrIj48L3RkPjx0ZCB3aWR0aD0iNjAyIiBhbGlnbj0ibGVmdCIgdmFsaWduPSJ0b3AiPjx0YWJsZSB3aWR0aD0iNjAyIiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCI+PHRyPjx0ZCB3aWR0aD0iMTUwIiBhbGlnbj0ibGVmdCI+PGZvbnQgY29sb3I9IiMzMzMzMzMiIGZhY2U9IkFyaWFsLCBIZWx2ZXRpY2EsIHNhbnMtc2VyaWYiIHN0eWxlPSJmb250LXNpemU6MTJweCI+T25saW5lIElEOiA8L2ZvbnQ+PC90ZD48dGQgd2lkdGg9IjEwIiBiZ2NvbG9yPSIjRTFFMUUxIj48aW1nIHNyYz0iaHR0cDovL2YuZW1haWwuc29ueWVudGVydGFpbm1lbnRuZXR3b3JrLmNvbS9pLzM4LzIwODkwOTIxODcvMDQxOTEyX1NFTl9zcGFjZXI2LmdpZiIgd2lkdGg9IjEwIiBoZWlnaHQ9IjIiIGhzcGFjZT0iMCIgdnNwYWNlPSIwIiBib3JkZXI9IjAiIHN0eWxlPSJkaXNwbGF5OmJsb2NrIj48L3RkPjx0ZD48Zm9udCBjb2xvcj0iIzAwMDAwMCIgZmFjZT0iQXJpYWwsIEhlbHZldGljYSwgc2Fucy1zZXJpZiIgc3R5bGU9ImZvbnQtc2l6ZToxMnB4OyBmb250LXdlaWdodDpib2xkIj5scHMtZ29waGVyaXQ8L2ZvbnQ+PC90ZD48L3RyPjwvdGFibGU+PHRhYmxlIHdpZHRoPSI2MDIiIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIj48dHI+PHRkIHdpZHRoPSIxNTAiIGFsaWduPSJsZWZ0Ij48Zm9udCBjb2xvcj0iIzMzMzMzMyIgZmFjZT0iQXJpYWwsIEhlbHZldGljYSwgc2Fucy1zZXJpZiIgc3R5bGU9ImZvbnQtc2l6ZToxMnB4Ij5PcmRlciBOdW1iZXI6IDwvZm9udD48L3RkPjx0ZCB3aWR0aD0iMTAiIGJnY29sb3I9IiNFMUUxRTEiPjxpbWcgc3JjPSJodHRwOi8vZi5lbWFpbC5zb255ZW50ZXJ0YWlubWVudG5ldHdvcmsuY29tL2kvMzgvMjA4OTA5MjE4Ny8wNDE5MTJfU0VOX3NwYWNlcjYuZ2lmIiB3aWR0aD0iMTAiIGhlaWdodD0iMiIgaHNwYWNlPSIwIiB2c3BhY2U9IjAiIGJvcmRlcj0iMCIgc3R5bGU9ImRpc3BsYXk6YmxvY2siPjwvdGQ+PHRkPjxmb250IGNvbG9yPSIjMDAwMDAwIiBmYWNlPSJBcmlhbCwgSGVsdmV0aWNhLCBzYW5zLXNlcmlmIiBzdHlsZT0iZm9udC1zaXplOjEycHg7IGZvbnQtd2VpZ2h0OmJvbGQiPjc1MTY3MDY5NjE8L2ZvbnQ+PC90ZD48L3RyPjwvdGFibGU+PHRhYmxlIHdpZHRoPSI2MDIiIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIj48dHI+IDx0ZD48aW1nIHNyYz0iaHR0cDovL2YuZW1haWwuc29ueWVudGVydGFpbm1lbnRuZXR3b3JrLmNvbS9pLzM4LzIwODkwOTIxODcvMDQxOTEyX1NFTl9zcGFjZXI2LmdpZiIgd2lkdGg9IjEiIGhlaWdodD0iMTAiIGhzcGFjZT0iMCIgdnNwYWNlPSIwIiBib3JkZXI9IjAiIHN0eWxlPSJkaXNwbGF5OmJsb2NrIj48L3RkPjwvdHI+PC90YWJsZT48L3RkPjx0ZCB3aWR0aD0iMTAiPjxpbWcgc3JjPSJodHRwOi8vZi5lbWFpbC5zb255ZW50ZXJ0YWlubWVudG5ldHdvcmsuY29tL2kvMzgvMjA4OTA5MjE4Ny8wNDE5MTJfU0VOX3NwYWNlcjYuZ2lmIiB3aWR0aD0iMTAiIGhlaWdodD0iMiIgaHNwYWNlPSIwIiB2c3BhY2U9IjAiIGJvcmRlcj0iMCIgc3R5bGU9ImRpc3BsYXk6YmxvY2siPjwvdGQ+PC90cj48L3RhYmxlPjwvdGFibGU+PGJyPg0KICAgICAgICAgICAgDQogICAgICAgICAgICA8dGFibGUgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiIHN0eWxlPSJ3aWR0aDogNjIycHgiPiA8dGJvZHk+PHRyPiA8dGQ+PHRhYmxlIHdpZHRoPSI2MjIiIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIj4gPHRib2R5Pjx0cj4gPHRkIGFsaWduPSJsZWZ0IiB2YWxpZ249InRvcCI+IDx0YWJsZSB3aWR0aD0iNjIyIiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCI+IDx0Ym9keT48dHI+IDx0ZCB3aWR0aD0iMTAiIGJnY29sb3I9IiMyNTI1MjUiPjxpbWcgc3JjPSJodHRwOi8vZi5lbWFpbC5zb255ZW50ZXJ0YWlubWVudG5ldHdvcmsuY29tL2kvMzgvMjA4OTA5MjE4Ny8xMjA0MzBfU0VOX1RYTl9TRU5fc3BhY2VyNzkuZ2lmIiB3aWR0aD0iMTAiIGhlaWdodD0iMSIgaHNwYWNlPSIwIiB2c3BhY2U9IjAiIGJvcmRlcj0iMCIgc3R5bGU9ImRpc3BsYXk6YmxvY2siPjwvdGQ+IDx0ZCB3aWR0aD0iNjAyIiBhbGlnbj0ibGVmdCIgdmFsaWduPSJ0b3AiPjx0YWJsZSB3aWR0aD0iNjEyIiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCI+IDx0Ym9keT48dHI+PHRkIGFsaWduPSJsZWZ0IiBiZ2NvbG9yPSIjMjUyNTI1IiB2YWxpZ249InRvcCIgc3R5bGU9IndpZHRoOiAyOTguNXB4Ij48dGFibGUgd2lkdGg9IjE3NSIgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiPiA8dGJvZHk+PHRyPiA8dGQgYmdjb2xvcj0iIzI1MjUyNSI+PGltZyBzcmM9Imh0dHA6Ly9mLmVtYWlsLnNvbnllbnRlcnRhaW5tZW50bmV0d29yay5jb20vaS8zOC8yMDg5MDkyMTg3LzEyMDQzMF9TRU5fVFhOX1NFTl9zcGFjZXI3OS5naWYiIHdpZHRoPSIxIiBoZWlnaHQ9IjEwIiBoc3BhY2U9IjAiIHZzcGFjZT0iMCIgYm9yZGVyPSIwIiBzdHlsZT0iZGlzcGxheTpibG9jayI+PC90ZD4gPC90cj4gPC90Ym9keT48L3RhYmxlPiA8dGFibGUgd2lkdGg9IjE3NSIgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiPiA8dGJvZHk+PHRyPiA8dGQgd2lkdGg9IjE3NSIgYWxpZ249ImxlZnQiIGJnY29sb3I9IiMyNTI1MjUiPjxmb250IGNvbG9yPSIjZmZmZmZmIiBmYWNlPSJBcmlhbCwgSGVsdmV0aWNhLCBzYW5zLXNlcmlmIiBzdHlsZT0iZm9udC1zaXplOjEycHg7IGZvbnQtd2VpZ2h0OmJvbGQiPkRhdGUgUHVyY2hhc2VkIDwvZm9udD48L3RkPiA8L3RyPiA8L3Rib2R5PjwvdGFibGU+IDx0YWJsZSB3aWR0aD0iMjAwIiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCI+IDx0Ym9keT48dHI+IDx0ZCBiZ2NvbG9yPSIjMjUyNTI1Ij48aW1nIHNyYz0iaHR0cDovL2YuZW1haWwuc29ueWVudGVydGFpbm1lbnRuZXR3b3JrLmNvbS9pLzM4LzIwODkwOTIxODcvMTIwNDMwX1NFTl9UWE5fU0VOX3NwYWNlcjc5LmdpZiIgd2lkdGg9IjEiIGhlaWdodD0iMTAiIGhzcGFjZT0iMCIgdnNwYWNlPSIwIiBib3JkZXI9IjAiIHN0eWxlPSJkaXNwbGF5OmJsb2NrIj48L3RkPiA8L3RyPiA8L3Rib2R5PjwvdGFibGU+PC90ZD4gPHRkIHdpZHRoPSI1IiBiZ2NvbG9yPSIjRkZGRkZGIj48aW1nIHNyYz0iaHR0cDovL2YuZW1haWwuc29ueWVudGVydGFpbm1lbnRuZXR3b3JrLmNvbS9pLzQ4LzIwODg1ODM4NzQvMTIwNDMwX1NFTl9UWE5fU0VOX3NwYWNlcjEuZ2lmIiB3aWR0aD0iNSIgaGVpZ2h0PSIyIiBoc3BhY2U9IjAiIHZzcGFjZT0iMCIgYm9yZGVyPSIwIiBzdHlsZT0iZGlzcGxheTpibG9jayI+PC90ZD4gPHRkIHdpZHRoPSIxMCIgYmdjb2xvcj0iIzI1MjUyNSI+PGltZyBzcmM9Imh0dHA6Ly9mLmVtYWlsLnNvbnllbnRlcnRhaW5tZW50bmV0d29yay5jb20vaS8zOC8yMDg5MDkyMTg3LzEyMDQzMF9TRU5fVFhOX1NFTl9zcGFjZXI3OS5naWYiIHdpZHRoPSI1IiBoZWlnaHQ9IjIiIGhzcGFjZT0iMCIgdnNwYWNlPSIwIiBib3JkZXI9IjAiIHN0eWxlPSJkaXNwbGF5OmJsb2NrIj48L3RkPiA8dGQgYWxpZ249ImxlZnQiIGJnY29sb3I9IiMyNTI1MjUiIHZhbGlnbj0idG9wIiBzdHlsZT0id2lkdGg6IDI5OC41cHgiPjx0YWJsZSB3aWR0aD0iMTc1IiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCI+IDx0Ym9keT48dHI+IDx0ZCBiZ2NvbG9yPSIjMjUyNTI1Ij48aW1nIHNyYz0iaHR0cDovL2YuZW1haWwuc29ueWVudGVydGFpbm1lbnRuZXR3b3JrLmNvbS9pLzM4LzIwODkwOTIxODcvMTIwNDMwX1NFTl9UWE5fU0VOX3NwYWNlcjc5LmdpZiIgd2lkdGg9IjEiIGhlaWdodD0iMTAiIGhzcGFjZT0iMCIgdnNwYWNlPSIwIiBib3JkZXI9IjAiIHN0eWxlPSJkaXNwbGF5OmJsb2NrIj48L3RkPiA8L3RyPiA8L3Rib2R5PjwvdGFibGU+IDx0YWJsZSB3aWR0aD0iMTc1IiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCI+IDx0Ym9keT48dHI+IDx0ZCB3aWR0aD0iMTc1IiBhbGlnbj0ibGVmdCIgYmdjb2xvcj0iIzI1MjUyNSI+PGZvbnQgY29sb3I9IiNmZmZmZmYiIGZhY2U9IkFyaWFsLCBIZWx2ZXRpY2EsIHNhbnMtc2VyaWYiIHN0eWxlPSJmb250LXNpemU6MTJweDsgZm9udC13ZWlnaHQ6Ym9sZCI+VG90YWw8L2ZvbnQ+PC90ZD4gPC90cj4gPC90Ym9keT48L3RhYmxlPiA8dGFibGUgd2lkdGg9IjIwMCIgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiPiA8dGJvZHk+PHRyPiA8dGQgYmdjb2xvcj0iIzI1MjUyNSI+PGltZyBzcmM9Imh0dHA6Ly9mLmVtYWlsLnNvbnllbnRlcnRhaW5tZW50bmV0d29yay5jb20vaS8zOC8yMDg5MDkyMTg3LzEyMDQzMF9TRU5fVFhOX1NFTl9zcGFjZXI3OS5naWYiIHdpZHRoPSIxIiBoZWlnaHQ9IjEwIiBoc3BhY2U9IjAiIHZzcGFjZT0iMCIgYm9yZGVyPSIwIiBzdHlsZT0iZGlzcGxheTpibG9jayI+PC90ZD4gPC90cj4gPC90Ym9keT48L3RhYmxlPjwvdGQ+IDwvdHI+IDwvdGJvZHk+PC90YWJsZT48L3RkPiA8L3RyPiA8L3Rib2R5PjwvdGFibGU+PC90ZD4gPC90cj4gPC90Ym9keT48L3RhYmxlPiA8L3RkPiA8L3RyPiA8L3Rib2R5PjwvdGFibGU+PHRhYmxlIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIiBzdHlsZT0id2lkdGg6IDYyMnB4Ij4gPHRib2R5Pjx0cj4gPHRkPjx0YWJsZSB3aWR0aD0iNjIyIiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCI+IDx0Ym9keT48dHI+IDx0ZCBhbGlnbj0ibGVmdCIgdmFsaWduPSJ0b3AiPiA8dGFibGUgd2lkdGg9IjYyMiIgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiPiA8dGJvZHk+PHRyPiA8dGQgd2lkdGg9IjEwIiBiZ2NvbG9yPSIjRTFFMUUxIj48aW1nIHNyYz0iaHR0cDovL2YuZW1haWwuc29ueWVudGVydGFpbm1lbnRuZXR3b3JrLmNvbS9pLzQ4LzIwODg1ODM4NzQvMTIwNDMwX1NFTl9UWE5fU0VOX3NwYWNlcjYuZ2lmIiB3aWR0aD0iMTAiIGhlaWdodD0iMSIgaHNwYWNlPSIwIiB2c3BhY2U9IjAiIGJvcmRlcj0iMCIgc3R5bGU9ImRpc3BsYXk6YmxvY2siPjwvdGQ+IDx0ZCB3aWR0aD0iNjAyIiBhbGlnbj0ibGVmdCIgdmFsaWduPSJ0b3AiPjx0YWJsZSB3aWR0aD0iNjEyIiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCI+IDx0Ym9keT48dHI+PHRkIGFsaWduPSJsZWZ0IiBiZ2NvbG9yPSIjRTFFMUUxIiB2YWxpZ249InRvcCIgc3R5bGU9IndpZHRoOiAyOTguNXB4Ij48dGFibGUgd2lkdGg9IjE3NSIgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiPiA8dGJvZHk+PHRyPiA8dGQgYmdjb2xvcj0iI0UxRTFFMSI+PGltZyBzcmM9Imh0dHA6Ly9mLmVtYWlsLnNvbnllbnRlcnRhaW5tZW50bmV0d29yay5jb20vaS80OC8yMDg4NTgzODc0LzEyMDQzMF9TRU5fVFhOX1NFTl9zcGFjZXI2LmdpZiIgd2lkdGg9IjEiIGhlaWdodD0iMTAiIGhzcGFjZT0iMCIgdnNwYWNlPSIwIiBib3JkZXI9IjAiIHN0eWxlPSJkaXNwbGF5OmJsb2NrIj48L3RkPiA8L3RyPiA8L3Rib2R5PjwvdGFibGU+IDx0YWJsZSB3aWR0aD0iMTc1IiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCI+IDx0Ym9keT48dHI+IDx0ZCB3aWR0aD0iMTc1IiBhbGlnbj0ibGVmdCIgYmdjb2xvcj0iI0UxRTFFMSI+PGZvbnQgY29sb3I9IiMwMDAwMDAiIGZhY2U9IkFyaWFsLCBIZWx2ZXRpY2EsIHNhbnMtc2VyaWYiIHN0eWxlPSJmb250LXNpemU6MTJweDsiPjEyLzIyLzIwMTQgQCAxMjo0NyBQTTwvZm9udD48L3RkPiA8L3RyPiA8L3Rib2R5PjwvdGFibGU+IDx0YWJsZSB3aWR0aD0iMjAwIiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCI+IDx0Ym9keT48dHI+IDx0ZCBiZ2NvbG9yPSIjRTFFMUUxIj48aW1nIHNyYz0iaHR0cDovL2YuZW1haWwuc29ueWVudGVydGFpbm1lbnRuZXR3b3JrLmNvbS9pLzQ4LzIwODg1ODM4NzQvMTIwNDMwX1NFTl9UWE5fU0VOX3NwYWNlcjYuZ2lmIiB3aWR0aD0iMSIgaGVpZ2h0PSIxMCIgaHNwYWNlPSIwIiB2c3BhY2U9IjAiIGJvcmRlcj0iMCIgc3R5bGU9ImRpc3BsYXk6YmxvY2siPjwvdGQ+IDwvdHI+IDwvdGJvZHk+PC90YWJsZT48L3RkPiA8dGQgd2lkdGg9IjUiIGJnY29sb3I9IiNGRkZGRkYiPjxpbWcgc3JjPSJodHRwOi8vZi5lbWFpbC5zb255ZW50ZXJ0YWlubWVudG5ldHdvcmsuY29tL2kvNDgvMjA4ODU4Mzg3NC8xMjA0MzBfU0VOX1RYTl9TRU5fc3BhY2VyMS5naWYiIHdpZHRoPSI1IiBoZWlnaHQ9IjIiIGhzcGFjZT0iMCIgdnNwYWNlPSIwIiBib3JkZXI9IjAiIHN0eWxlPSJkaXNwbGF5OmJsb2NrIj48L3RkPiA8dGQgd2lkdGg9IjEwIiBiZ2NvbG9yPSIjRTFFMUUxIj48aW1nIHNyYz0iaHR0cDovL2YuZW1haWwuc29ueWVudGVydGFpbm1lbnRuZXR3b3JrLmNvbS9pLzQ4LzIwODg1ODM4NzQvMTIwNDMwX1NFTl9UWE5fU0VOX3NwYWNlcjYuZ2lmIiB3aWR0aD0iNSIgaGVpZ2h0PSIyIiBoc3BhY2U9IjAiIHZzcGFjZT0iMCIgYm9yZGVyPSIwIiBzdHlsZT0iZGlzcGxheTpibG9jayI+PC90ZD4gPHRkIGFsaWduPSJsZWZ0IiBiZ2NvbG9yPSIjRTFFMUUxIiB2YWxpZ249InRvcCIgc3R5bGU9IndpZHRoOiAyOTguNXB4Ij48dGFibGUgd2lkdGg9IjE3NSIgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiPiA8dGJvZHk+PHRyPiA8dGQgYmdjb2xvcj0iI0UxRTFFMSI+PGltZyBzcmM9Imh0dHA6Ly9mLmVtYWlsLnNvbnllbnRlcnRhaW5tZW50bmV0d29yay5jb20vaS80OC8yMDg4NTgzODc0LzEyMDQzMF9TRU5fVFhOX1NFTl9zcGFjZXI2LmdpZiIgd2lkdGg9IjEiIGhlaWdodD0iMTAiIGhzcGFjZT0iMCIgdnNwYWNlPSIwIiBib3JkZXI9IjAiIHN0eWxlPSJkaXNwbGF5OmJsb2NrIj48L3RkPiA8L3RyPiA8L3Rib2R5PjwvdGFibGU+IDx0YWJsZSB3aWR0aD0iMTc1IiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCI+IDx0Ym9keT48dHI+IDx0ZCB3aWR0aD0iMTc1IiBhbGlnbj0ibGVmdCIgdGQgdmFsaWduPSJ0b3AiIGJnY29sb3I9IiNFMUUxRTEiPjxmb250IGNvbG9yPSIjMDAwMDAwIiBmYWNlPSJBcmlhbCwgSGVsdmV0aWNhLCBzYW5zLXNlcmlmIiBzdHlsZT0iZm9udC1zaXplOjEycHg7Ij4kMC4wMDwvZm9udD48L3RkPiA8L3RyPiA8L3Rib2R5PjwvdGFibGU+IDx0YWJsZSB3aWR0aD0iMjAwIiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCI+IDx0Ym9keT48dHI+IDx0ZCBiZ2NvbG9yPSIjRTFFMUUxIj48aW1nIHNyYz0iaHR0cDovL2YuZW1haWwuc29ueWVudGVydGFpbm1lbnRuZXR3b3JrLmNvbS9pLzQ4LzIwODg1ODM4NzQvMTIwNDMwX1NFTl9UWE5fU0VOX3NwYWNlcjYuZ2lmIiB3aWR0aD0iMSIgaGVpZ2h0PSIxMCIgaHNwYWNlPSIwIiB2c3BhY2U9IjAiIGJvcmRlcj0iMCIgc3R5bGU9ImRpc3BsYXk6YmxvY2siPjwvdGQ+IDwvdHI+IDwvdGJvZHk+PC90YWJsZT48L3RkPiA8L3RyPiA8L3Rib2R5PjwvdGFibGU+PC90ZD4gPC90cj4gPC90Ym9keT48L3RhYmxlPjwvdGQ+IDwvdHI+IDwvdGJvZHk+PC90YWJsZT4gPC90ZD4gPC90cj4gPC90Ym9keT48L3RhYmxlPg0KICAgICAgICAgICAgDQogICAgICAgICAgICA8dGFibGUgY2VsbHNwYWNpbmc9IjAiIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIA0Kd2lkdGg9IjYyMCI+DQogICAgICAgICAgICAgICAgICA8dGJvZHk+PHRyPg0KICAgICAgICAgICAgICAgICAgICA8dGQgaGVpZ2h0PSIyMCI+Jm5ic3A7PC90ZD4NCiAgICAgICAgICAgICAgICAgIDwvdHI+DQogICAgICAgICAgICAgICAgPC90Ym9keT48L3RhYmxlPg0KDQogICAgICAgICAgDQogICAgICAgICAgPC90ZD4NCiAgICAgICAgICA8dGQgd2lkdGg9IjIwIj4mbmJzcDs8L3RkPg0KICAgICAgICA8L3RyPg0KICAgIDwvdGJvZHk+PC90YWJsZT4NCiAgICAgDQogICAgICA8dGFibGUgY2VsbHNwYWNpbmc9IjAiIGJnY29sb3I9IiNmZmZmZmYiIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIA0Kd2lkdGg9IjY2MCI+DQogICAgICAgIDx0Ym9keT48dHI+DQogICAgICAgICAgPHRkIHdpZHRoPSIyMCI+Jm5ic3A7PC90ZD4NCiAgICAgICAgICA8dGQ+PHRhYmxlIGNlbGxzcGFjaW5nPSIwIiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIj4NCiAgICAgICAgICAgIDx0Ym9keT48dHI+DQogICAgICAgICAgICAgIDx0ZCBoZWlnaHQ9IjIwIj4mbmJzcDs8L3RkPg0KICAgICAgICAgICAgPC90cj4NCiAgICAgICAgICAgIDx0cj4NCiAgICAgICAgICAgICAgPHRkIGFsaWduPSJsZWZ0Ij4NCiAgICAgICAgICAgICAgICANCiAgICAgICAgICAgICAgICAgPHRhYmxlIHdpZHRoPSI2MjIiIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIj48dHI+PHRkIGFsaWduPSJsZWZ0IiB2YWxpZ249InRvcCI+PHRhYmxlIHdpZHRoPSI2MjIiIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIj48dHI+PHRkIHdpZHRoPSIxMCIgYmdjb2xvcj0iIzI1MjUyNSI+PGltZyBzcmM9Imh0dHA6Ly9mLmVtYWlsLnNvbnllbnRlcnRhaW5tZW50bmV0d29yay5jb20vaS8zOC8yMDg5MDkyMTg3LzEyMDQzMF9TRU5fVFhOX1NFTl9zcGFjZXI3OS5naWYiIHdpZHRoPSIxMCIgaGVpZ2h0PSIxIiBoc3BhY2U9IjAiIHZzcGFjZT0iMCIgYm9yZGVyPSIwIiBzdHlsZT0iZGlzcGxheTpibG9jayI+PC90ZD48dGQgd2lkdGg9IjYwMiIgYWxpZ249ImxlZnQiIHZhbGlnbj0idG9wIj48dGFibGUgd2lkdGg9IjYwMiIgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiPjx0cj48dGQgYWxpZ249ImxlZnQiIGJnY29sb3I9IiMyNTI1MjUiIHZhbGlnbj0idG9wIj48dGFibGUgd2lkdGg9IjYwMiIgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAic3R5bGU9IHRhYmxlLWxheW91dDpmaXhlZDs+PHRyPjx0ZCBiZ2NvbG9yPSIjMjUyNTI1Ij48aW1nIHNyYz0iaHR0cDovL2YuZW1haWwuc29ueWVudGVydGFpbm1lbnRuZXR3b3JrLmNvbS9pLzM4LzIwODkwOTIxODcvMTIwNDMwX1NFTl9UWE5fU0VOX3NwYWNlcjc5LmdpZiIgd2lkdGg9IjEiIGhlaWdodD0iMTAiIGhzcGFjZT0iMCIgdnNwYWNlPSIwIiBib3JkZXI9IjAiIHN0eWxlPSJkaXNwbGF5OmJsb2NrIj48L3RkPjwvdHI+IDwvdGFibGU+PHRhYmxlIHdpZHRoPSI2MDIiIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIj48dHI+PHRkIHdpZHRoPSIxIiB2YWxpZ249ImxlZnQiIGFsaWduPSJsZWZ0Ij48Zm9udCBjb2xvcj0iI2ZmZmZmZiIgZmFjZT0iQXJpYWwsIEhlbHZldGljYSwgc2Fucy1zZXJpZiIgc3R5bGU9ImZvbnQtc2l6ZToxMnB4OyI+PC9mb250PjwvdGQ+PHRkIGFsaWduPSJsZWZ0IiB3aWR0aD0iNTAwIiBiZ2NvbG9yPSIjMjUyNTI1Ij48Zm9udCBjb2xvcj0iI2ZmZmZmZiIgZmFjZT0iQXJpYWwsIEhlbHZldGljYSwgc2Fucy1zZXJpZiIgc3R5bGU9ImZvbnQtc2l6ZToxMnB4OyBmb250LXdlaWdodDpib2xkIj5EZXRhaWxzPC9mb250PjwvdGQ+PHRkIHdpZHRoPSIyMCIgYmdjb2xvcj0iIzI1MjUyNSI+PGltZyBzcmM9Imh0dHA6Ly9mLmVtYWlsLnNvbnllbnRlcnRhaW5tZW50bmV0d29yay5jb20vaS8zOC8yMDg5MDkyMTg3LzEyMDQzMF9TRU5fVFhOX1NFTl9zcGFjZXI3OS5naWYiIHdpZHRoPSIyMCIgaGVpZ2h0PSIyIiBoc3BhY2U9IjAiIHZzcGFjZT0iMCIgYm9yZGVyPSIwIiBzdHlsZT0iZGlzcGxheTpibG9jayI+PC90ZD48dGQgYWxpZ249ImxlZnQiIHdpZHRoPSI5MiIgYmdjb2xvcj0iIzI1MjUyNSI+PGZvbnQgY29sb3I9IiNmZmZmZmYiIGZhY2U9IkFyaWFsLCBIZWx2ZXRpY2EsIHNhbnMtc2VyaWYiIHN0eWxlPSJmb250LXNpemU6MTJweDsgZm9udC13ZWlnaHQ6Ym9sZCI+UHJpY2U8L2ZvbnQ+PC90ZD48L3RyPjwvdGFibGU+PHRhYmxlIHdpZHRoPSI2MDIiIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIj48dHI+PHRkIGJnY29sb3I9IiMyNTI1MjUiPjxpbWcgc3JjPSJodHRwOi8vZi5lbWFpbC5zb255ZW50ZXJ0YWlubWVudG5ldHdvcmsuY29tL2kvMzgvMjA4OTA5MjE4Ny8xMjA0MzBfU0VOX1RYTl9TRU5fc3BhY2VyNzkuZ2lmIiB3aWR0aD0iMSIgaGVpZ2h0PSIxMCIgaHNwYWNlPSIwIiB2c3BhY2U9IjAiIGJvcmRlcj0iMCIgc3R5bGU9ImRpc3BsYXk6YmxvY2siPjwvdGQ+PC90cj48L3RhYmxlPjwvdGQ+PC90cj48L3RhYmxlPjwvdGQ+PHRkIHdpZHRoPSIxMCIgYmdjb2xvcj0iIzI1MjUyNSI+PGltZyBzcmM9Imh0dHA6Ly9mLmVtYWlsLnNvbnllbnRlcnRhaW5tZW50bmV0d29yay5jb20vaS8zOC8yMDg5MDkyMTg3LzEyMDQzMF9TRU5fVFhOX1NFTl9zcGFjZXI3OS5naWYiIHdpZHRoPSIwIiBoZWlnaHQ9IjEiIGhzcGFjZT0iMCIgdnNwYWNlPSIwIiBib3JkZXI9IjAiIHN0eWxlPSJkaXNwbGF5OmJsb2NrIj48L3RkPjwvdHI+PC90YWJsZT48L3RhYmxlPiANCjx0YWJsZSB3aWR0aD0iNjIyIiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCI+PHRyPjx0ZCBhbGlnbj0ibGVmdCIgdmFsaWduPSJ0b3AiPjx0YWJsZSB3aWR0aD0iNjIyIiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCI+PHRyPjx0ZCB3aWR0aD0iMTAiIGJnY29sb3I9IiNFMUUxRTEiPjxpbWcgc3JjPSJodHRwOi8vZi5lbWFpbC5zb255ZW50ZXJ0YWlubWVudG5ldHdvcmsuY29tL2kvNDgvMjA4ODU4Mzg3NC8xMjA0MzBfU0VOX1RYTl9TRU5fc3BhY2VyNi5naWYiIHdpZHRoPSIxMCIgaGVpZ2h0PSIxIiBoc3BhY2U9IjAiIHZzcGFjZT0iMCIgYm9yZGVyPSIwIiBzdHlsZT0iZGlzcGxheTpibG9jayI+PC90ZD48dGQgd2lkdGg9IjYwMiIgYWxpZ249ImxlZnQiIHZhbGlnbj0idG9wIj48dGFibGUgd2lkdGg9IjYwMiIgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiPjx0cj48dGQgYWxpZ249ImxlZnQiIGJnY29sb3I9IiNFMUUxRTEiIHZhbGlnbj0idG9wIj48dGFibGUgd2lkdGg9IjYwMiIgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAic3R5bGU9IHRhYmxlLWxheW91dDpmaXhlZDs+PHRyPjx0ZCBiZ2NvbG9yPSIjRTFFMUUxIj48aW1nIHNyYz0iaHR0cDovL2YuZW1haWwuc29ueWVudGVydGFpbm1lbnRuZXR3b3JrLmNvbS9pLzQ4LzIwODg1ODM4NzQvMTIwNDMwX1NFTl9UWE5fU0VOX3NwYWNlcjYuZ2lmIiB3aWR0aD0iMSIgaGVpZ2h0PSIxMCIgaHNwYWNlPSIwIiB2c3BhY2U9IjAiIGJvcmRlcj0iMCIgc3R5bGU9ImRpc3BsYXk6YmxvY2siPjwvdGQ+PC90cj4gPC90YWJsZT48dGFibGUgd2lkdGg9IjYwMiIgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiPjx0cj48dGQgd2lkdGg9IjEiIHZhbGlnbj0ibGVmdCIgYWxpZ249ImxlZnQiPjxmb250IGNvbG9yPSIjMDAwMDAwIiBmYWNlPSJBcmlhbCwgSGVsdmV0aWNhLCBzYW5zLXNlcmlmIiBzdHlsZT0iZm9udC1zaXplOjEycHg7Ij48L2ZvbnQ+PC90ZD48dGQgYWxpZ249ImxlZnQiIHdpZHRoPSI1MDAiIGJnY29sb3I9IiNFMUUxRTEiPjxmb250IGNvbG9yPSIjMDAwMDAwIiBmYWNlPSJBcmlhbCwgSGVsdmV0aWNhLCBzYW5zLXNlcmlmIiBzdHlsZT0iZm9udC1zaXplOjEycHg7Ij5QUyBQbHVzOiAyLURheSBUcmlhbCAgIDwvZm9udD48L3RkPjx0ZCB3aWR0aD0iMjAiIGJnY29sb3I9IiNFMUUxRTEiPjxpbWcgc3JjPSJodHRwOi8vZi5lbWFpbC5zb255ZW50ZXJ0YWlubWVudG5ldHdvcmsuY29tL2kvNDgvMjA4ODU4Mzg3NC8xMjA0MzBfU0VOX1RYTl9TRU5fc3BhY2VyNi5naWYiIHdpZHRoPSIyMCIgaGVpZ2h0PSIyIiBoc3BhY2U9IjAiIHZzcGFjZT0iMCIgYm9yZGVyPSIwIiBzdHlsZT0iZGlzcGxheTpibG9jayI+PC90ZD48dGQgYWxpZ249ImxlZnQiIHZhbGlnbj0idG9wIiB3aWR0aD0iOTIiIGJnY29sb3I9IiNFMUUxRTEiPjxmb250IGNvbG9yPSIjMDAwMDAwIiBmYWNlPSJBcmlhbCwgSGVsdmV0aWNhLCBzYW5zLXNlcmlmIiBzdHlsZT0iZm9udC1zaXplOjEycHg7Ij4kMC4wMDwvZm9udD48L3RkPjwvdHI+PC90YWJsZT48dGFibGUgd2lkdGg9IjYwMiIgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiPjx0cj48dGQgYmdjb2xvcj0iI0UxRTFFMSI+PGltZyBzcmM9Imh0dHA6Ly9mLmVtYWlsLnNvbnllbnRlcnRhaW5tZW50bmV0d29yay5jb20vaS80OC8yMDg4NTgzODc0LzEyMDQzMF9TRU5fVFhOX1NFTl9zcGFjZXI2LmdpZiIgd2lkdGg9IjEiIGhlaWdodD0iMTAiIGhzcGFjZT0iMCIgdnNwYWNlPSIwIiBib3JkZXI9IjAiIHN0eWxlPSJkaXNwbGF5OmJsb2NrIj48L3RkPjwvdHI+PC90YWJsZT48L3RkPjwvdHI+PC90YWJsZT48L3RkPjx0ZCB3aWR0aD0iMTAiIGJnY29sb3I9IiNFMUUxRTEiPjxpbWcgc3JjPSJodHRwOi8vZi5lbWFpbC5zb255ZW50ZXJ0YWlubWVudG5ldHdvcmsuY29tL2kvNDgvMjA4ODU4Mzg3NC8xMjA0MzBfU0VOX1RYTl9TRU5fc3BhY2VyNi5naWYiIHdpZHRoPSIwIiBoZWlnaHQ9IjEiIGhzcGFjZT0iMCIgdnNwYWNlPSIwIiBib3JkZXI9IjAiIHN0eWxlPSJkaXNwbGF5OmJsb2NrIj48L3RkPjwvdHI+PC90YWJsZT48L3RhYmxlPiA8YnI+DQogICAgICAgICAgICAgICAgDQogICAgICAgICAgICAgICAgPHRhYmxlIGNlbGxzcGFjaW5nPSIwIiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIj4NCiAgICAgICAgICAgICAgICAgIDx0Ym9keT48dHI+DQogICAgICAgICAgICAgICAgICAgIDx0ZCBoZWlnaHQ9IjExIj48aW1nIA0Kc3JjPSJodHRwOi8vZi5lbWFpbC5zb255ZW50ZXJ0YWlubWVudG5ldHdvcmsuY29tL2kvMzgvMjA4OTA5MjE4Ny8yMDE0MDIyN19zb255X3RyYW5zYWN0aW9uYWxfc3Bjci5naWYiIA0Kc3R5bGU9ImRpc3BsYXk6YmxvY2s7IiBoZWlnaHQ9IjExIiBib3JkZXI9IjAiIHdpZHRoPSIxMCI+PC90ZD4NCiAgICAgICAgICAgICAgICAgIDwvdHI+DQogICAgICAgICAgICAgICAgICA8dHI+DQogICAgICAgICAgICAgICAgICAgIDx0ZCBhbGlnbj0ibGVmdCI+PGZvbnQgc3R5bGU9ImZvbnQtZmFtaWx5OkFyaWFsLCANCkhlbHZldGljYSwgc2Fucy1zZXJpZjsgZm9udC1zaXplOjExcHg7IGNvbG9yOiM1NTU1NTU7IA0KbGluZS1oZWlnaHQ6MThweDsiPjxzdHJvbmcgc3R5bGU9ImZvbnQtc2l6ZToxNHB4OyI+DQogICAgICAgICAgICAgICAgICAgICAgDQogPGZvbnQgY29sb3I9IiMwMDcxOWYiIHN0eWxlPSJmb250LWZhbWlseTogQXJpYWwsIEhlbHZldGljYSwgc2Fucy1zZXJpZjsgZm9udC1zaXplOiAxMnB4OyB0ZXh0LWFsaWduOiAtd2Via2l0LWxlZnQ7IGZvbnQtd2VpZ2h0OiBib2xkOyAiPlRvdGFsOiA8L2ZvbnQ+PHN0cm9uZz4kMC4wMDwvc3Ryb25nPjxicj4gDQogPC9zdHJvbmc+PC9mb250PiAgIA0KICAgICAgICAgICAgICAgICAgICAgIA0KPGZvbnQgc3R5bGU9ImZvbnQtZmFtaWx5OkFyaWFsLCBIZWx2ZXRpY2EsIHNhbnMtc2VyaWY7IGZvbnQtc2l6ZToxM3B4OyANCmNvbG9yOiM1NTU1NTU7Ij4NCiAgPC9mb250PiAgICAgICAgICAgICAgICAgICAgDQogICAgICAgICAgICAgICAgICAgICAgDQogICAgICAgICAgICAgICAgICAgICAgIA0KICAgICAgICAgICAgICAgICAgICAgIA0KPC90ZD4NCjwvdHI+DQo8L3Rib2R5PjwvdGFibGU+DQo8dGFibGUgY2VsbHNwYWNpbmc9IjAiIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIHdpZHRoPSI2MjAiPg0KPHRib2R5Pjx0cj4NCjx0ZCBoZWlnaHQ9IjIwIj4NCjx0YWJsZSB3aWR0aD0iNjIyIiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCI+PHRyPjx0ZCB3aWR0aD0iNjIyIiB2YWxpZ249InRvcCIgYmdjb2xvcj0iI0ZGRkZGRiI+PHRhYmxlIHdpZHRoPSI2MjIiIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIj48dHI+PHRkIGJnY29sb3I9IiNGRkZGRkYiPjxpbWcgc3JjPSJodHRwOi8vZi5lbWFpbC5zb255ZW50ZXJ0YWlubWVudG5ldHdvcmsuY29tL2kvNDgvMjA4ODU4Mzg3NC8xMjA0MzBfU0VOX1RYTl9TRU5fc3BhY2VyMS5naWYiIHdpZHRoPSIxIiBoZWlnaHQ9IjIwIiBoc3BhY2U9IjAiIHZzcGFjZT0iMCIgYm9yZGVyPSIwIiBzdHlsZT0iZGlzcGxheTpibG9jayI+PC90ZD48L3RyPjwvdGFibGU+PHRhYmxlIHdpZHRoPSIxMDAlIiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCIgYWxpZ249ImNlbnRlciI+PHRyPjx0ZCBhbGlnbj0iY2VudGVyIj48YSBocmVmPSJodHRwOi8vZW1haWwuc29ueWVudGVydGFpbm1lbnRuZXR3b3JrLmNvbS9hL2hCVWloUFhCOGhRUmJCOHZCTUdBQWFxUFMuQjhoUVJiV3Ivc2VuMTI5Ij48aW1nIHNyYz0iaHR0cDovL2YuZW1haWwuc29ueWVudGVydGFpbm1lbnRuZXR3b3JrLmNvbS9pLzM4LzIwODkwOTIxODcvUExBWV9PTl83MHg3MC5qcGciIHdpZHRoPSI3MCIgaGVpZ2h0PSI3MCIgaHNwYWNlPSIwIiB2c3BhY2U9IjAiIGJvcmRlcj0iMCIgc3R5bGU9ImRpc3BsYXk6YmxvY2siPjwvYT48L3RkPjx0ZCB3aWR0aD0iMTUiIGFsaWduPSJsZWZ0IiB2YWxpZ249InRvcCI+PC90ZD48dGQgYWxpZ249ImxlZnQiIHZhbGlnbj0idG9wIj48Zm9udCBjb2xvcj0iIzMzMzMzMyIgZmFjZT0iQXJpYWwsIEhlbHZldGljYSwgc2Fucy1zZXJpZiIgc3R5bGU9ImZvbnQtc2l6ZToxMnB4Ij48YnI+RWFybiBTb255IFJld2FyZHMgcG9pbnRzIGZvciBhbGwgeW91ciBQbGF5U3RhdGlvbiZyZWc7U3RvcmUgcHVyY2hhc2VzLiBQb2ludHMgY2FuIGJlIHJlZGVlbWVkIGZvciB0aGUgbGF0ZXN0IGdhbWVzLCBQbGF5U3RhdGlvbiZyZWc7U3RvcmUgY29kZXMgYW5kIG1vcmUuIE5vdCBhIG1lbWJlcj8gPGEgaHJlZj0iaHR0cDovL2VtYWlsLnNvbnllbnRlcnRhaW5tZW50bmV0d29yay5jb20vYS9oQlVpaFBYQjhoUVJiQjh2Qk1HQUFhcVBTLkI4aFFSYldyL3NlbjEyOSI+TGVhcm4gbW9yZTwvYT4gJiM2MjsmIzYyOzwvZm9udD48L3RkPjwvdHI+PC90YWJsZT48dGFibGUgd2lkdGg9IjYyMiIgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiPjwvdGFibGU+PC90ZD48L3RyPjwvdGFibGU+PHN0cm9uZz48L3N0cm9uZz48dGFibGUgd2lkdGg9IjYyMiIgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiPiA8dGJvZHk+PHRyPiA8dGQ+PGltZyBzcmM9Imh0dHA6Ly9mLmVtYWlsLnNvbnllbnRlcnRhaW5tZW50bmV0d29yay5jb20vaS80OC8yMDg4NTgzODc0LzEyMDQzMF9TRU5fVFhOX1NFTl9zcGFjZXIxLmdpZiIgd2lkdGg9IjEiIGhlaWdodD0iMjAiIGhzcGFjZT0iMCIgdnNwYWNlPSIwIiBib3JkZXI9IjAiIHN0eWxlPSJkaXNwbGF5OmJsb2NrIj48L3RkPiA8L3RyPiA8L3Rib2R5PjwvdGFibGU+PHRhYmxlIHdpZHRoPSI2MjIiIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwInN0eWxlPSB0YWJsZS1sYXlvdXQ6Zml4ZWQ7Pjx0Ym9keT48dHI+PHRkIGJnY29sb3I9IzRFNEU0RT48aW1nIHNyYz1odHRwOi8vZi5lbWFpbC5zb255ZW50ZXJ0YWlubWVudG5ldHdvcmsuY29tL2kvNDgvMjA4ODU4Mzg3NC8xMjA0MzBfU0VOX1RYTl9TRU5fc3BhY2VyMi5naWYgd2lkdGg9MSBoZWlnaHQ9MSBoc3BhY2U9MCB2c3BhY2U9MCBib3JkZXI9MCBzdHlsZT1kaXNwbGF5OmJsb2NrPjwvdGQ+PC90cj48L3Rib2R5PjwvdGFibGU+PGJyPjxmb250IGNvbG9yPSIjMDA3MTlmIiBmYWNlPSJBcmlhbCwgSGVsdmV0aWNhLCBzYW5zLXNlcmlmIiBzaXplPSIyIj48Yj5DaGVjayBvdXQgdGhlc2Ugb3RoZXIgZ3JlYXQgcHJvbW90aW9uczwvYj48L2ZvbnQ+PGJyPjxicj48dGFibGUgd2lkdGg9IjYyMiIgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiPjx0cj48dGQgd2lkdGg9IjYyMiIgdmFsaWduPSJ0b3AiIGJnY29sb3I9IiNGRkZGRkYiPjx0YWJsZSB3aWR0aD0iNjIyIiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCI+PHRyPjx0ZCBiZ2NvbG9yPSIjRkZGRkZGIj48aW1nIHNyYz0iaHR0cDovL2YuZW1haWwuc29ueWVudGVydGFpbm1lbnRuZXR3b3JrLmNvbS9pLzQ4LzIwODg1ODM4NzQvMTIwNDMwX1NFTl9UWE5fU0VOX3NwYWNlcjEuZ2lmIiB3aWR0aD0iMSIgaGVpZ2h0PSIxMCIgaHNwYWNlPSIwIiB2c3BhY2U9IjAiIGJvcmRlcj0iMCIgc3R5bGU9ImRpc3BsYXk6YmxvY2siPjwvdGQ+PC90cj48L3RhYmxlPjx0YWJsZSB3aWR0aD0iMTAwJSIgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiIGFsaWduPSJjZW50ZXIiPjx0cj48dGQgYWxpZ249ImNlbnRlciI+PGEgaHJlZj0iaHR0cDovL2VtYWlsLnNvbnllbnRlcnRhaW5tZW50bmV0d29yay5jb20vYS9oQlVpaFBYQjhoUVJiQjh2Qk1HQUFhcVBTLkI4aFFSYldyL3NlbjE2OSMhL2Rlc3RpbnktZGlnaXRhbC1ndWFyZGlhbi1lZGl0aW9uLS9jaWQ9U1RPUkUtTVNGNzcwMDgtOV9TVE9SRU5FV1NHUklEND9lbWNpZD1lbS1nYS0xNTk5OCI+PGltZyBzcmM9Imh0dHA6Ly9mLmVtYWlsLnNvbnllbnRlcnRhaW5tZW50bmV0d29yay5jb20vaS8zOC8yMDg5MDkyMTg3L0Rlc3RpbnlfREdFLmpwZyIgd2lkdGg9IjE1NSIgaGVpZ2h0PSIxNTUiIGhzcGFjZT0iMCIgdnNwYWNlPSIwIiBib3JkZXI9IjAiIHN0eWxlPSJkaXNwbGF5OmJsb2NrIj48L2E+PC90ZD48dGQgYWxpZ249ImNlbnRlciI+PGEgaHJlZj0iaHR0cDovL2VtYWlsLnNvbnllbnRlcnRhaW5tZW50bmV0d29yay5jb20vYS9oQlVpaFBYQjhoUVJiQjh2Qk1HQUFhcVBTLkI4aFFSYldyL3NlbjEwNiMhL2VuLXVzL2JlY29tZS1hLW1lbWJlci9jaWQ9U1RPUkUtTVNGNzcwMDgtUFNQTFVTTUVNQkVSP3V0bV9tZWRpdW09ZW1haWwmdXRtX3NvdXJjZT10eG4tcHVyY2gtY29uZiZ1dG1fY2FtcGFpZ249MTMwNDAyJmVtY2lkPUNISTAwMDIwMyI+PGltZyBzcmM9Imh0dHA6Ly9mLmVtYWlsLnNvbnllbnRlcnRhaW5tZW50bmV0d29yay5jb20vaS8zOC8yMDg5MDkyMTg3L1BTX3BsdXNfYS5qcGciIHdpZHRoPSIxNTUiIGhlaWdodD0iMTU1IiBoc3BhY2U9IjAiIHZzcGFjZT0iMCIgYm9yZGVyPSIwIiBzdHlsZT0iZGlzcGxheTpibG9jayI+PC9hPjwvdGQ+PHRkIGFsaWduPSJjZW50ZXIiPjxhIGhyZWY9Imh0dHA6Ly9lbWFpbC5zb255ZW50ZXJ0YWlubWVudG5ldHdvcmsuY29tL2EvaEJVaWhQWEI4aFFSYkI4dkJNR0FBYXFQUy5COGhRUmJXci9zZW4xNzEjIS9tb3ZpZXMvZ29kemlsbGEtKHBsdXMtYm9udXMtZmVhdHVyZXMpL2NpZD1VVjAwMDQtTlBWQTk2OTM1X0NOLTAwMDAwMDAwMDAyNDY0OTA/ZW1jaWQ9ZW0tdmktMTYwMDAiPjxpbWcgc3JjPSJodHRwOi8vZi5lbWFpbC5zb255ZW50ZXJ0YWlubWVudG5ldHdvcmsuY29tL2kvMzgvMjA4OTA5MjE4Ny9Hb2R6aWxsYS5qcGciIHdpZHRoPSIxNTUiIGhlaWdodD0iMTU1IiBoc3BhY2U9IjAiIHZzcGFjZT0iMCIgYm9yZGVyPSIwIiBzdHlsZT0iZGlzcGxheTpibG9jayI+PC9hPjwvdGQ+PC90cj48L3RhYmxlPjx0YWJsZSB3aWR0aD0iNjIyIiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCI+PHRyPjx0ZCBiZ2NvbG9yPSIjRkZGRkZGIj48aW1nIHNyYz0iaHR0cDovL2YuZW1haWwuc29ueWVudGVydGFpbm1lbnRuZXR3b3JrLmNvbS9pLzQ4LzIwODg1ODM4NzQvMTIwNDMwX1NFTl9UWE5fU0VOX3NwYWNlcjEuZ2lmIiB3aWR0aD0iMSIgaGVpZ2h0PSIyMCIgaHNwYWNlPSIwIiB2c3BhY2U9IjAiIGJvcmRlcj0iMCIgc3R5bGU9ImRpc3BsYXk6YmxvY2siPjwvdGQ+PC90cj48L3RhYmxlPjwvdGQ+PC90cj48L3RhYmxlPjxzdHJvbmc+PC9zdHJvbmc+PGJyPiAgDQogICAgICAgICAgICAgICAgICAgIDwvdGQ+DQogICAgICAgICAgICAgICAgICA8L3RyPg0KICAgICAgICAgICAgICAgIDwvdGJvZHk+PC90YWJsZT4NCiAgICAgICAgICAgICAgIA0KICAgICAgICAgICAgICA8L3RkPg0KICAgICAgICAgICAgPC90cj4NCiAgICAgICAgICA8L3Rib2R5PjwvdGFibGU+PC90ZD4NCiAgICAgICAgICA8dGQgd2lkdGg9IjIwIj4mbmJzcDs8L3RkPg0KICAgICAgICA8L3RyPg0KICAgIDwvdGJvZHk+PC90YWJsZT4NCiAgICAgDQogICAgIA0KICAgICAgPHRhYmxlIGNlbGxzcGFjaW5nPSIwIiBiZ2NvbG9yPSIjZmZmZmZmIiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiANCndpZHRoPSI2NjAiPg0KICAgICAgICA8dGJvZHk+PHRyPg0KICAgICAgICAgIDx0ZCB3aWR0aD0iMjAiPiZuYnNwOzwvdGQ+DQogICAgICAgICAgPHRkPjx0YWJsZSBjZWxsc3BhY2luZz0iMCIgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCI+DQogICAgICAgICAgICA8dGJvZHk+DQogICAgICAgICAgICA8dHI+DQogICAgICAgICAgICAgIDx0ZCBhbGlnbj0ibGVmdCI+PGZvbnQgc3R5bGU9ImZvbnQtZmFtaWx5OkFyaWFsLCBIZWx2ZXRpY2EsIA0Kc2Fucy1zZXJpZjsgZm9udC1zaXplOjEzcHg7IGNvbG9yOiM1NTU1NTU7Ij4NCg0KDQogICAgICAgICAgICAgICAgDQoNCiAgICAgICAgICANCiAgICAgICAgICAgICAgICANCjxmb250IHN0eWxlPSJmb250LWZhbWlseTpBcmlhbCwgSGVsdmV0aWNhLCBzYW5zLXNlcmlmOyBmb250LXNpemU6MTNweDsgDQpjb2xvcjojNTU1NTU1OyI+DQogIDwvZm9udD4gICAgICAgICAgICAgICAgICANCiAgICAgICAgICAgICAgICANCjwvZm9udD48YnI+PC90ZD4NCiAgICAgICAgICAgIDwvdHI+DQogICAgICAgICAgICA8dHI+DQogICAgICAgICAgICAgIDx0ZCBoZWlnaHQ9IjIwIj4mbmJzcDs8L3RkPg0KICAgICAgICAgICAgPC90cj4NCiAgICAgICAgICA8L3Rib2R5PjwvdGFibGU+PC90ZD4NCiAgICAgICAgICA8dGQgd2lkdGg9IjIwIj4mbmJzcDs8L3RkPg0KICAgICAgICA8L3RyPg0KICAgIDwvdGJvZHk+PC90YWJsZT4NCiAgICAgIDx0YWJsZSBjZWxsc3BhY2luZz0iMCIgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgd2lkdGg9IjY2MCI+DQogICAgICAgIDx0Ym9keT48dHI+DQogICAgICAgICAgPHRkIGhlaWdodD0iMTEiPjxpbWcgDQpzcmM9Imh0dHA6Ly9mLmVtYWlsLnNvbnllbnRlcnRhaW5tZW50bmV0d29yay5jb20vaS8zOC8yMDg5MDkyMTg3LzIwMTQwMjI3X3NvbnlfdHJhbnNhY3Rpb25hbF9zcGNyLmdpZiIgDQpzdHlsZT0iZGlzcGxheTpibG9jazsiIGhlaWdodD0iMjAiIGJvcmRlcj0iMCIgd2lkdGg9IjEwIj48L3RkPg0KICAgICAgICA8L3RyPg0KICAgICAgICA8dHI+DQogICAgICAgICAgPHRkIGFsaWduPSJjZW50ZXIiPjxmb250IHN0eWxlPSJmb250LWZhbWlseTpBcmlhbCwgSGVsdmV0aWNhLCANCnNhbnMtc2VyaWY7IGZvbnQtc2l6ZToxMXB4OyBjb2xvcjojNTU1NTU1OyI+DQoNCiAgIFRvIHVwZGF0ZSB5b3VyIG1hcmtldGluZyBwcmVmZXJlbmNlcywgcGxlYXNlIGNsaWNrIDxhIHN0eWxlPSJjb2xvcjojMDA5OWNjIiBocmVmPSJodHRwOi8vZW1haWwuc29ueWVudGVydGFpbm1lbnRuZXR3b3JrLmNvbS9hL2hCVWloUFhCOGhRUmJCOHZCTUdBQWFxUFMuQjhoUVJiV3Ivc2VuMjA5Ij5oZXJlPC9hPi48YnI+PEJSPlRoaXMgZS1tYWlsIG1lc3NhZ2UgaGFzIGJlZW4gZGVsaXZlcmVkIGZyb20gYSBzZW5kLW9ubHkgYWRkcmVzcy4gUGxlYXNlIGRvIG5vdCByZXBseSB0byB0aGlzIG1lc3NhZ2UuIEZvciBtb3JlIGluZm9ybWF0aW9uIGFib3V0IHlvdXIgYWNjb3VudCwgcGxlYXNlIHZpc2l0IHRoZSBsaW5rcyBiZWxvdy48YnI+PGJyPlN1cHBvcnQ6PGJyPjxhIHN0eWxlPSJjb2xvcjojMDA5OWNjIiBocmVmPSJodHRwOi8vZW1haWwuc29ueWVudGVydGFpbm1lbnRuZXR3b3JrLmNvbS9hL2hCVWloUFhCOGhRUmJCOHZCTUdBQWFxUFMuQjhoUVJiV3Ivc2VuMjA4Ij5odHRwOi8vd3d3LnVzLnBsYXlzdGF0aW9uLmNvbS9jb3Jwb3JhdGUvY29udGFjdHVzLzwvYT48YnI+PEJSPlRlcm1zIG9mIFVzZSBhbmQgUHJpdmFjeSBQb2xpY3k6PGJyPjxhIHN0eWxlPSJjb2xvcjojMDA5OWNjIiBocmVmPSJodHRwOi8vZW1haWwuc29ueWVudGVydGFpbm1lbnRuZXR3b3JrLmNvbS9hL2hCVWloUFhCOGhRUmJCOHZCTUdBQWFxUFMuQjhoUVJiV3Ivc2VuMjA1Ij5odHRwOi8vd3d3LnNvbnllbnRlcnRhaW5tZW50bmV0d29yay5jb20vbGVnYWwvPC9hPjxicj48QlI+4oCcU29ueSBFbnRlcnRhaW5tZW50IE5ldHdvcmvigJ0gYW5kIOKAnFNvbnkgRW50ZXJ0YWlubWVudCBOZXR3b3JrIExvZ2/igJ0gYXJlIHRyYWRlbWFya3Mgb2YgU29ueSBDb3Jwb3JhdGlvbi4gICAgICAgDQogICAgICAgICAgDQogICAgICAgICAgPC9mb250PjwvdGQ+DQogICAgICAgIDwvdHI+DQogICAgICAgIDx0cj4NCiAgICAgICAgICA8dGQgaGVpZ2h0PSI0NSIgYWxpZ249ImNlbnRlciI+Jm5ic3A7PC90ZD4NCiAgICAgICAgPC90cj4NCiAgICA8L3Rib2R5PjwvdGFibGU+PC90ZD4NCiAgPC90cj4NCjwvdGJvZHk+PC90YWJsZT4NCg0KDQogIDwhLS0gIDEwLzI2IGRlcGxveW1lbnQgDQogIA0KICBUaGVtZQkJUFNOPGJyPg0KbG9jYWxlCQllbl9VUzxicj4NCkNVUlJFTkNZCQlVU0Q8YnI+DQpQU19IQU5ETEUJCWxwcy1nb3BoZXJpdDxicj4NCkZJUlNUX05BTUUJCUpvcmR5bjxicj4NCkxBU1RfTkFNRQkJUnViaW5za3k8YnI+DQpJVEVNLiMuTkVYVF9SRU5FV0FMX0RBVEUJCTxicj4NCklURU0uIy5ERVNDUklQVElPTgkJPGJyPg0KSVRFTS4jLlNLVV9JRAkJPGJyPg0KSVRFTS4jLlNVQl9UT1RBTAkJPGJyPg0KSVRFTS4jLlRPVEFMCQk8YnI+DQpUUkFOU0FDVElPTl9USU1FU1RBTVAJCTEyLzIyLzIwMTQgQCAxMjo0NyBQTTxicj4NClRSQU5TQUNUSU9OX0lECQk3NTE2NzA2OTYxPGJyPg0KV0FMTEVUX0JBTEFOQ0UJCSQwLjAwPGJyPg0KR1JFRVRJTkcJCTxicj4NClRJTUVaT05FCQk8YnI+DQpQQVlNRU5UX01FVEhPRF9JRAkJPGJyPg0KQklMTElOR19JTkZPCQk8YnI+DQpERUNJTUFMX1BPU0lUSU9OCQk8YnI+DQpERUNJTUFMX0RJU1BMQVlfUE9TSVRJT05fTUFYX0RJR0lUUwkJDQo8YnI+DQpERUNJTUFMX0RJU1BMQVlfUE9TSVRJT05fTUlOX0RJR0lUUwkJDQo8YnI+DQpDQVJUX1NVQl9UT1RBTAkJJDAuMDA8YnI+DQpDQVJUX1RPVEFMX1NBTEVTX1RBWAkJJDAuMDA8YnI+DQpDQVJUX1RPVEFMICAJCTxicj4NCklTX1BBUkVOVAkJZmFsc2U8YnI+DQpDQVJUX0RJU1BMQVlfVEFYX0lOQ0xVU0lWRV9QUklDRVMJCQ0KZmFsc2U8YnI+TG9vcGluZzo8YnI+PHA+PC9wPjx0YWJsZSBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCIgc3R5bGU9ImJvcmRlci1jb2xsYXBzZTpjb2xsYXBzZTt3aWR0aDoxMDhwdCIgd2lkdGg9IjE0NCI+PGNvbGdyb3VwPjxjb2wgc3Bhbj0iMiIgc3R5bGU9IndpZHRoOjU0cHQid2lkdGg9IjcyIiAvPjwvY29sZ3JvdXA+PHRib2R5Pjx0ciBoZWlnaHQ9IjM5IiBzdHlsZT0iaGVpZ2h0OjI5LjI1cHQiPjx0ZCBjbGFzcz0ieGw4MSIgaGVpZ2h0PSIzOSIgc3R5bGU9ImhlaWdodDoyOS4yNXB0O3dpZHRoOjU0cHQiIHdpZHRoPSI3MiI+Q2hhcmdlIERhdGUmbmJzcDs8L3RkPjx0ZCBjbGFzcz0ieGw4MiIgc3R5bGU9IndpZHRoOjU0cHQiIHdpZHRoPSI3MiI+PC90ZD48L3RyPjx0ciBoZWlnaHQ9IjM5IiBzdHlsZT0iaGVpZ2h0OjI5LjI1cHQiPjx0ZCBjbGFzcz0ieGw4MSIgaGVpZ2h0PSIzOSIgc3R5bGU9ImhlaWdodDoyOS4yNXB0O3dpZHRoOjU0cHQiIHdpZHRoPSI3MiI+Q29udGVudCBUeXBlJm5ic3A7PC90ZD48dGQgY2xhc3M9InhsODIiPkNPTlRFTlRfVFlQRV9HQU1FPC90ZD48L3RyPjx0ciBoZWlnaHQ9IjM5IiBzdHlsZT0iaGVpZ2h0OjI5LjI1cHQiPjx0ZCBjbGFzcz0ieGw4MSIgaGVpZ2h0PSIzOSIgc3R5bGU9ImhlaWdodDoyOS4yNXB0O3dpZHRoOjU0cHQiIHdpZHRoPSI3MiI+RGVzY3JpcHRpb24mbmJzcDs8L3RkPjx0ZCBjbGFzcz0ieGw4MiI+UFMgUGx1czogMi1EYXkgVHJpYWw8L3RkPjwvdHI+PHRyIGhlaWdodD0iMzkiIHN0eWxlPSJoZWlnaHQ6MjkuMjVwdCI+PHRkIGNsYXNzPSJ4bDgxIiBoZWlnaHQ9IjM5IiBzdHlsZT0iaGVpZ2h0OjI5LjI1cHQ7d2lkdGg6NTRwdCIgd2lkdGg9IjcyIj5FcGlzb2RlIE51bWJlciZuYnNwOzwvdGQ+PHRkIGNsYXNzPSJ4bDgyIj48L3RkPjwvdHI+PHRyIGhlaWdodD0iMzkiIHN0eWxlPSJoZWlnaHQ6MjkuMjVwdCI+PHRkIGNsYXNzPSJ4bDgxIiBoZWlnaHQ9IjM5IiBzdHlsZT0iaGVpZ2h0OjI5LjI1cHQ7d2lkdGg6NTRwdCIgd2lkdGg9IjcyIj5SZW5ld2FsIERhdGUmbmJzcDs8L3RkPjx0ZCBjbGFzcz0ieGw4MyI+PC90ZD48L3RyPjx0ciBoZWlnaHQ9IjM5IiBzdHlsZT0iaGVpZ2h0OjI5LjI1cHQiPjx0ZCBjbGFzcz0ieGw4MSIgaGVpZ2h0PSIzOSIgc3R5bGU9ImhlaWdodDoyOS4yNXB0O3dpZHRoOjU0cHQiIHdpZHRoPSI3MiI+UmVsZWFzZSBEYXRlJm5ic3A7PC90ZD48dGQgY2xhc3M9InhsODIiPjwvdGQ+PC90cj48dHIgaGVpZ2h0PSIzOSIgc3R5bGU9ImhlaWdodDoyOS4yNXB0Ij48dGQgY2xhc3M9InhsODEiIGhlaWdodD0iMzkiIHN0eWxlPSJoZWlnaHQ6MjkuMjVwdDt3aWR0aDo1NHB0IiB3aWR0aD0iNzIiPlNlYXNvbiBOYW1lJm5ic3A7PC90ZD48dGQgY2xhc3M9InhsODIiPjwvdGQ+PC90cj48dHIgaGVpZ2h0PSIyMCIgc3R5bGU9ImhlaWdodDoxNS4wcHQiPjx0ZCBjbGFzcz0ieGw4MSIgaGVpZ2h0PSIyMCIgc3R5bGU9ImhlaWdodDoxNS4wcHQ7d2lkdGg6NTRwdCIgd2lkdGg9IjcyIj5TS1UgSUQmbmJzcDs8L3RkPjx0ZCBjbGFzcz0ieGw4MiI+SVA5MTAxLU5QSUE5MDAwNV8wMS1ESVNDT1ZFUlRZUEUwMDAxLVUwMDE8L3RkPjwvdHI+PHRyIGhlaWdodD0iMjAiIHN0eWxlPSJoZWlnaHQ6MTUuMHB0Ij48dGQgY2xhc3M9InhsODEiIGhlaWdodD0iMjAiIHN0eWxlPSJoZWlnaHQ6MTUuMHB0O3dpZHRoOjU0cHQiIHdpZHRoPSI3MiI+U3ViIFRvdGFsJm5ic3A7PC90ZD48dGQgY2xhc3M9InhsODIiPiQwLjAwPC90ZD48L3RyPjx0ciBoZWlnaHQ9IjIwIiBzdHlsZT0iaGVpZ2h0OjE1LjBwdCI+PHRkIGNsYXNzPSJ4bDgxIiBoZWlnaHQ9IjIwIiBzdHlsZT0iaGVpZ2h0OjE1LjBwdDt3aWR0aDo1NHB0IiB3aWR0aD0iNzIiPlRvdGFsJm5ic3A7PC90ZD48dGQgY2xhc3M9InhsODIiPjwvdGQ+PC90cj48dHIgaGVpZ2h0PSIzOSIgc3R5bGU9ImhlaWdodDoyOS4yNXB0Ij48dGQgY2xhc3M9InhsODEiIGhlaWdodD0iMzkiIHN0eWxlPSJoZWlnaHQ6MjkuMjVwdDt3aWR0aDo1NHB0IiB3aWR0aD0iNzIiPlNlcmllcyBOYW1lJm5ic3A7PC90ZD48dGQgY2xhc3M9InhsODIiPjwvdGQ+PC90cj48dHIgaGVpZ2h0PSIzOSIgc3R5bGU9ImhlaWdodDoyOS4yNXB0Ij48dGQgY2xhc3M9InhsODEiIGhlaWdodD0iMzkiIHN0eWxlPSJoZWlnaHQ6MjkuMjVwdDt3aWR0aDo1NHB0IiB3aWR0aD0iNzIiPlZpZGVvIFR5cGUmbmJzcDs8L3RkPjx0ZCBjbGFzcz0ieGw4MiI+PC90ZD48L3RyPjwvdGJvZHk+PC90YWJsZT48QlI+DQogIG51bWJlcnMgdGVzdGluZyA8b2w+DQo8bGk+dGV4dDwvbGk+DQo8bGk+dGV4dDwvbGk+DQo8bGk+dGV4dDwvbGk+DQo8L29sPg0KLS0+IA0KPGltZyBzcmM9Imh0dHA6Ly9lbWFpbC5zb255ZW50ZXJ0YWlubWVudG5ldHdvcmsuY29tL2EvaEJVaWhQWEI4aFFSYkI4dkJNR0FBYXFQUy5COGhRUmJXci9jb2x1bW4uZ2lmIj4NCjwvYm9keT4NCjwvaHRtbD4iO319fX19czoxMToiACoAX2tleVR5cGUiO2k6MjtzOjExOiIAKgBfb2JDbGFzcyI7czoyODoiSG9yZGVfSW1hcF9DbGllbnRfRGF0YV9GZXRjaCI7fQ==Horde_ActiveSync-2.31.1/test/Horde/ActiveSync/fixtures/invitation_one.eml0000664000076500000240000002745612654565405023426 0ustar Return-Path: <3fTDSTwwJBKoWSUObeLSXcUiQWKSV.MYWWSUOdROeZcdKSbcbYYW.MYW@calendar-server.bounces.google.com> X-Original-To: mike@theupstairsroom.com Delivered-To: mike@theupstairsroom.com Received: by prod.theupstairsroom.com (Postfix, from userid 5001) id 2B5945C0AA4; Fri, 8 Jun 2012 13:04:02 -0400 (EDT) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on prod.theupstairsroom.com X-Spam-Level: X-Spam-Status: No, score=-4.2 required=5.0 tests=BAYES_00,FREEMAIL_FROM, HTML_MESSAGE,RCVD_IN_DNSWL_MED autolearn=ham version=3.3.1 Received: from mail-ob0-f201.google.com (mail-ob0-f201.google.com [209.85.214.201]) (using TLSv1 with cipher RC4-SHA (128/128 bits)) (No client certificate requested) by prod.theupstairsroom.com (Postfix) with ESMTPS id 410385C0A8D for ; Fri, 8 Jun 2012 13:03:58 -0400 (EDT) Received: by obbwd18 with SMTP id wd18so1442099obb.4 for ; Fri, 08 Jun 2012 10:03:57 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20120113; h=mime-version:reply-to:sender:auto-submitted:message-id:date:subject :from:to:content-type; bh=jJh2E3SH8aNAaMrugFpQEJ6vIlduIV8H2PrFPUQfTxI=; b=LYr/qlgG5CMR2AB6Uis5xQoDcsHHAubPWtMfPJI3OwrioJZKo4+BjTj0X/r6U1uTu9 fCI4vx+0e/53hhFNe6ne3sZoJsJEYByZwpgNozZwhYZMaypVEpoAqtvHzMA9+g0kU/p7 Wx2P69tnoCgsWyq4Plg1cQmIKd0VSNt3isvKI46wZb+ECJHlyP1pUfItEghUPekdEZHr M+K3YS7mj71Z4ty/SPApfNMpw82T/EcRAmK1MwD8YrdFJ4WOFfiD0ajv/OcgmUwbN4hf WleqnoInoHUzc6PQazDehv1EscTeyBh2xgLKSm7fcbbM2zM5tOxchtN4NO+c7Q4jTnGE +tBA== MIME-Version: 1.0 Received: by 10.50.40.230 with SMTP id a6mr3851232igl.2.1339175037754; Fri, 08 Jun 2012 10:03:57 -0700 (PDT) Reply-To: Michael Rubinsky Sender: Google Calendar Auto-Submitted: auto-generated Message-ID: <14dae93411cf6ade7504c1f8fcc0@google.com> Date: Fri, 08 Jun 2012 17:03:57 +0000 Subject: Invitation: test @ Fri Jun 8 1:30pm - 2:30pm (mike@theupstairsroom.com) From: Michael Rubinsky To: "mike@theupstairsroom.com" Content-Type: multipart/mixed; boundary=14dae93411cf6ade6804c1f8fcbf --14dae93411cf6ade6804c1f8fcbf Content-Type: multipart/alternative; boundary=14dae93411cf6ade5f04c1f8fcbd --14dae93411cf6ade5f04c1f8fcbd Content-Type: text/plain; charset=windows-1252; format=flowed; delsp=yes Content-Transfer-Encoding: base64 WW91IGhhdmUgYmVlbiBpbnZpdGVkIHRvIHRoZSBmb2xsb3dpbmcgZXZlbnQuDQoNClRpdGxlOiB0 ZXN0DQpmYXNkZmFzZGZzYWYNCldoZW46IEZyaSBKdW4gOCAxOjMwcG0gliAyOjMwcG0gRWFzdGVy biBUaW1lDQpDYWxlbmRhcjogbWlrZUB0aGV1cHN0YWlyc3Jvb20uY29tDQpXaG86DQogICAgICog TWljaGFlbCBSdWJpbnNreSAtIG9yZ2FuaXplcg0KICAgICAqIG1pa2VAdGhldXBzdGFpcnNyb29t LmNvbQ0KDQpFdmVudCBkZXRhaWxzOiAgDQpodHRwczovL3d3dy5nb29nbGUuY29tL2NhbGVuZGFy L2V2ZW50P2FjdGlvbj1WSUVXJmVpZD1NRE5oZFRnNWRteGpNMm8wYXpGc05EaHFNWFE0WldaMGRu TWdiV2xyWlVCMGFHVjFjSE4wWVdseWMzSnZiMjB1WTI5dCZ0b2s9TWpJamJXbHJaWEoxWW1sdWMy dDVRR2R0WVdsc0xtTnZiV000WkRRNFpHWXdNekZoTVRZd01tUTVZVFZsWldRMk56Z3pPVFUzWXpk aU9UTmlabUl4T0RNJmN0ej1BbWVyaWNhL05ld19Zb3JrJmhsPWVuDQoNCkludml0YXRpb24gZnJv bSBHb29nbGUgQ2FsZW5kYXI6IGh0dHBzOi8vd3d3Lmdvb2dsZS5jb20vY2FsZW5kYXIvDQoNCllv dSBhcmUgcmVjZWl2aW5nIHRoaXMgY291cnRlc3kgZW1haWwgYXQgdGhlIGFjY291bnQgIA0KbWlr ZUB0aGV1cHN0YWlyc3Jvb20uY29tIGJlY2F1c2UgeW91IGFyZSBhbiBhdHRlbmRlZSBvZiB0aGlz IGV2ZW50Lg0KDQpUbyBzdG9wIHJlY2VpdmluZyBmdXR1cmUgbm90aWZpY2F0aW9ucyBmb3IgdGhp cyBldmVudCwgZGVjbGluZSB0aGlzIGV2ZW50LiAgDQpBbHRlcm5hdGl2ZWx5IHlvdSBjYW4gc2ln biB1cCBmb3IgYSBHb29nbGUgYWNjb3VudCBhdCAgDQpodHRwczovL3d3dy5nb29nbGUuY29tL2Nh bGVuZGFyLyBhbmQgY29udHJvbCB5b3VyIG5vdGlmaWNhdGlvbiBzZXR0aW5ncyBmb3IgIA0KeW91 ciBlbnRpcmUgY2FsZW5kYXIuDQo= --14dae93411cf6ade5f04c1f8fcbd Content-Type: text/html; charset=windows-1252 Content-Transfer-Encoding: quoted-printable

test

fasdfasdfsaf
When
Fri Jun 8 1:30pm =96 2:3= 0pm Eastern Time
Calendar
mike@theupstairsroom.c= om
Who
Michael Rubinsky - organizer
mike@theupstairsroom.com

Going?   Yes - Maybe - No    <= wbr>more options »

Invitation from Google Calendar<= /p>

You are receiving this courtesy email at the account mike@theupstairs= room.com because you are an attendee of this event.

To stop receiving= future notifications for this event, decline this event. Alternatively you= can sign up for a Google account at https://www.google.com/calendar/ and c= ontrol your notification settings for your entire calendar.

--14dae93411cf6ade5f04c1f8fcbd Content-Type: text/calendar; charset=UTF-8; method=REQUEST Content-Transfer-Encoding: 7bit BEGIN:VCALENDAR PRODID:-//Google Inc//Google Calendar 70.9054//EN VERSION:2.0 CALSCALE:GREGORIAN METHOD:REQUEST BEGIN:VEVENT DTSTART:20120608T173000Z DTEND:20120608T183000Z DTSTAMP:20120608T170357Z ORGANIZER;CN=Michael Rubinsky:mailto:mikerubinsky@gmail.com UID:03au89vlc3j4k1l48j1t8eftvs@google.com ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP= TRUE;CN=mike@theupstairsroom.com;X-NUM-GUESTS=0:mailto:mike@theupstairsroom .com ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;RSVP=TRUE ;CN=Michael Rubinsky;X-NUM-GUESTS=0:mailto:mikerubinsky@gmail.com CREATED:20120608T170357Z DESCRIPTION:FooBar\nView your event at http://www.google.com/calendar /event?action=VIEW&eid=MDNhdTg5dmxjM2o0azFsNDhqMXQ4ZWZ0dnMgbWlrZUB0aGV1cHN0 YWlyc3Jvb20uY29t&tok=MjIjbWlrZXJ1Ymluc2t5QGdtYWlsLmNvbWM4ZDQ4ZGYwMzFhMTYwMm Q5YTVlZWQ2NzgzOTU3YzdiOTNiZmIxODM&ctz=America/New_York&hl=en. LAST-MODIFIED:20120608T170357Z LOCATION:Philadelphia,PA SEQUENCE:0 STATUS:CONFIRMED SUMMARY:test TRANSP:OPAQUE END:VEVENT END:VCALENDAR --14dae93411cf6ade5f04c1f8fcbd-- --14dae93411cf6ade6804c1f8fcbf Content-Type: application/ics; name="invite.ics" Content-Disposition: attachment; filename="invite.ics" Content-Transfer-Encoding: base64 QkVHSU46VkNBTEVOREFSDQpQUk9ESUQ6LS8vR29vZ2xlIEluYy8vR29vZ2xlIENhbGVuZGFyIDcw LjkwNTQvL0VODQpWRVJTSU9OOjIuMA0KQ0FMU0NBTEU6R1JFR09SSUFODQpNRVRIT0Q6UkVRVUVT VA0KQkVHSU46VkVWRU5UDQpEVFNUQVJUOjIwMTIwNjA4VDE3MzAwMFoNCkRURU5EOjIwMTIwNjA4 VDE4MzAwMFoNCkRUU1RBTVA6MjAxMjA2MDhUMTcwMzU3Wg0KT1JHQU5JWkVSO0NOPU1pY2hhZWwg UnViaW5za3k6bWFpbHRvOm1pa2VydWJpbnNreUBnbWFpbC5jb20NClVJRDowM2F1ODl2bGMzajRr MWw0OGoxdDhlZnR2c0Bnb29nbGUuY29tDQpBVFRFTkRFRTtDVVRZUEU9SU5ESVZJRFVBTDtST0xF PVJFUS1QQVJUSUNJUEFOVDtQQVJUU1RBVD1ORUVEUy1BQ1RJT047UlNWUD0NCiBUUlVFO0NOPW1p a2VAdGhldXBzdGFpcnNyb29tLmNvbTtYLU5VTS1HVUVTVFM9MDptYWlsdG86bWlrZUB0aGV1cHN0 YWlyc3Jvb20NCiAuY29tDQpBVFRFTkRFRTtDVVRZUEU9SU5ESVZJRFVBTDtST0xFPVJFUS1QQVJU SUNJUEFOVDtQQVJUU1RBVD1BQ0NFUFRFRDtSU1ZQPVRSVUUNCiA7Q049TWljaGFlbCBSdWJpbnNr eTtYLU5VTS1HVUVTVFM9MDptYWlsdG86bWlrZXJ1Ymluc2t5QGdtYWlsLmNvbQ0KQ1JFQVRFRDoy MDEyMDYwOFQxNzAzNTdaDQpERVNDUklQVElPTjpmYXNkZmFzZGZzYWZcblZpZXcgeW91ciBldmVu dCBhdCBodHRwOi8vd3d3Lmdvb2dsZS5jb20vY2FsZW5kYXINCiAvZXZlbnQ/YWN0aW9uPVZJRVcm ZWlkPU1ETmhkVGc1ZG14ak0ybzBhekZzTkRocU1YUTRaV1owZG5NZ2JXbHJaVUIwYUdWMWNITjAN CiBZV2x5YzNKdmIyMHVZMjl0JnRvaz1NaklqYldsclpYSjFZbWx1YzJ0NVFHZHRZV2xzTG1OdmJX TTRaRFE0WkdZd016RmhNVFl3TW0NCiBRNVlUVmxaV1EyTnpnek9UVTNZemRpT1ROaVptSXhPRE0m Y3R6PUFtZXJpY2EvTmV3X1lvcmsmaGw9ZW4uDQpMQVNULU1PRElGSUVEOjIwMTIwNjA4VDE3MDM1 N1oNCkxPQ0FUSU9OOg0KU0VRVUVOQ0U6MA0KU1RBVFVTOkNPTkZJUk1FRA0KU1VNTUFSWTp0ZXN0 DQpUUkFOU1A6T1BBUVVFDQpFTkQ6VkVWRU5UDQpFTkQ6VkNBTEVOREFSDQo= --14dae93411cf6ade6804c1f8fcbf-- Horde_ActiveSync-2.31.1/test/Horde/ActiveSync/fixtures/iOSMultipartAlternative.eml0000664000076500000240000000164412654565405025163 0ustar Subject: Testing From: mrubinsk@horde.org Content-Type: multipart/alternative; boundary=Apple-Mail-B1C01B47-00D8-4AFB-8B65-DF81C4E4B47D Message-Id: Date: Tue, 1 Jan 2013 18:10:37 -0500 To: Michael Rubinsky Content-Transfer-Encoding: 7bit Mime-Version: 1.0 (1.0) --Apple-Mail-B1C01B47-00D8-4AFB-8B65-DF81C4E4B47D Content-Transfer-Encoding: 7bit Content-Type: text/plain; charset=us-ascii This is an HTML email. -- Mike Sent from my iPad... --Apple-Mail-B1C01B47-00D8-4AFB-8B65-DF81C4E4B47D Content-Transfer-Encoding: 7bit Content-Type: text/html; charset=utf-8

This is an HTML email.


--
Mike
Sent from my iPad...
--Apple-Mail-B1C01B47-00D8-4AFB-8B65-DF81C4E4B47D--Horde_ActiveSync-2.31.1/test/Horde/ActiveSync/fixtures/meeting_request_one.wbxml0000664000076500000240000000077712654565405025013 0ustar Z0q2012-06-08T17:30:00.000Z]2012-06-08T17:03:57.000Z^2012-06-08T18:30:00.000Z_0aPhiladelphia,PAcmikerubinsky@gmail.comf1`2sAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==tBAAAAIIA4AB0xbcQGoLgCAAAAAAAAAAAAAAAAAAAAAAAAAAAJQAAAHZDYWwtVWlkAQAAADAzYXU4OXZsYzNqNGsxbDQ4ajF0OGVmdHZzQGdvb2dsZS5jb20AHorde_ActiveSync-2.31.1/test/Horde/ActiveSync/fixtures/mime_encoding.eml0000664000076500000240000000167612654565405023172 0ustar Date: Mon, 01 Sep 2014 20:22:36 -0400 Subject: =?utf-8?b?w4PDhMOjw6s=?= From: mrubinsk@horde.org To: Michael Rubinsky MIME-Version: 1.0 Content-Type: multipart/alternative; boundary=--_com.ninefolders.hd3.email_9208724182131_alt ----_com.ninefolders.hd3.email_9208724182131_alt Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: base64 ClRlc3QKClNlbnQgZnJvbSBOaW5l ----_com.ninefolders.hd3.email_9208724182131_alt Content-Type: text/html; charset=utf-8 Content-Transfer-Encoding: base64 PGRpdiBzdHlsZT0iZm9udC1mYW1pbHk6Q2FsaWJyaSwgQXJpYWwsIEhlbHZldGljYSwgc2Fucy1z ZXJpZjtmb250LXNpemU6MTJwdDsiPjxwIGRpcj0ibHRyIj48YnI+ClRlc3Q8YnI+CjwvcD4KPGRp diBpZD0ic2lnbmF0dXJlIiBzdHlsZT0iLXdlYmtpdC11c2VyLXNlbGVjdDpub25lOyI+IFNlbnQg ZnJvbSA8YSBocmVmPSJodHRwOi8vd3d3Ljlmb2xkZXJzLmNvbS8iIHN0eWxlPSJ0ZXh0LWRlY29y YXRpb246bm9uZTtjb2xvcjojMDA5QkRGIj5OaW5lPC9hPjwvZGl2PjwvZGl2Pg== ----_com.ninefolders.hd3.email_9208724182131_alt-- Horde_ActiveSync-2.31.1/test/Horde/ActiveSync/fixtures/recurrence.wbxml0000664000076500000240000000061212654565405023073 0ustar ]ELAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsAAAABAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAACAAIAAAAAAAAAxP///w==g20111201T200000ZR20111201T210000ZfEvent TitleWPhiladelphia, PAM2[\1_2`16e1Q20111201T200000ZX0KEvent DescriptionL0Horde_ActiveSync-2.31.1/test/Horde/ActiveSync/fixtures/rfc822_multipart.eml0000664000076500000240000016544712654565405023513 0ustar From: m@example.com To: f@example.com Subject: WG: Office Thread-Topic: Office Thread-Index: AdDu3s12f7fwGVfxSHahHMjK6vVTbgBa2iWg Date: Wed, 16 Sep 2015 06:40:19 +0000 Message-ID: References: <4165d7fe46254b1f9558650733831316@TLREXCH1.example.local> In-Reply-To: <4165d7fe46254b1f9558650733831316@TLREXCH1.example.local> Accept-Language: de-AT, en-US Content-Language: de-DE X-MS-Has-Attach: yes X-MS-TNEF-Correlator: x-ms-exchange-transport-fromentityheader: Hosted Content-Type: multipart/mixed; boundary="_004_a550a4571813423e982b2b024ad2bf7bTLREXCH1examplelocal_" MIME-Version: 1.0 --_004_a550a4571813423e982b2b024ad2bf7bTLREXCH1examplelocal_ Content-Type: multipart/alternative; boundary="_000_a550a4571813423e982b2b024ad2bf7bTLREXCH1examplelocal_" --_000_a550a4571813423e982b2b024ad2bf7bTLREXCH1examplelocal_ Content-Type: text/plain; charset="iso-8859-1" Content-Transfer-Encoding: quoted-printable Hi Flo, anbei das Mail. MfG Matthias Von: m Gesendet: Montag, 14. September 2015 13:17 An: Fillafer Markus Betreff: Office Hallo Markus, anbei wie besprochen das Sitzungsprotokoll. MfG Matthias --_000_a550a4571813423e982b2b024ad2bf7bTLREXCH1examplelocal_ Content-Type: text/html; charset="iso-8859-1" Content-Transfer-Encoding: quoted-printable

Hi Flo,

 

anbei das Mail.

 

MfG

Matthias

 

Von: m
Gesendet: Montag, 14. September 2015 13:17
An: Fillafer Markus
Betreff: Office

 

Hallo Markus,

 

anbei wie besprochen das Sitzungsprotokol= l.

 

MfG

Matthias

--_000_a550a4571813423e982b2b024ad2bf7bTLREXCH1examplelocal_-- --_004_a550a4571813423e982b2b024ad2bf7bTLREXCH1examplelocal_ Content-Type: message/rfc822 Content-Disposition: attachment; creation-date="Mon, 14 Sep 2015 11:16:01 GMT"; modification-date="Mon, 14 Sep 2015 11:17:12 GMT" From: m@example.com To: "P P (p.p@ph-example.ac.at)" , =?iso-8859-1?Q?Nimmerfall_G=FCnter_=28g=2Enimmerfall=40ph-example=2Ea?= =?iso-8859-1?Q?c=2Eat=29?= , "Pietra Benjamin (b.p@ph-example.ac.at)" , =?iso-8859-1?Q?K=D6LLICH_f?= Subject: Office Thread-Topic: Office Thread-Index: AdDEQSETj/SB8h0dRX2+CoJVkR9sNg== Date: Wed, 22 Jul 2015 10:54:15 +0000 Message-ID: Content-Language: de-DE X-MS-Has-Attach: X-MS-Exchange-Organization-SCL: -1 X-MS-TNEF-Correlator: x-ms-exchange-organization-originalclientipaddress: 10.10.130.150 x-ms-exchange-organization-originalserveripaddress: fe80::416f:510a:b711:9af1%20 Content-Type: multipart/alternative; boundary="_000_cf3d11da12434e0a88f3ea7245bda02dTLREXCH1examplelocal_" MIME-Version: 1.0 --_000_cf3d11da12434e0a88f3ea7245bda02dTLREXCH1examplelocal_ Content-Type: text/plain; charset="iso-8859-1" Content-Transfer-Encoding: quoted-printable Liebe Alle, anbei =FCbermittle ich das Besprechungsprotokoll zu unserem gestrigen Meeti= ng. BG Matthias DI Matthias Peintner DVT - Daten-Verarbeitung-example GmbH 6020 Innsbruck . Adamgasse 22 Tel: +43 512 508 3325 Fax: +43 512 508 743355 dvt@example.gv.at https://www.dvt.at Termin: 21.7.2015 16:00 - 17:00 Ort: DVT Teilnehmer: Pallhuber, Nimmerfall, Pietra, K=F6llich, Peintner Zahl: 47/2157-2015 Themen: =B7 Die PH example baut ihre Infrastruktur um und wird dabei von der = Firma Cumulo (Ansprechpartner Martin Leonhardsberger) unterst=FCtzt. Die Fi= rma Cumulo verbindet die lokale Infrastruktur der PHT mit dem Identity Mana= gement von PH-Online. Dabei kommt Office 365 in der Microsoft Cloud zum Ein= satz und die PHT wechselt mit dem Mail von TSN nach Office 365. Mit dem Ums= tieg des Identity Managements werden mehrere Portal example Anwendungen f=FCr= die PHT obsolet und somit eingestellt. =B7 F=FCr PHT Bedienstete wird das TSN Postfach migriert und gel=F6= scht, der TSN Benutzer deaktiviert. In Ausnahmef=E4lle, bleibet der Benutze= r ohne Postfach bestehen um weiterhin eine Anwendung im Portal example zu nut= zen. =B7 Der PH example dienstzugeteilte oder an der PHT mitverwendete Lan= deslehrer oder Bundeslehrer behalten ihr TSN Postfach und den TSN Benutzer.= Rechte auf PHT Anwendungen gehen verloren. =B7 Zeitplan f=FCr die Umstellung: o Start am 1.9.2015 mit der Umstellung des Mailroutings f=FCr ph-example.ac= .at und Beginn der Migrationsphase. o Ende der Migrationsphase mit 1.10.2015. =B7 Mengenger=FCst: o Es sind zwei Organisationseinheiten in der TSNUSR betroffen: =A7 P=E4dagogische Hochschule SNR:701660 =A7 P=E4dagogische Hochschule extern SON:701660 o PH example - Benutzer: =A7 Vortragende / Lehrk=F6rper (Mitglieder in TSNUSR-Lehrer) = 380 =A7 Studenten (Mitglieder in TSNUSR-Sch=FCler) = 1.730 =A7 Verwaltungspersonal (Mitglieder in TSNUSR-Sonstige) = 143 =A7 davon mitverwende oder dienstzugeteilte Landeslehrer/Bundeslehrer = 367 o PHT - Anwendungen, durchgestrichene Anwendungen entfallen nach der Migr= ation: =A7 BIDI - Bildungsdienste Sondergenehmigungen f=FCr dienstzugeteilte/mitv= erwendete Lehrer =A7 LOKWEB - LeOn Suche Bildungsmedien =FCber Internet ? =A7 TISIS - exampleer Schulsportservice ? =A7 TSNmail - Mail =A7 BSA - Bediensteten Service Applikation (mitverwendete dienstzugeteilte= erhalten dieses Recht bei der Stamm OE) =A7 BSG - Bedienstetenschutzgesetz (mitverwendete dienstzugeteilte erhalte= n dieses Recht bei der Stamm OE) =A7 EGM - Entgeltmanagement (mitverwendete dienstzugeteilte erhalten diese= s Recht bei der Stamm OE) =A7 IVW - Inventarverwaltung (mitverwendete dienstzugeteilte erhalten dies= es Recht bei der Stamm OE) =A7 TBSCMS - Tibs CMS =A7 PHT moodle =A7 MOODLE - TSNmoodle E-Learning Plattform =A7 MAHARA - TSNmahara E-Portfolio =A7 TSNschool - Directory Synchronisation o PHT extern - Benutzer: =A7 Sonstige (Mitglieder in TSNUSR-Sonstige) 57 o PHT extern - Anwendungen, durchgestrichene Anwendungen entfallen nach d= er Migration: =A7 MOODLE - TSNmoodle E-Learning Plattform =B7 Vorgehen f=FCr ausgew=E4hlte Anwendungen: o Das PHT Moodle wird aktuell mit dem TSN Benutzer =FCber das Portal Tiro= l gen=FCtzt. Das PHT Moodle wird zeitgerecht auf das neu entstehende ADFS (= Active Directory Federation Service) an der PHT umgeh=E4ngt. o Der Intranet Bereich am PHT Webauftritt sowie der Redaktionseinstieg wi= rd aktuell mit dem TSN Benutzer =FCber das Portal example genutzt. Dieses Sze= nario ist nach der Migration nicht mehr vorgesehen. Die Frage ist ob es m=F6glich ist den Auftritt am Tibs CMS auf das ADFS der= PHT umzuh=E4ngen, somit w=FCrden zwei Federation Services auf der selben I= nstanz laufen was problematisch ist. Alternativ sollte es kein Problem dars= tellen den Webauftritt auf einem unabh=E4ngigen Server zu Hosten und an das= ADFS der PHT anzuh=E4ngen. Die PHT kl=E4rt intern das weitere Vorgehen, die zeitliche Abh=E4ngigkeit z= ur Migrationsphase sollte beachtet werden. =B7 Vorbereitungsma=DFnahmen: o Die DVT: =A7 setzt den TXT Eintrag am DNS Server. o Die PHT: =A7 informiert ihre Bediensteten =FCber die bevorstehende Migrationsphase = und =A7 kl=E4rt das weitere Vorgehen mit dem Intranet Bereich am PHT Webauftri= tt. =B7 Stichtag 1.9.2015: o Die DVT =E4ndert den MX Domain Record f=FCr ph-example.ac.at, die daf=FCr= n=F6tigen Informationen liefert die Firma Cumulo. o Die DVT l=F6scht zentral alle vorhandenen ph-example.ac.at Adressen in de= r TSN Userverwaltung. o Ab diesem Zeitpunkt werden alle externen Mails an ph-example.ac.at an die= Microsoft Cloud zugestellt. =B7 Migrationsphase bis 1.10.2015: o Wegen einer Limitierung des Microsoft Exchange 2010 Servers, welcher al= s TSN Mailsystem im Einsatz ist, kann nicht gew=E4hrleistet werden, dass Ma= ils von TSN Kunden (exampleer Schulen, Kinderbetreuungseinrichtungen und Lehr= er) welche an die =FCbertragene ph-example.ac.at Mailadresse gerichtet sind k= orrekt an Office 365 zugestellt werden. Diesem Umstand geschuldet ist es wi= chtig die Migrationsphase einzuhalten damit f=FCr die Benutzer ein geordnet= er =DCbergang stattfinden kann. o Die Migration erfolgt in zwei Schritten: =A7 Der PH-example Bedienstete holt seine Daten am lokalen Mailclient ein le= tztes Mal vom TSN Postfach ab. Dabei werden abh=E4ngig vom Verbindungsproto= koll alle Mails/Kontakte/Kalender/Notizen etc. lokal am Client gespeichert. =A7 Im Anschluss wendet sich der Bedienstete an einen Administrator in der= PH-example. Dieser steigt in die TSN Userverwaltung ein und l=F6scht das Mai= l-Postfach, falls mit dem TSN Benutzer keine weitere Portal example Anwendung= genutzt wird deaktiviert er den Benutzer. Ein deaktivieren des Benutzers ohne l=F6schen des Postfaches reicht nicht a= us und f=FChrt zu Fehlern. =A7 An der PH example besitzen folgende Personen Administratorberechtigungen= f=FCr die TSN Userverwaltung und k=F6nnen somit von den Bediensteten f=FC= r die Migration kontaktiert werden: Bacher Heidi, Bl=FCmel Adelinde, Knitel= Dietmar, Mader Robert, Nimmerfall G=FCnter, Perger Paul, Pietra Benjamin, = Saurer Bettina, Sch=F6pf Markus, Wieser Reinhard, Winkler Petra. o Die PHT Standorte sind im TSN Netz, somit ist das Verbindungsprotokoll = MAPI zug=E4nglich =FCber welches der gesamte Postfachinhalt abgeholt wird. = Au=DFerhalb der PHT Standorte haben Benutzer als Verbindungsprotokoll POP3 = und IMAP zur Verf=FCgung =FCber welches nur Mails abgeholt werden. o Personen welche an der PHT Mitverwendet oder Dienstzugeteilt sind haben= in der TSN Userverwaltung als Stammorganisation eine fremde Dienststelle e= ingetragen. Die PHT Administratoren haben in diesem Fall keinen Zugriff auf= das Postfach oder den Benutzerstatus es m=FCssen nur die Rechte f=FCr den = Benutzer gel=F6scht werden. =B7 Zust=E4ndigkeiten: o PH example: =A7 Information der betroffene Personen sowie technische und organisatoris= che Unterst=FCtzung der Personen. =A7 Bearbeiten der Benutzer in der TSN Userverwaltung. =A7 Zeitgerechtes umstellen der Authentifizierung f=FCr das PHT Moodle und= den PHT Webauftritt (Drupal). =A7 Ansprechpartner: G=FCnter Nimmerfall o DVT: =A7 DNS Eintr=E4ge setzen. =A7 ph-example.ac.act Adressen l=F6schen. =A7 Ansprechpartner: Matthias Peintner =B7 Weitere Punkte: o Die PH example wird eingeladen zu pr=FCfen ob es organisatorisch nicht ei= nfacher w=E4re wenn die Firma Cumulo mittelfristig auch das DNS Hosting =FC= bernimmt. o Die PH example wird ersucht das Ansinnen des LSR f=FCr example und der Abt= . Bildung zu unterst=FCtzten in welchem die Umstellung der PH Online Portal= example Anbindung auf das bPK angestrebt wird (Authentifizierung anhand des = bereichspezifischen Personenkennzeichens in der Qualit=E4t der B=FCrgerkart= e). Ziel dieser Umstellung ist es den unn=F6tig hohen Supportaufwand auf der Se= ite der PH example als auch auf Seite der DVT zu reduzieren. -- Ende der Notiz -- --_000_cf3d11da12434e0a88f3ea7245bda02dTLREXCH1examplelocal_ Content-Type: text/html; charset="iso-8859-1" Content-Transfer-Encoding: quoted-printable ">

Liebe Alle,

 

anbei =FCbermittle ich das Besprechungsprotokoll zu = unserem gestrigen Meeting.

 

BG

Matthias

 

DI Matthias Peintner

DVT - Daten-Verarbeitung-example GmbH

6020 Innsbruck . Adamgasse 22

Tel: +43 512 508 3325

Fax: +43 512 508 743355

 

 

Termin:       &= nbsp;       21.7.2015 16:00 – 17:00

Ort:                    &= nbsp;   DVT

Teilnehmer:      Pal= lhuber, Nimmerfall, Pietra, K=F6llich, Peintner

Zahl:       &n= bsp;            = ; 47/2157-2015

Themen:

=B7         Die PH example baut ihre Infrastruktur um und = wird dabei von der Firma Cumulo (Ansprechpartner Martin Leonhardsberger) un= terst=FCtzt. Die Firma Cumulo verbindet die lokale Infrastruktur der PHT mi= t dem Identity Management von PH-Online. Dabei kommt Office 365 in der Microsoft Cloud zum Einsatz und die PHT wech= selt mit dem Mail von TSN nach Office 365. Mit dem Umstieg des Identity Man= agements werden mehrere Portal example Anwendungen f=FCr die PHT obsolet und = somit eingestellt.

=B7         F=FCr PHT Bedienstete wird das TSN Postfach = migriert und gel=F6scht, der TSN Benutzer deaktiviert. In Ausnahmef=E4lle, = bleibet der Benutzer ohne Postfach bestehen um weiterhin eine Anwendung im = Portal example zu nutzen.

=B7         Der PH example dienstzugeteilte oder an der PH= T mitverwendete Landeslehrer oder Bundeslehrer behalten ihr TSN Postfach un= d den TSN Benutzer. Rechte auf PHT Anwendungen gehen verloren.

=B7         Zeitplan f=FCr die Umstellung:

o   Start am 1.9.2015 mit der Umstellung des Mai= lroutings f=FCr ph-example.ac.at und Beginn der Migrationsphase.

o   Ende der Migrationsphase mit 1.10.2015.=

=B7         Mengenger=FCst:

o   Es sind zwei Organisationseinheiten in der T= SNUSR betroffen:

=A7&= nbsp; P=E4dagogische Hochschule   &= nbsp;           &nbs= p;         SNR:701660

=A7&= nbsp; P=E4dagogische Hochschule extern  =          SON:701660

o   PH example - Benutzer:

=A7&= nbsp; Vortragende / Lehrk=F6rper (Mitglieder in TS= NUSR-Lehrer)           &n= bsp;            = ;    380

=A7&= nbsp; Studenten (Mitglieder in TSNUSR-Sch=FCler) &= nbsp;           &nbs= p;             =              &n= bsp;            = ;  1.730

=A7&= nbsp; Verwaltungspersonal (Mitglieder in TSNUSR-So= nstige)           &n= bsp;            &nbs= p;             =             143

=A7&= nbsp; davon mitverwende oder dienstzugeteilte Land= eslehrer/Bundeslehrer         =       367

o   PHT - Anwendungen, durchgestrichene Anwendun= gen entfallen nach der Migration:

=A7&= nbsp; BIDI – Bildungsdienste Sondergenehmigu= ngen f=FCr dienstzugeteilte/mitverwendete Lehrer

=A7&= nbsp; LOKWEB – LeOn Suche Bildungsmedien =FC= ber Internet ?

=A7&= nbsp; TISIS – exampleer Schulsportservice ?

=A7  TSNmail - Mail

=A7  BSA – Bediensteten Service Appl= ikation (mitverwendete dienstzugeteilte erhalten dieses Recht bei der Stamm= OE)

=A7  BSG – Bedienstetenschutzgesetz = (mitverwendete dienstzugeteilte erhalten dieses Recht bei der Stamm OE)

=A7  EGM - Entgeltmanagement (mitverwendet= e dienstzugeteilte erhalten dieses Recht bei der Stamm OE)

=A7  IVW - Inventarverwaltung (mitverwende= te dienstzugeteilte erhalten dieses Recht bei der Stamm OE)<= /p>

=A7  TBSCMS – Tibs CMS

=A7  PHT moodle

=A7  MOODLE – TSNmoodle E-Learning P= lattform

=A7  MAHARA – TSNmahara E-Portfolio<= o:p>

=A7  TSNschool – Directory Synchroni= sation

o   PHT extern - Benutzer:

=A7&= nbsp; Sonstige (Mitglieder in TSNUSR-Sonstige)&nbs= p;             =            57<= /p>

o   PHT extern - Anwendungen, durchgestrichene A= nwendungen entfallen nach der Migration:

=A7  MOODLE – TSNmoodle E-Learning P= lattform

=B7         Vorgehen f=FCr ausgew=E4hlte Anwendungen:

o   Das PHT Moodle wird aktuell mit dem TSN Benu= tzer =FCber das Portal example gen=FCtzt. Das PHT Moodle wird zeitgerecht auf= das neu entstehende ADFS (Active Directory Federation Service) an der PHT = umgeh=E4ngt.

o   Der Intranet Bereich am PHT Webauftritt sowi= e der Redaktionseinstieg wird aktuell mit dem TSN Benutzer =FCber das Porta= l example genutzt. Dieses Szenario ist nach der Migration nicht mehr vorgeseh= en.

Die Frage ist ob= es m=F6glich ist den Auftritt am Tibs CMS auf das ADFS der PHT umzuh=E4nge= n, somit w=FCrden zwei Federation Services auf der selben Instanz laufen wa= s problematisch ist. Alternativ sollte es kein Problem darstellen den Webauftritt auf einem unabh=E4ngigen Server zu= Hosten und an das ADFS der PHT anzuh=E4ngen.
Die PHT kl=E4rt intern das weitere Vorgehen, die zeitliche Abh=E4ngigkeit z= ur Migrationsphase sollte beachtet werden.

=B7         Vorbereitungsma=DFnahmen:

o   Die DVT:

=A7&= nbsp; setzt den TXT Eintrag am DNS Server.

o   Die PHT:

=A7&= nbsp; informiert ihre Bediensteten =FCber die bevo= rstehende Migrationsphase und

=A7&= nbsp; kl=E4rt das weitere Vorgehen mit dem Intrane= t Bereich am PHT Webauftritt.

=B7         Stichtag 1.9.2015:

o   Die DVT =E4ndert den MX Domain Record f=FCr = ph-example.ac.at, die daf=FCr n=F6tigen Informationen liefert die Firma Cumul= o.

o   Die DVT l=F6scht zentral alle vorhandenen ph= -example.ac.at Adressen in der TSN Userverwaltung.

o   Ab diesem Zeitpunkt werden alle externen Mai= ls an ph-example.ac.at an die Microsoft Cloud zugestellt.

=B7         Migrationsphase bis 1.10.2015:

o   Wegen einer Limitierung des Microsoft Exchan= ge 2010 Servers, welcher als TSN Mailsystem im Einsatz ist, kann nicht gew= =E4hrleistet werden, dass Mails von TSN Kunden (exampleer Schulen, Kinderbetr= euungseinrichtungen und Lehrer) welche an die =FCbertragene ph-example.ac.at Mailadresse gerichtet sind korrekt an = Office 365 zugestellt werden. Diesem Umstand geschuldet ist es wichtig die = Migrationsphase einzuhalten damit f=FCr die Benutzer ein geordneter =DCberg= ang stattfinden kann.

o   Die Migration erfolgt in zwei Schritten:

=A7&= nbsp; Der PH-example Bedienstete holt seine Daten am= lokalen Mailclient ein letztes Mal vom TSN Postfach ab. Dabei werden abh= =E4ngig vom Verbindungsprotokoll alle Mails/Kontakte/Kalender/Notizen etc. = lokal am Client gespeichert.

=A7&= nbsp; Im Anschluss wendet sich der Bedienstete an = einen Administrator in der PH-example. Dieser steigt in die TSN Userverwaltun= g ein und l=F6scht das Mail-Postfach, falls mit dem TSN Benutzer keine weit= ere Portal example Anwendung genutzt wird deaktiviert er den Benutzer.

Ein deaktiviere= n des Benutzers ohne l=F6schen des Postfaches reicht nicht aus und f=FChrt = zu Fehlern.

=A7&= nbsp; An der PH example besitzen folgende Personen A= dministratorberechtigungen f=FCr die TSN Userverwaltung  und k=F6nnen = somit von den Bediensteten f=FCr die Migration kontaktiert werden: Bacher H= eidi, Bl=FCmel Adelinde, Knitel Dietmar, Mader Robert, Nimmerfall G=FCnter, Perger Paul, Pietra Benjamin, Saurer Bettina,= Sch=F6pf Markus, Wieser Reinhard, Winkler Petra.

o   Die PHT Standorte sind im TSN Netz, somit is= t das Verbindungsprotokoll MAPI zug=E4nglich =FCber welches der gesamte Pos= tfachinhalt abgeholt wird. Au=DFerhalb der PHT Standorte haben Benutzer als= Verbindungsprotokoll POP3 und IMAP zur Verf=FCgung =FCber welches nur Mails abgeholt werden.

o   Personen welche an der PHT Mitverwendet oder= Dienstzugeteilt sind haben in der TSN Userverwaltung als Stammorganisation= eine fremde Dienststelle eingetragen. Die PHT Administratoren haben in die= sem Fall keinen Zugriff auf das Postfach oder den Benutzerstatus es m=FCssen nur die Rechte f=FCr den Benu= tzer gel=F6scht werden.

=B7         Zust=E4ndigkeiten:

o   PH example:

=A7&= nbsp; Information der betroffene Personen sowie te= chnische und organisatorische Unterst=FCtzung der Personen.

=A7&= nbsp; Bearbeiten der Benutzer in der TSN Userverwa= ltung.

=A7&= nbsp; Zeitgerechtes umstellen der Authentifizierun= g f=FCr das PHT Moodle und den PHT Webauftritt (Drupal).

=A7&= nbsp; Ansprechpartner: G=FCnter Nimmerfall

o   DVT:

=A7&= nbsp; DNS Eintr=E4ge setzen.

=A7&= nbsp; ph-example.ac.act Adressen l=F6schen.

=A7&= nbsp; Ansprechpartner: Matthias Peintner

=B7         Weitere Punkte:

o   Die PH example wird eingeladen zu pr=FCfen ob = es organisatorisch nicht einfacher w=E4re wenn die Firma Cumulo mittelfrist= ig auch das DNS Hosting =FCbernimmt.

o   Die PH example wird ersucht das Ansinnen des L= SR  f=FCr example und der Abt. Bildung zu unterst=FCtzten in welchem die= Umstellung der PH Online Portal example Anbindung auf das bPK angestrebt wir= d (Authentifizierung anhand des bereichspezifischen Personenkennzeichens in der Qualit=E4t der B=FCrgerkarte).

Ziel dieser Umst= ellung ist es den unn=F6tig hohen Supportaufwand auf der Seite der PH example= als auch auf Seite der DVT zu reduzieren.

-- Ende der Notiz --

--_000_cf3d11da12434e0a88f3ea7245bda02dTLREXCH1examplelocal_-- --_004_a550a4571813423e982b2b024ad2bf7bTLREXCH1examplelocal_-- Horde_ActiveSync-2.31.1/test/Horde/ActiveSync/fixtures/signed_attachment.eml0000664000076500000240000026035012654565405024052 0ustar Return-Path: X-Original-To: mike@theupstairsroom.com Delivered-To: mike@theupstairsroom.com Received: from localhost (localhost [127.0.0.1]) by door.theupstairsroom.com (Postfix) with ESMTP id D93355C1555 for ; Mon, 8 Sep 2014 10:44:00 -0400 (EDT) X-Virus-Scanned: Debian amavisd-new at mail.theupstairsroom.com X-Spam-Flag: NO X-Spam-Score: -4.272 X-Spam-Level: X-Spam-Status: No, score=-4.272 required=5 tests=[RCVD_IN_DNSWL_NONE=-0.0001, RCVD_IN_MSPIKE_H2=-1.772, RP_MATCHES_RCVD=-2.499, SPF_HELO_PASS=-0.001, SPF_PASS=-0.001, URIBL_BLOCKED=0.001] autolearn=unavailable autolearn_force=no Received: from door.theupstairsroom.com ([127.0.0.1]) by localhost (door.theupstairsroom.com [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id 20bCnbZuhvfr for ; Mon, 8 Sep 2014 10:43:57 -0400 (EDT) Received: from lists.horde.org (lists.horde.org [200.46.208.138]) by door.theupstairsroom.com (Postfix) with ESMTP id 5C01E5C1554 for ; Mon, 8 Sep 2014 10:43:56 -0400 (EDT) Received: by lists.horde.org (Postfix) id CBCA014F6C99; Mon, 8 Sep 2014 14:43:55 +0000 (UTC) Delivered-To: mrubinsk@horde.org Received: from maia.hub.org (unknown [200.46.151.188]) by lists.horde.org (Postfix) with ESMTP id 8DB7F14F6C98 for ; Mon, 8 Sep 2014 14:43:55 +0000 (UTC) Received: from lists.horde.org ([200.46.208.138]) by maia.hub.org (mx1.hub.org [200.46.151.188]) (amavisd-maia, port 10024) with ESMTP id 95278-05 for ; Mon, 8 Sep 2014 14:43:54 +0000 (UTC) X-Greylist: from auto-whitelisted by SQLgrey-1.8.0 Received: from door.theupstairsroom.com (door.theupstairsroom.com [50.248.141.100]) by lists.horde.org (Postfix) with ESMTP id EF00514F6C97 for ; Mon, 8 Sep 2014 14:43:53 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by door.theupstairsroom.com (Postfix) with ESMTP id C46705C1555 for ; Mon, 8 Sep 2014 10:43:52 -0400 (EDT) X-Virus-Scanned: Debian amavisd-new at mail.theupstairsroom.com Received: from door.theupstairsroom.com ([127.0.0.1]) by localhost (door.theupstairsroom.com [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id EHut8tsFWCKQ for ; Mon, 8 Sep 2014 10:43:47 -0400 (EDT) Received: from door (localhost [127.0.0.1]) (using TLSv1 with cipher ECDHE-RSA-AES256-SHA (256/256 bits)) (No client certificate requested) by door.theupstairsroom.com (Postfix) with ESMTPS id 967945C1554 for ; Mon, 8 Sep 2014 10:43:47 -0400 (EDT) Received: from coffee.theupstairsroom.com (coffee.theupstairsroom.com [50.248.141.99]) by h4.theupstairsroom.com (Horde Framework) with HTTP; Mon, 08 Sep 2014 10:43:47 -0400 Date: Mon, 08 Sep 2014 10:43:46 -0400 Message-ID: <20140908104346.Horde.HdeyXVAthz2I-RoYhtDuYA1@h4.theupstairsroom.com> From: Michael J Rubinsky To: mrubinsk@horde.org Subject: Subject Reply-to: mrubinsk@horde.org User-Agent: Internet Messaging Program (IMP) H5 (6.3.0-git) Content-Type: multipart/signed; boundary="=_EYQ5GjJ9ihZDst-d1a81_w3"; protocol="application/pkcs7-signature"; micalg=sha-1 MIME-Version: 1.0 This is a cryptographically signed message in MIME format. --=_EYQ5GjJ9ihZDst-d1a81_w3 Content-Type: multipart/mixed; boundary="=_LD5EcWzbxCyMHu2m-anR-w9" This message is in MIME format. --=_LD5EcWzbxCyMHu2m-anR-w9 Content-Type: text/plain; charset=UTF-8; format=flowed; DelSp=Yes Content-Disposition: inline Have an attachment. -- mike The Horde Project http://www.horde.org https://www.facebook.com/hordeproject https://www.twitter.com/hordeproject --=_LD5EcWzbxCyMHu2m-anR-w9 Content-Type: image/png; name=foxtrotjobs.png Content-Disposition: attachment; size=57902; filename=foxtrotjobs.png Content-Transfer-Encoding: base64 iVBORw0KGgoAAAANSUhEUgAABAAAAAQACAMAAABIw9uxAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJ bWFnZVJlYWR5ccllPAAAAwBQTFRFaIWniHtr/c67lIRrrZqTlK+5u6ScOsf1ARQi17eqVLLWa3qK nbW/JMX1A8Pz2LR05bxymLTDAQcOa2RZbompR0VFZYOlcYurG8X1VlVY6sS0vLmMODY3V2Vu+NHB KsX1KSUmMcb1xql0GYqr/8dk68R1////amVkhWs4vaNy/8lpbIKcjaq1ka24lbPBRMn1YVpVnbCT tbq9/9GBfHJmiJ2l5ufoJ6zVlKmgQcf1LbjkIBAJ88RxV0QeloqFdIqSfHV02NnaDnaVdWxjm7Km UlthYmt1ZnJ9p7e4eoKGPTs7/81yTT031MGPjKKrTVJY/dG/9Mu6xcfJq5ZwOUZMw6qgUExK9MJs ipOYACo7XXmJm7W/m7OxE6vV+8q2DLnnjIJ++sVpmbG7/8Vcqbanwrp65MvAtZxyoaOll5qc8sm3 zrKn9PT0AFJp5L+xa4eoLisr9spzpZJvWFJNepGa2cF1SUxQYpuzrY5OysLA37dw3cnBJzQ6o4h9 78a1NL7rYpSrCpi+ubqeWnyghYuP7b9vn41tboKOICwxsZpvYqC61bBuI5zBTMv14sWA8Ml4/Mt2 AD9TNigPyqltA2N90K91nrW5+vr6+Mp1+c+99827mqt7Rrzl+8lx9cFlz6ZbGBsfm7W6NTIx+s12 98ZwY4udJb7tlrC0XV9jXajG+Mq4k7G++s14787BYIKSMS4vkKex88V1+shugJeg98lqzM3PrK6x YICiwZpUZHJkboakvb6/mrO4+81ymLCu0a6g/c+9/812/810/8twnoFI/814/8tyl7G7mbO9m7O9 /896lbG7/8tuNsf1/8ls/816/894mLO//8luPMf1/cu4/8tsQT8/4OLi/c94/8989/f4/c12/c14 oZGL/892+8ez8PHx/c10/c92/ctw/8lml7O9/ctz3d7f7e3u/Mlz/8lw/clum7O//clw/ctul6+5 wcLEt5+W98t46sKxmbG90tTVPsPvMT1CQE9UPMn1/c963byunrO8/NPBIx8g/857WANu5wAA3sRJ REFUeNrsvQ1YlOeZ9w23jkoAIxioKFRMQOrIuOAjjZZEaRhAJcK4CjEdSg2RJtEniFFsaw3bmhjM pocmttpg2q6SNBJNClu3dWsozjCFmXkGccFZkjcpm/ZtSLJ9ehzW+KZ6LHXy3tfH/TGfDF/CwP+P H4jDCMj5u87zvM6PsP8FQdCUVdj/+hyCoCkqAACCAAAIggAACIIAAAiCAAAIggAACIIAAAiCAAAI ggAACIIAAAiCAAAIggAACIIAAAiCAAAIggAACIIAAAiCAAAIggAACIIAAAiCAAAIggAACIIAAAiC AAAIggAACIIAAAiCAAAIggAACIIAAAiCAAAIggAACIIAAAiCAAAIggAACIIAAAiCAAAIggAACIIA AAgCAAAACAIAIAgCACAIAgAgCAIAIAgCACAIAgAgCAIAIAgCACAIAgAgCAIAIAgCACAIAgAgCAIA IAgCACAIAgAgCAIAIAgCACAIAgAgCAIAIAgCACAIAgAgCAIAIAgCACAIAgAgCAIAIAgCACAIAgAg CAIAIAgCACAIAgAgCAIAIAgCACAIAgAgCAIAIAgCACAIAgAgCAAAACAIAIAgCACAIAgAgCAIAIAg CACAIAgAgCAIAIAgCACAIAgAgCAIAIAgCACAIAgAgCAIAIAgCACAIAgAgCAIAIAgCACAIAgAgCAI AIAgCACAIAgAgCAIAIAgCACAIAgAgCAIAIAgCACAIAgAgCAIAIAgCACAIAgAgCAIAIAgCACAIAgA gCAIAIAgCACAIAgAgCAAAF8ECAIAIAgCACAIAgAgCAIAIAgCACAIAgAgCAIAIAgCACAIAgAgCAIA IAgCACAIAgAgCAIAIAgCACAIAgAgCAIAIAgCACAIAgAgCAIAIAgCACAIAgAgCAIAIAgCACAIAgAg CAIAIAgCACAIAgAgCAIAIAgCACAIAgAgCAIAIAgCACAIAgAgCAIAIAgCACAIAgAgCACAIAgAgCAI AIAgCACAIAgAgCAIAIAgCACAIAgAgCAIAIAgCACAIAgAgCAIAIAgCACAIAgAgCAIAIAgCACAIAgA gCAIAIAgCACAIAgAgCAIAIAgCACAIAgAgCAIAIAgCACAIAgAgCAIAIAgCACAIAgAgCAIAIAgCACA IAgAgCAIAIAgCACAIAgAgCAIAIAgCACAIAAAgiAAAIIgAACCIAAAgiAAAIIgAACCIAAAgiAAAIIg AACCIAAAgiAAAIIgAACCIAAAgiAAAIIgAACCIAAAgiAAAIIgAACCIAAAgiAAAIIgAACCIAAAgiAA AIIgAACCIAAAgiAAAIIgAACCIAAAgiAAAIIgAACCIAAAgiAAAIIgAACCIAAAgiAAAIIgAACCIAAA ggAACIIAAAiCAAAIggAACIIAAAiCAAAIggAACIIAAAiCAAAIggAACIIAAAiCAAAIggAACIIAAAiC AAAIggAACIIAAAiCAAAIggAACIIAAAiCAAAIggAACIIAAAiCAAAIggAACIIAAAiCAAAIggAACIIA AAiCAAAIggAACIIAAAiCAIAJrcv4EkAAAARBAAAEQQAABEEAAARBAAAEQQAABEEAAARBAAAEQQAA BEEAAARBAAAEQQAABEEAwPiqvR1fAwgAmMIEwJcAAgAgCAIAIAgCACCfwlAQCACYwnmAKQaAdi58 fwAAIMBU/KK0m6nwxQEA4NxPua8IOfvNIAAAAE1JmdXClwMAgKYgAF577b9BAAAAmiIhv0pmC9H9 R++fUwgAAADQVDD9l3ep9G+iCu/Pzr7/YKEoXAUAANBkNf53XxX16ZGaDZ56LDE7NvuFf/vlL197 H9eBAAA0CY3/929+euTIYaLKBS3L1Dq7bFlBbKwIgMOHaw6nHjny6ZvvAgIAADSZvP7ft3+aWlO5 YFmMMcYYZbQZY9xejOcOxWbHfu1U919iFlRW1qR+2v7uu0AAAABNAtN/l0X6qZVry9YeN0apFENe 6FuMp2+ILsBm8bXuMvFRaxfU1NSkvo/6QAAACumQ/+VXd6XWHGDadvYvxm7F+o3Gskw9eaF/EkOA bBEAdvL2blv3NlEHNux6ede7QAAAAIWm/X965PCGDRsWZNpjRC1rzsqqUJ3/9rJT+kwqo81mOxUp OgCxNxXfoNsWs2yb+N4kJZB65NN3gQAAAAolvfvmq4crFzi6l8Uwk9ZEulzNfxcN326n5/+pzNYW +gejTaPRaSJnxZ6MvVnB/5b7CN3LlmVWEh3e9ea7+JICAFDInP7mV2tqDtha5PPe3pzsSm7+i7El M1Pf2tIvvojnvpFKBIBGc+6Q6AHsz1qpf6UlhrxResfj9qhTUVHbKmuOIBAAAKCQiPxJoU9qzbZt p3jEbxSdgL+sTHS5VrYStRDbl6yfAMAqioYAsQWtp1tb9WX6zBaVHyD+OL5t24KaI2YgAACAJj4A Xny5ZsOBbTEsvy8asF6vbz29MsPlerb5dKvDyk1fyQZUWK9edWwW7f/QHo3ubGtZq/gOLUaaJ+QP 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 --=_LD5EcWzbxCyMHu2m-anR-w9-- --=_EYQ5GjJ9ihZDst-d1a81_w3 Content-Type: application/pkcs7-signature; name=smime.p7s Content-Description: S/MIME Signature Content-Disposition: attachment; filename=smime.p7s Content-Transfer-Encoding: base64 MIIW6QYJKoZIhvcNAQcCoIIW2jCCFtYCAQExDzANBglghkgBZQMEAgEFADALBgkqhkiG9w0BBwGg ghQGMIIGPzCCBSegAwIBAgIDCub6MA0GCSqGSIb3DQEBBQUAMIGMMQswCQYDVQQGEwJJTDEWMBQG A1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUg U2lnbmluZzE4MDYGA1UEAxMvU3RhcnRDb20gQ2xhc3MgMSBQcmltYXJ5IEludGVybWVkaWF0ZSBD bGllbnQgQ0EwHhcNMTQwODIwMDAzMDMzWhcNMTUwODIwMTkyODE4WjBbMRkwFwYDVQQNExA3MENO ODVGZU9jVzdVaGM2MRswGQYDVQQDDBJtcnViaW5za0Bob3JkZS5vcmcxITAfBgkqhkiG9w0BCQEW Em1ydWJpbnNrQGhvcmRlLm9yZzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALhwAuB3 QSKeGGkaj76NDCNZ/9Rmv67dm9CISZBtMjN3LYWVSFUDQ4HCbYtZvmiXHW2sHse6f9yelEbey1oI n9ZycAG/EEEozTUZ3u/DmWbXFRF/HFmvd4QkVZ0CfCjvsM23HjJicXxF5Gdd5FPePp7YB4qg1wAu DGkDj34B0pODNb1hIkBt57bYc8wMMorhvl4+VeKJ5XeXnb90Wuc7yukxGXikpCZy3CL4UVC1Rk65 7ISodSYyhP6s9li49CYANFVagqLz+9SgXOEGTkPwtkb3Qr9d+nUEhRJdOlD3M/NQvnsnDoyu767z H1a4Jc/mIZ9xZxOOHdsomxUtp1hHw+sCAwEAAaOCAtgwggLUMAkGA1UdEwQCMAAwCwYDVR0PBAQD AgSwMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDBDAdBgNVHQ4EFgQUzm+FuTXGfuSxT8K5 3NRmsMo/KE8wHwYDVR0jBBgwFoAUU3Ltkpzg2ssBXHx+ljVO8tS4UYIwHQYDVR0RBBYwFIESbXJ1 Ymluc2tAaG9yZGUub3JnMIIBTAYDVR0gBIIBQzCCAT8wggE7BgsrBgEEAYG1NwECAzCCASowLgYI KwYBBQUHAgEWImh0dHA6Ly93d3cuc3RhcnRzc2wuY29tL3BvbGljeS5wZGYwgfcGCCsGAQUFBwIC MIHqMCcWIFN0YXJ0Q29tIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MAMCAQEagb5UaGlzIGNlcnRp ZmljYXRlIHdhcyBpc3N1ZWQgYWNjb3JkaW5nIHRvIHRoZSBDbGFzcyAxIFZhbGlkYXRpb24gcmVx dWlyZW1lbnRzIG9mIHRoZSBTdGFydENvbSBDQSBwb2xpY3ksIHJlbGlhbmNlIG9ubHkgZm9yIHRo ZSBpbnRlbmRlZCBwdXJwb3NlIGluIGNvbXBsaWFuY2Ugb2YgdGhlIHJlbHlpbmcgcGFydHkgb2Js aWdhdGlvbnMuMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwuc3RhcnRzc2wuY29tL2NydHUx LWNybC5jcmwwgY4GCCsGAQUFBwEBBIGBMH8wOQYIKwYBBQUHMAGGLWh0dHA6Ly9vY3NwLnN0YXJ0 c3NsLmNvbS9zdWIvY2xhc3MxL2NsaWVudC9jYTBCBggrBgEFBQcwAoY2aHR0cDovL2FpYS5zdGFy dHNzbC5jb20vY2VydHMvc3ViLmNsYXNzMS5jbGllbnQuY2EuY3J0MCMGA1UdEgQcMBqGGGh0dHA6 Ly93d3cuc3RhcnRzc2wuY29tLzANBgkqhkiG9w0BAQUFAAOCAQEAXlCyZJlNzWD42T/Pw9NUhSGX 3rS8d+PiYpaM4ahs1+K0E98HcjPzdpVzjiQ7FNmQuafnIP73GRxkQ1POKxv0LNtBs8uddtUVCP5j oOaiLY9NSCI/L7ZgkZKJF535oSUMEyroKvmnQYGNOIpoRH5k+/6UbpThGrffLCA3GA3eSPR1CsSp Slf9awZcOhSRPIHdxCT00kxSkzJzme4CeXxBW1Xm1ncgeVNNH41s0BnOFwzIiBaYfSJk2DuafX6q n16u8JmuY7a+kxY03OEEknfe+nC+vZOiMi5JS2n/XGrt63MVTJMQjkrNffNoHp+9RGJ7q7IKaG5t G0h/bSqhh2MMOjCCBjQwggQcoAMCAQICAR4wDQYJKoZIhvcNAQEFBQAwfTELMAkGA1UEBhMCSUwx FjAUBgNVBAoTDVN0YXJ0Q29tIEx0ZC4xKzApBgNVBAsTIlNlY3VyZSBEaWdpdGFsIENlcnRpZmlj YXRlIFNpZ25pbmcxKTAnBgNVBAMTIFN0YXJ0Q29tIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4X DTA3MTAyNDIxMDE1NVoXDTE3MTAyNDIxMDE1NVowgYwxCzAJBgNVBAYTAklMMRYwFAYDVQQKEw1T dGFydENvbSBMdGQuMSswKQYDVQQLEyJTZWN1cmUgRGlnaXRhbCBDZXJ0aWZpY2F0ZSBTaWduaW5n MTgwNgYDVQQDEy9TdGFydENvbSBDbGFzcyAxIFByaW1hcnkgSW50ZXJtZWRpYXRlIENsaWVudCBD QTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMcJg8zOLdgasSmkLhOrlr6KMoOMpohB llVHrdRvEg/q6r8jR+EK75xCGhR8ToREoqe7zM9/UnC6TS2y9UKTpT1v7RSMzR0t6ndl0TWBuUr/ UXBhPk+Kmy7bI4yW4urC+y7P3/1/X7U8ocb8VpH/Clt+4iq7nirMcNh6qJR+xjOhV+VHzQMALuGY n5KZmc1NbJQYclsGkDxDz2UbFqE2+6vIZoL+jb9x4Pa5gNf1TwSDkOkikZB1xtB4ZqtXThaABSON dfmv/Z1pua3FYxnCFmdr/+N2JLKutIxMYqQOJebr/f/h5t95m4JgrM3Y/w7YX9d7YAL9jvN4SydH sU6n65cCAwEAAaOCAa0wggGpMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1Ud DgQWBBRTcu2SnODaywFcfH6WNU7y1LhRgjAfBgNVHSMEGDAWgBROC+8apEBbpRdphzDKNGhD0EGu 8jBmBggrBgEFBQcBAQRaMFgwJwYIKwYBBQUHMAGGG2h0dHA6Ly9vY3NwLnN0YXJ0c3NsLmNvbS9j YTAtBggrBgEFBQcwAoYhaHR0cDovL3d3dy5zdGFydHNzbC5jb20vc2ZzY2EuY3J0MFsGA1UdHwRU MFIwJ6AloCOGIWh0dHA6Ly93d3cuc3RhcnRzc2wuY29tL3Nmc2NhLmNybDAnoCWgI4YhaHR0cDov L2NybC5zdGFydHNzbC5jb20vc2ZzY2EuY3JsMIGABgNVHSAEeTB3MHUGCysGAQQBgbU3AQIBMGYw LgYIKwYBBQUHAgEWImh0dHA6Ly93d3cuc3RhcnRzc2wuY29tL3BvbGljeS5wZGYwNAYIKwYBBQUH AgEWKGh0dHA6Ly93d3cuc3RhcnRzc2wuY29tL2ludGVybWVkaWF0ZS5wZGYwDQYJKoZIhvcNAQEF BQADggIBAAqDCH14qywGXLhjjF6uHLkjd02hcdh9hrw+VUsv+q1eeQWB21jWj3kJ96AUlPCoEGZ/ ynJNScWy6QMVQjbbMXltUfO4n4bGGdKo3awPWp61tjAFgraLJgDk+DsSvUD6EowjMTNx25GQgyYJ 5RPIzKKR9tQW8gGK+2+RHxkUCTbYFnL6kl8Ch507rUdPPipJ9CgJFws3kDS3gOS5WFMxcjO5DwKf KSETEPrHh7p5shuuNktvsv6hxHTLhiMKX893gxdT3XLS9OKmCv87vkINQcNEcIIoFWbP9HORz9v3 vQwR4e3ksLc2JZOAFK+ssS5XMEoznzpihEP0PLc4dCBYjbvSD7kxgDwZ+Aj8Q9PkbvE9sIPP7ON0 fz095HdThKjiVJe6vofq+n6b1NBc8XdrQvBmunwxD5nvtTW4vtN6VY7mUCmxsCieuoBJ9OlqmsVW QvifIYf40dJPZkk9YgGTzWLpXDSfLSplbY2LL9C9U0ptvjcDjefLTvqSFc7tw1sEhF0n/qpA2r0G pvkLRDmcSwVyPvmjFBGqUp/pNy8ZuPGQmHwFi2/14+xeSUDG2bwnsYJQG2EdJCB6luQ57GEnTA/y KZSTKI8dDQa8Sd3zfXb19mOgSF0bBdXbuKhEpuP9wirslFe6fQ1t5j5R0xi72MZ8ikMu1RQZKCyD bMwazlHiMIIHhzCCBW+gAwIBAgIBLTANBgkqhkiG9w0BAQsFADB9MQswCQYDVQQGEwJJTDEWMBQG A1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUg U2lnbmluZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDYw OTE3MTk0NjM3WhcNMzYwOTE3MTk0NjM2WjB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRD b20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcG A1UEAxMgU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUA A4ICDwAwggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZkpMyONvg45iPw bm2xPN1yo4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rfOQVSWff0G0ZDpNKFhdLDcfN1 YjS6LIp/Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/CJi/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+ 7bWgiA/deMotHweXMAEtcnn6RtYTKqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfk K+F2PrRt2PZE4XNiHzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb 6kMMAv+Z6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w+2OqqGwa VLRcJXrJosmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+Gkhpi8KWTRoSsmkXwQqQ 1vp5Iki/untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ +xjGtrVcUjyJthkqcwEKDwOzEmDyei+B26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbj M4xdCUsT37uMdBNSSwIDAQABo4ICEDCCAgwwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC AQYwHQYDVR0OBBYEFE4L7xqkQFulF2mHMMo0aEPQQa7yMB8GA1UdIwQYMBaAFE4L7xqkQFulF2mH MMo0aEPQQa7yMIIBWgYDVR0gBIIBUTCCAU0wggFJBgsrBgEEAYG1NwEBATCCATgwLgYIKwYBBQUH AgEWImh0dHA6Ly93d3cuc3RhcnRzc2wuY29tL3BvbGljeS5wZGYwNAYIKwYBBQUHAgEWKGh0dHA6 Ly93d3cuc3RhcnRzc2wuY29tL2ludGVybWVkaWF0ZS5wZGYwgc8GCCsGAQUFBwICMIHCMCcWIFN0 YXJ0IENvbW1lcmNpYWwgKFN0YXJ0Q29tKSBMdGQuMAMCAQEagZZMaW1pdGVkIExpYWJpbGl0eSwg cmVhZCB0aGUgc2VjdGlvbiAqTGVnYWwgTGltaXRhdGlvbnMqIG9mIHRoZSBTdGFydENvbSBDZXJ0 aWZpY2F0aW9uIEF1dGhvcml0eSBQb2xpY3kgYXZhaWxhYmxlIGF0IGh0dHA6Ly93d3cuc3RhcnRz c2wuY29tL3BvbGljeS5wZGYwEQYJYIZIAYb4QgEBBAQDAgAHMDgGCWCGSAGG+EIBDQQrFilTdGFy dENvbSBGcmVlIFNTTCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTANBgkqhkiG9w0BAQsFAAOCAgEA jo/n3JR5fPGFf59Jb2vKXfuM/gTFwWLRfUKKvFO3lANmMD+x5wqnUCBVJX92ehQN6wQOQOY+2Iir ByeDqXWmN3PH/UvSTa0XQMhGvjt/UfzDtgUx3M2FIk5xt/JxXrAaxrqTi3iSSoX4eA+D/i+tLPfk pLst0OcNOrg+zvZ49q5HJMqjNTbOx8aHmNrs++myziebiMMEofYLWWivydsQD032ZGNcpRJvkrKT lMeIFw6Ttn5ii5B/q06f/ON1FE8qMt9bDeD1e5MNq6HPh+GlBEXoPBKlCcWw0bdT82AUuoVpaiF8 H3VhFyAXe2w7QSlc4axa0c2Mm+tgHRns9+Ww2vl5GKVFP0lDV9LdJNUso/2RjSe15esUBppMeyG7 Oq0wBhjA2MFrLH9ZXF2RsXAiV+uKa0hK1Q8p7MZAwC+ITGgBF3f0JBlPvfrhsiAhS90a2Cl9qrje VOwhVYBsHvUwyKMQ5bLmKhQxw4UtjJixhlpPiVktucf3HMiKf8CdBUrmQk9io20ppB+Fq9vlgcit Kj1MXVuEJnHEhV5xJMqlG2zYYdMa4FTbzrqpMrUi9nNBCV24F10OD5mQ1kfabwo6YigUZ4LZ8dCA WZvLMdibD4x3TrVoivJs9iQOLWxwxXPR3hTQcY+203sC9uO41Alua551hDnmfyWl8kgAwKQB2j8x ggKnMIICowIBATCBlDCBjDELMAkGA1UEBhMCSUwxFjAUBgNVBAoTDVN0YXJ0Q29tIEx0ZC4xKzAp BgNVBAsTIlNlY3VyZSBEaWdpdGFsIENlcnRpZmljYXRlIFNpZ25pbmcxODA2BgNVBAMTL1N0YXJ0 Q29tIENsYXNzIDEgUHJpbWFyeSBJbnRlcm1lZGlhdGUgQ2xpZW50IENBAgMK5vowDQYJYIZIAWUD BAIBBQCggeQwGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMTQwOTA4 MTQ0MzQ2WjAvBgkqhkiG9w0BCQQxIgQgt7o/WJgBA9RymXdcvr8eOTiJn/C7jGPmot++KbejuJEw eQYJKoZIhvcNAQkPMWwwajALBglghkgBZQMEASowCwYJYIZIAWUDBAEWMAsGCWCGSAFlAwQBAjAK BggqhkiG9w0DBzAOBggqhkiG9w0DAgICAIAwDQYIKoZIhvcNAwICAUAwBwYFKw4DAgcwDQYIKoZI hvcNAwICASgwDQYJKoZIhvcNAQEBBQAEggEAiSOsHl1o15yI27pLG3ERo+IzP1GYbad45keRb/pf N6NQF/MrZOB/uL5fNg9bUjbtmauZaZVyyvxCQUGTos1tHA6QURs2K/lmnK1wo6nBMmmp45rLwGu9 PNbSRa0yrEcNyD1PH4zxIsAp4gWMACxhPTaS69IocTkq3TYkYomJewGu/7yrwloHdfavWeMvlsNn ICAYcDGBBQzzHL6OD2Zf+4Ab+7p7Hvo9Z4VmrLqUQCiiG2GM/19NVLj2OmMD/S0cy7F/J638CucD eysD7F0sufCIj1fRn3bU2DmjMnBkp9ySpbfm1+9wsN/ANfN8o7eW9Ao9RoKmDeSUPDYUJ58JxA== --=_EYQ5GjJ9ihZDst-d1a81_w3-- Horde_ActiveSync-2.31.1/test/Horde/ActiveSync/fixtures/simpleexception.wbxml0000664000076500000240000000065412654565405024154 0ustar ]ELAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsAAAABAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAACAAIAAAAAAAAAxP///w==g20111201T200000ZR20111201T210000ZfEvent TitleWPhiladelphia, PAM2[\1_2`16e1Q20111201T200000ZX0TSV20111229T200000ZU1L0KEvent DescriptionL0Horde_ActiveSync-2.31.1/test/Horde/ActiveSync/StateTest/Mongo/BaseTest.php0000664000076500000240000001251312654565405023237 0ustar * @license http://www.horde.org/licenses/gpl GPLv2 * @category Horde * @package Horde_ActiveSync * @subpackage UnitTests */ class Horde_ActiveSync_StateTest_Mongo_BaseTest extends Horde_ActiveSync_StateTest_Base { protected static $db; protected static $reason; public function testGetDeviceInfo() { $this->_testGetDeviceInfo(); } /** * @depends testGetDeviceInfo */ public function testListDevices() { $this->_testListDevices(); } /** * @depends testListDevices */ public function testPolicyKeys() { $this->_testPolicyKeys(); } /** * @depends testListDevices */ public function testDuplicatePIMAddition() { // @TODO. For now, cheat and add the data directly to the db. try { $mongo = new Horde_Mongo_Client(); $mongo->activesync_test->HAS_map->insert(array( 'sync_clientid' => 'abc', 'sync_user' => 'mike', 'message_uid' => 'def', 'sync_devid' => 'dev123')); self::$state->loadDeviceInfo('dev123', 'mike'); $this->assertEquals('def', self::$state->isDuplicatePIMAddition('abc')); } catch (MongoConnectionException $e) {} } /** * @depends testGetDeviceInfo */ public function testCacheInitialState() { $this->_testCacheInitialState(); } /** * @depends testCacheInitialState */ public function testCacheFolders() { $this->_testCacheFolders(); } /** * @depends testCacheFolders */ public function testCacheDataRestrictFields() { $this->_testCacheDataRestrictFields(); } /** * @depends testCacheFolders */ public function testCacheFoldersPersistence() { $this->_testCacheFoldersPersistence(); } /** * @depends testCacheFolders */ public function testCacheUniqueness() { $this->_testCacheUniqueness(); } /** * @depends testCacheFolders */ public function testCacheCollections() { $this->_testCacheCollections(); } /** * @depends testCacheCollections */ public function testLoadCollectionsFromCache() { return $this->_testLoadCollectionsFromCache(); } /** * @depends testCacheCollections */ public function testGettingImapId() { $this->_testGettingImapId(); } /** * @depends testCacheCollections */ public function testCacheRefreshCollections() { $this->_testCacheRefreshCollections(); } /** * @depends testCacheCollections */ public function testCollectionsFromCache() { $this->_testCollectionsFromCache(); } /** * @depends testCacheFolders */ public function testGetStateWithNoState() { $this->_testGetStateWithNoState(); } /** * @depends testCollectionsFromCache */ public function testCollectionHandler() { $this->_testCollectionHandler(); } /** * @depends testCollectionHandler */ public function testPartialSyncWithChangedCollections() { $this->_testPartialSyncWithChangedCollections(); } /** * @depends testCollectionHandler */ public function testPartialSyncWithUnchangedCollections() { $this->_testPartialSyncWithUnchangedCollections(); } /** * @depends testCollectionHandler */ public function testMissingCollections() { $this->_testMissingCollections(); } /** * @depends testCollectionHandler */ public function testChangingFilterType() { $this->_testChangingFilterType(); } /** * @depends testCollectionHandler */ public function testEmptyResponse() { $this->_testEmptyResponse(); } /** * @depends testGetDeviceInfo */ public function testHierarchy() { $this->_testHierarchy(); } /** * @depends testCollectionHandler */ public function testPartialSyncWithOnlyChangedHbInterval() { $this->_testPartialSyncWithOnlyChangedHbInterval(); } public static function tearDownAfterClass() { if (class_exists('MongoDB')) { try { $mongo = new Horde_Mongo_Client(); $mongo->activesync_test->drop(); } catch (MongoConnectionException $e) { } } parent::tearDownAfterClass(); } public function setUp() { if (!class_exists('MongoDB')) { $this->markTestSkipped('MongoDB extension not loaded.'); return; } try { $mongo = new Horde_Mongo_Client(); } catch (MongoConnectionException $e) { $this->markTestSkipped('Mongo connection failed.'); return; } $mongo->dbname = 'activesync_test'; self::$state = new Horde_ActiveSync_State_Mongo(array('connection' => $mongo)); $backend = $this->getMockSkipConstructor('Horde_ActiveSync_Driver_Base'); $backend->expects($this->any())->method('getUser')->will($this->returnValue('mike')); self::$state->setBackend($backend); self::$logger = new Horde_Test_Log(); } } Horde_ActiveSync-2.31.1/test/Horde/ActiveSync/StateTest/Sql/Pdo/MysqlTest.php0000664000076500000240000000170012654565405023670 0ustar * @license http://www.horde.org/licenses/gpl GPLv2 * @category Horde * @package Horde_ActiveSync * @subpackage UnitTests */ class Horde_ActiveSync_StateTest_Sql_Pdo_MysqlTest extends Horde_ActiveSync_StateTest_Sql_Base { public static function setUpBeforeClass() { if (!extension_loaded('pdo') || !in_array('mysql', PDO::getAvailableDrivers())) { self::$reason = 'No mysql extension or no mysql PDO driver'; return; } $config = self::getConfig('ACTIVESYNC_SQL_PDO_MYSQL_TEST_CONFIG', dirname(__FILE__) . '/../../..'); if ($config && !empty($config['activesync']['sql']['pdo_mysql'])) { self::$db = new Horde_Db_Adapter_Pdo_Mysql($config['activesync']['sql']['pdo_mysql']); parent::setUpBeforeClass(); } else { self::$reason = 'No pdo_mysql configuration'; } } } Horde_ActiveSync-2.31.1/test/Horde/ActiveSync/StateTest/Sql/Pdo/SqliteTest.php0000664000076500000240000000113612654565405024027 0ustar * @license http://www.horde.org/licenses/gpl GPLv2 * @category Horde * @package Horde_ActiveSync * @subpackage UnitTests */ class Horde_ActiveSync_StateTest_Sql_Pdo_SqliteTest extends Horde_ActiveSync_StateTest_Sql_Base { public static function setUpBeforeClass() { $factory_db = new Horde_Test_Factory_Db(); try { self::$db = $factory_db->create(); parent::setUpBeforeClass(); } catch (Horde_Test_Exception $e) { self::$reason = 'Sqlite not available'; } } } Horde_ActiveSync-2.31.1/test/Horde/ActiveSync/StateTest/Sql/Base.php0000664000076500000240000001220012654565405022050 0ustar * @license http://www.horde.org/licenses/gpl GPLv2 * @category Horde * @package Horde_ActiveSync * @subpackage UnitTests */ class Horde_ActiveSync_StateTest_Sql_Base extends Horde_ActiveSync_StateTest_Base { protected static $db; protected static $migrator; protected static $reason; public function testGetDeviceInfo() { $this->_testGetDeviceInfo(); } /** * @depends testGetDeviceInfo */ public function testCacheInitialState() { $this->_testCacheInitialState(); } /** * @depends testCacheInitialState */ public function testCacheFolders() { $this->_testCacheFolders(); } /** * @depends testCacheFolders */ public function testCacheDataRestrictFields() { $this->_testCacheDataRestrictFields(); } /** * @depends testCacheFolders */ public function testCacheFoldersPersistence() { $this->_testCacheFoldersPersistence(); } /** * @depends testCacheFolders */ public function testCacheUniqueness() { $this->_testCacheUniqueness(); } /** * @depends testCacheFolders */ public function testCacheCollections() { $this->_testCacheCollections(); } /** * @depends testCacheCollections */ public function testLoadCollectionsFromCache() { return $this->_testLoadCollectionsFromCache(); } /** * @depends testCacheCollections */ public function testGettingImapId() { $this->_testGettingImapId(); } /** * @depends testCacheCollections */ public function testCacheRefreshCollections() { $this->_testCacheRefreshCollections(); } /** * @depends testCacheCollections */ public function testCollectionsFromCache() { $this->_testCollectionsFromCache(); } /** * @depends testCacheFolders */ public function testGetStateWithNoState() { $this->_testGetStateWithNoState(); } /** * @depends testCollectionsFromCache */ public function testCollectionHandler() { $this->_testCollectionHandler(); } /** * @depends testCollectionHandler */ public function testPartialSyncWithChangedCollections() { $this->_testPartialSyncWithChangedCollections(); } /** * @depends testCollectionHandler */ public function testPartialSyncWithUnchangedCollections() { $this->_testPartialSyncWithUnchangedCollections(); } /** * @depends testCollectionHandler */ public function testMissingCollections() { $this->_testMissingCollections(); } /** * @depends testCollectionHandler */ public function testChangingFilterType() { $this->_testChangingFilterType(); } /** * @depends testCollectionHandler */ public function testEmptyResponse() { $this->_testEmptyResponse(); } /** * @depends testGetDeviceInfo */ public function testHierarchy() { $this->_testHierarchy(); } /** * @depends testGetDeviceInfo */ public function testListDevices() { $this->_testListDevices(); } /** * @depends testListDevices */ public function testPolicyKeys() { $this->_testPolicyKeys(); } /** * @depends testCollectionHandler */ public function testPartialSyncWithOnlyChangedHbInterval() { $this->_testPartialSyncWithOnlyChangedHbInterval(); } public static function setUpBeforeClass() { $dir = dirname(__FILE__) . '/../../../../../migration/Horde/ActiveSync'; if (!is_dir($dir)) { error_reporting(E_ALL & ~E_DEPRECATED); $dir = PEAR_Config::singleton() ->get('data_dir', null, 'pear.horde.org') . '/Horde_ActiveSync/migration'; error_reporting(E_ALL | E_STRICT); } self::$logger = new Horde_Test_Log(); if (self::$db) { self::$migrator = new Horde_Db_Migration_Migrator( self::$db, self::$logger->getLogger(), array('migrationsPath' => $dir, 'schemaTableName' => 'horde_activesync_schema_info')); self::$migrator->up(); } } public static function tearDownAfterClass() { if (self::$db) { if (self::$migrator) { self::$migrator->down(); } self::$db->disconnect(); self::$db = null; } parent::tearDownAfterClass(); } public function setUp() { if (!self::$db) { $this->markTestSkipped(self::$reason); return; } self::$state = new Horde_ActiveSync_State_Sql(array('db' => self::$db)); $backend = $this->getMockSkipConstructor('Horde_ActiveSync_Driver_Base'); $backend->expects($this->any())->method('getUser')->will($this->returnValue('mike')); self::$state->setBackend($backend); } }Horde_ActiveSync-2.31.1/test/Horde/ActiveSync/StateTest/Sql/MysqliTest.php0000664000076500000240000000153312654565405023323 0ustar * @license http://www.horde.org/licenses/gpl GPLv2 * @category Horde * @package Horde_ActiveSync * @subpackage UnitTests */ class Horde_ActiveSync_StateTest_Sql_MysqliTest extends Horde_ActiveSync_StateTest_Sql_Base { public static function setUpBeforeClass() { if (!extension_loaded('mysqli')) { self::$reason = 'No mysqli extension'; return; } $config = self::getConfig('ACTIVESYNC_SQL_MYSQLI_TEST_CONFIG', dirname(__FILE__) . '/../..');; if ($config && !empty($config['activesync']['sql']['mysqli'])) { self::$db = new Horde_Db_Adapter_Mysqli($config['activesync']['sql']['mysqli']); parent::setUpBeforeClass(); } else { self::$reason = 'No mysqli configuration'; } } }Horde_ActiveSync-2.31.1/test/Horde/ActiveSync/StateTest/Sql/MysqlTest.php0000664000076500000240000000152312654565405023151 0ustar * @license http://www.horde.org/licenses/gpl GPLv2 * @category Horde * @package Horde_ActiveSync * @subpackage UnitTests */ class Horde_ActiveSync_StateTest_Sql_MysqlTest extends Horde_ActiveSync_StateTest_Sql_Base { public static function setUpBeforeClass() { if (!extension_loaded('mysql')) { self::$reason = 'No mysql extension.'; return; } $config = self::getConfig('ACTIVESYNC_SQL_MYSQL_TEST_CONFIG', dirname(__FILE__) . '/../..'); if ($config && !empty($config['activesync']['sql']['mysql'])) { self::$db = new Horde_Db_Adapter_Mysql($config['activesync']['sql']['mysql']); parent::setUpBeforeClass(); } else { self::$reason = 'No mysql configuration'; } } }Horde_ActiveSync-2.31.1/test/Horde/ActiveSync/StateTest/Sql/Oci8Test.php0000664000076500000240000000151512654565405022647 0ustar * @license http://www.horde.org/licenses/gpl GPLv2 * @category Horde * @package Horde_ActiveSync * @subpackage UnitTests */ class Horde_ActiveSync_StateTest_Sql_Oci8Test extends Horde_ActiveSync_StateTest_Sql_Base { public static function setUpBeforeClass() { if (!extension_loaded('oci8')) { self::$reason = 'No oci8 extension.'; return; } $config = self::getConfig('ACTIVESYNC_SQL_OCI8_TEST_CONFIG', dirname(__FILE__) . '/../..'); if ($config && !empty($config['activesync']['sql']['oci8'])) { self::$db = new Horde_Db_Adapter_Oci8($config['activesync']['sql']['oci8']); parent::setUpBeforeClass(); } else { self::$reason = 'No oci8 configuration'; } } }Horde_ActiveSync-2.31.1/test/Horde/ActiveSync/StateTest/Base.php0000664000076500000240000005553112654565405021327 0ustar * @license http://www.horde.org/licenses/gpl GPLv2 * @category Horde * @package Horde_ActiveSync * @subpackage UnitTests */ class Horde_ActiveSync_StateTest_Base extends Horde_Test_Case { protected static $state; protected static $logger; protected function _testGetDeviceInfo() { // First with no existing deivce. $this->assertEquals(false, (boolean)self::$state->deviceExists('dev123', 'mike')); // Can't use setExpectedException here since it stops the rest // of the method from running when it's thrown. try { self::$state->loadDeviceInfo('dev123', 'mike'); $this->fail('Did not raise expected Horde_ActiveSync_Exception.'); } catch (Horde_ActiveSync_Exception $e) { } // Add the device, then retreive it. $deviceInfo = new Horde_ActiveSync_Device(self::$state); $deviceInfo->rwstatus = 0; $deviceInfo->deviceType = 'Test Device'; $deviceInfo->userAgent = 'Horde Tests'; $deviceInfo->id = 'dev123'; $deviceInfo->user = 'mike'; $deviceInfo->policykey = 456; $deviceInfo->supported = array(); $deviceInfo->save(); $this->assertEquals(true, (boolean)self::$state->deviceExists('dev123', 'mike')); $di = self::$state->loadDeviceInfo('dev123', 'mike'); $this->assertEquals($deviceInfo, $di); } /** * @return [type] [description] */ protected function _testListDevices() { $devices = self::$state->listDevices(); $this->assertCount(1, $devices); $deviceInfo = new Horde_ActiveSync_Device(self::$state); $deviceInfo->rwstatus = 0; $deviceInfo->deviceType = 'Test Device'; $deviceInfo->userAgent = 'Horde Tests'; $deviceInfo->id = 'dev123'; $deviceInfo->user = 'ashley'; $deviceInfo->policykey = 123; $deviceInfo->supported = array(); $deviceInfo->save(); $devices = self::$state->listDevices(); $this->assertCount(2, $devices); } protected function _testPolicyKeys() { $device = self::$state->loadDeviceInfo('dev123', 'mike'); $this->assertEquals(456, $device->policykey); self::$state->setPolicyKey('dev123', 789); // Make sure it took without affected other data // (need to load a different device first, to clear // the local copy of the data) $device = self::$state->loadDeviceInfo('dev123', 'ashley'); $this->assertEquals(123, $device->policykey); $device = self::$state->loadDeviceInfo('dev123', 'mike'); $this->assertEquals(789, $device->policykey); self::$state->resetAllPolicyKeys(); $device = self::$state->loadDeviceInfo('dev123', 'ashley'); $this->assertEquals(0, $device->policykey); $device = self::$state->loadDeviceInfo('dev123', 'mike'); $this->assertEquals(0, $device->policykey); } protected function _testDuplicatePIMAddition() { // @TODO. Need to implement getChanges/saveState tests. } /** * Not much testing here yet, just run through a save and check for * fatals. * */ protected function _loadStateTest() { $this->markTestSkipped(); $collection = array( 'serverid' => '@Contacts@', 'folderid' => '@Contacts@', 'class' => Horde_ActiveSync::CLASS_CONTACTS); self::$state->loadDeviceInfo('dev123', 'mike'); self::$state->loadState($collection, 0, Horde_ActiveSync::REQUEST_TYPE_SYNC, 'abcdef'); self::$state->setNewSyncKey('{51941e99-0b9c-41f8-b678-1532c0a8015f}1'); self::$state->save(); } protected function _testCacheInitialState() { $cache = new Horde_ActiveSync_SyncCache(self::$state, 'dev123', 'mike', self::$logger->getLogger()); $this->assertEquals(array(), $cache->getCollections()); $this->assertEquals(array(), $cache->getCollections(true)); $this->assertEquals(0, $cache->countCollections()); $this->assertEquals(false, $cache->collectionExists('@Contacts@')); $this->assertEquals(false, $cache->collectionIsPingable('@Contacts@')); $this->assertEquals(false, $cache->collectionIsPingable('@Contacts@')); $this->assertEquals(array(), $cache->getFolders()); $this->assertEquals(false, $cache->getFolder('@Contacts@')); } protected function _testCacheFolders() { $log = new Horde_Test_Log(); $cache = new Horde_ActiveSync_SyncCache(self::$state, 'dev123', 'mike', self::$logger->getLogger()); // First Fixture $folder = new Horde_ActiveSync_Message_Folder((array('logger' => $log->getLogger(), 'protocolversion' => Horde_ActiveSync::VERSION_TWELVEONE))); $folder->type = Horde_ActiveSync::FOLDER_TYPE_CONTACT; $folder->serverid = '@Contacts@'; $folder->_serverid = '@Contacts@'; $cache->updateFolder($folder); // Second fixture $folder = new Horde_ActiveSync_Message_Folder((array('logger' => $log->getLogger(), 'protocolversion' => Horde_ActiveSync::VERSION_TWELVEONE))); $folder->type = Horde_ActiveSync::FOLDER_TYPE_INBOX; $folder->serverid = '519422f1-4c5c-4547-946a-1701c0a8015f'; $folder->_serverid = 'INBOX'; $cache->updateFolder($folder); $expected = array( '@Contacts@' => array( 'class' => 'Contacts', 'serverid' => '@Contacts@', 'type' => 9 ), '519422f1-4c5c-4547-946a-1701c0a8015f' => array( 'class' => 'Email', 'serverid' => 'INBOX', 'type' => 2 ) ); $this->assertEquals($expected, $cache->getFolders()); $expected = array( 'class' => 'Email', 'serverid' => 'INBOX', 'type' => 2 ); $this->assertEquals($expected, $cache->getFolder('519422f1-4c5c-4547-946a-1701c0a8015f')); $cache->save(); } protected function _testCacheDataRestrictFields() { $cache_data = self::$state->getSyncCache('dev123', 'mike', array('folders')); $this->assertCount(1, $cache_data); list($key, $value) = each($cache_data); $this->assertEquals('folders', $key); } protected function _testCacheFoldersPersistence() { $cache = new Horde_ActiveSync_SyncCache(self::$state, 'dev123', 'mike', self::$logger->getLogger()); $expected = array( '@Contacts@' => array( 'class' => 'Contacts', 'serverid' => '@Contacts@', 'type' => 9 ), '519422f1-4c5c-4547-946a-1701c0a8015f' => array( 'class' => 'Email', 'serverid' => 'INBOX', 'type' => 2 ) ); $this->assertEquals($expected, $cache->getFolders()); $expected = array( 'class' => 'Email', 'serverid' => 'INBOX', 'type' => 2 ); $this->assertEquals($expected, $cache->getFolder('519422f1-4c5c-4547-946a-1701c0a8015f')); } protected function _testCacheCollections() { $collections = array( '519422f1-4c5c-4547-946a-1701c0a8015f' => array( 'class' => 'Email', 'windowsize' => 5, 'truncation' => 0, 'mimesupport' => 0, 'mimetruncation' => 8, 'conflict' => 1, 'bodyprefs' => array( 'wanted' => 2, 2 => array( 'type' => 2, 'truncationsize' => 200000) ), 'deletesasmoves' => 1, 'filtertype' => 5, 'id' => '519422f1-4c5c-4547-946a-1701c0a8015f', 'serverid' => 'INBOX'), '@Contacts@' => array( 'class' => 'Contacts', 'windowsize' => 4, 'truncation' => 0, 'mimesupport' => 0, 'mimetruncation' => 8, 'conflict' => 1, 'bodyprefs' => array( 'wanted' => 1, 1 => array( 'type' => 1, 'truncationsize' => 200000) ), 'deletesasmoves' => 1, 'id' => '@Contacts@', 'serverid' => '@Contacts@') ); $cache = new Horde_ActiveSync_SyncCache(self::$state, 'dev123', 'mike', self::$logger->getLogger()); foreach ($collections as $collection) { $cache->addCollection($collection); } // Mangle the fixture to match what we expect. $collections['519422f1-4c5c-4547-946a-1701c0a8015f']['rtftruncation'] = null; $collections['@Contacts@']['rtftruncation'] = null; $collections['@Contacts@']['filtertype'] = null; $this->assertEquals(2, $cache->countCollections()); $this->assertEquals($collections, $cache->getCollections(false)); $this->assertEquals(array(), $cache->getCollections(true)); $this->assertEquals(true, $cache->collectionExists('@Contacts@')); $this->assertEquals(true, $cache->collectionExists('519422f1-4c5c-4547-946a-1701c0a8015f')); $this->assertEquals(false, $cache->collectionExists('foo')); $this->assertEquals(false, $cache->collectionIsPingable('@Contacts@')); $cache->setPingableCollection('@Contacts@'); $this->assertEquals(true, $cache->collectionIsPingable('@Contacts@')); $cache->removePingableCollection('@Contacts@'); $this->assertEquals(false, $cache->collectionIsPingable('@Contacts@')); $cache->updateCollection( array('id' => '519422f1-4c5c-4547-946a-1701c0a8015f', 'newsynckey' => '{51941e99-0b9c-41f8-b678-1532c0a8015f}2'), array('newsynckey' => true)); $cache->save(); // Now we should have a lastsynckey $this->assertEquals(1, count($cache->getCollections())); // And still have 2 if we don't need a synckey $this->assertEquals(2, count($cache->getCollections(false))); } protected function _testLoadCollectionsFromCache() { $collections = $this->getCollectionHandler(); $collections->loadCollectionsFromCache(); $this->assertEquals(2, $collections->collectionCount()); } protected function _testCollectionsFromCache() { $cache = new Horde_ActiveSync_SyncCache(self::$state, 'dev123', 'mike', self::$logger->getLogger()); $collections = array('519422f1-4c5c-4547-946a-1701c0a8015f' => array('id' => '519422f1-4c5c-4547-946a-1701c0a8015f')); $expected = array('519422f1-4c5c-4547-946a-1701c0a8015f' => array( 'class' => 'Email', 'windowsize' => 5, 'truncation' => 0, 'mimesupport' => 0, 'mimetruncation' => 8, 'bodyprefs' => array( 'wanted' => 2, 2 => array( 'type' => 2, 'truncationsize' => 200000) ), 'filtertype' => 5, 'id' => '519422f1-4c5c-4547-946a-1701c0a8015f', 'serverid' => 'INBOX', 'type' => 2)); $cache->validateCollectionsFromCache($collections); $this->assertEquals($expected, $collections); } protected function _testCacheRefreshCollections() { $cache = new Horde_ActiveSync_SyncCache(self::$state, 'dev123', 'mike', self::$logger->getLogger()); $newcache = new Horde_ActiveSync_SyncCache(self::$state, 'dev123', 'mike', self::$logger->getLogger()); $newcache->updateCollection( array('id' => '519422f1-4c5c-4547-946a-1701c0a8015f', 'newsynckey' => '{51941e99-0b9c-41f8-b678-1532c0a8015f}3'), array('newsynckey' => true)); sleep(1); $newcache->save(); $cache->refreshCollections(); $collection = $cache->getCollections(); $this->assertEquals($collection['519422f1-4c5c-4547-946a-1701c0a8015f']['lastsynckey'], '{51941e99-0b9c-41f8-b678-1532c0a8015f}3'); // Test timestamp $this->assertEquals(false, $cache->validateCache()); } protected function _testValidateAfterUpdateTimestamp() { $cache = new Horde_ActiveSync_SyncCache(self::$state, 'dev123', 'mike', self::$logger->getLogger()); $this->assertEquals(true, $cache->validateCache()); $cache->updateTimestamp(); $this->assertEquals(true, $cache->validateCache()); } /** * */ protected function _testCacheUniqueness() { $cache = new Horde_ActiveSync_SyncCache(self::$state, 'dev123', 'bob', self::$logger->getLogger()); $this->assertEquals(array(), $cache->getFolders()); $cache = new Horde_ActiveSync_SyncCache(self::$state, 'dev456', 'mike', self::$logger->getLogger()); $this->assertEquals(array(), $cache->getFolders()); } protected function _testGetStateWithNoState() { self::$state->loadDeviceInfo('dev123'); self::$state->loadState(array(), 0, Horde_ActiveSync::REQUEST_TYPE_FOLDERSYNC); } protected function _testCollectionHandler() { $collections = $this->getCollectionHandler(); // Initial state $this->assertEquals(0, $collections->collectionCount()); // No syncable collections either, even though we have a synccache, none // of the collections have a 'synckey' and have not been loaded into // collection handler. $this->assertEquals(false, $collections->haveSyncableCollections(Horde_ActiveSync::VERSION_TWOFIVE)); $this->assertEquals(false, $collections->haveSyncableCollections(Horde_ActiveSync::VERSION_TWELVEONE)); $this->assertEquals(2, $collections->cachedCollectionCount()); // Now load the collections $collections->loadCollectionsFromCache(); $this->assertEquals(2, $collections->collectionCount()); $this->assertEquals(true, $collections->haveSyncableCollections(Horde_ActiveSync::VERSION_TWOFIVE)); $this->assertEquals(true, $collections->haveSyncableCollections(Horde_ActiveSync::VERSION_TWELVEONE)); } /** * Tests the setup for a PARTIAL sync request. */ protected function _testPartialSyncWithChangedCollections() { $collections = $this->getCollectionHandler(); $collections->loadCollectionsFromCache(); // Now import a collection that IS different (which is the only reason // to have imported colletions with PARTIAL). $col = array( 'id' => '519422f1-4c5c-4547-946a-1701c0a8015f', 'windowsize' => 5, 'truncation' => 0, 'mimesupport' => 0, 'mimetruncation' => 8, 'conflict' => 1, 'bodyprefs' => array( 'wanted' => 2, 2 => array( 'type' => 2, 'truncationsize' => 100000) ), 'synckey' => '{51941e99-0b9c-41f8-b678-1532c0a8015f}3', 'deletesasmoves' => 1, 'filtertype' => 5, ); $collections->addCollection($col); $this->assertEquals(2, $collections->collectionCount()); $this->assertEquals(true, $collections->initPartialSync()); } /** * Tests the setup for a PARTIAL sync request. */ protected function _testPartialSyncWithUnchangedCollections() { // Pretend the heartbeat was not sent by the client. $cache = new Horde_ActiveSync_SyncCache(self::$state, 'dev123', 'mike', self::$logger->getLogger()); $cache->hbinterval = false; $cache->wait = false; $cache->save(); $collections = $this->getCollectionHandler(); $collections->loadCollectionsFromCache(); // Should return false because we haven't loaded (from incoming xml) any // collections yet, so no collections in handler are syncable. $this->assertEquals(false, $collections->initPartialSync()); // Pretent to read a new collection in from xml. // This one is identical to what we already have, so this should also // fail. $col = array( 'id' => '519422f1-4c5c-4547-946a-1701c0a8015f', 'windowsize' => 5, 'truncation' => 0, 'mimesupport' => 0, 'mimetruncation' => 8, 'conflict' => 1, 'bodyprefs' => array( 'wanted' => 2, 2 => array( 'type' => 2, 'truncationsize' => 200000) ), 'synckey' => '{517541cc-b188-478d-9e1a-fa49c0a8015f}3', 'deletesasmoves' => 1, 'filtertype' => 5, ); $collections->addCollection($col); $this->assertEquals(false, $collections->initPartialSync()); // Change the filtertype to simulate a new filtertype request from client. // This should now return true. $col['filtertype'] = 6; $collections->addCollection($col); $this->assertEquals(true, $collections->initPartialSync()); } protected function _testPartialSyncWithOnlyChangedHbInterval() { $this->markTestSkipped('No idea why the cache does not load the collections here.'); $collections = $this->getCollectionHandler(); $collections->loadCollectionsFromCache(); $collections->setHeartbeat(array('hbinterval' => 1)); $result = $collections->initPartialSync(); $this->assertEquals(true, $result); } protected function _testEmptyResponse() { $cache = new Horde_ActiveSync_SyncCache(self::$state, 'dev123', 'mike', self::$logger->getLogger()); // Reset these from other tests. $cache->hbinterval = 100; $cache->save(); $collections = $this->getCollectionHandler(); // False since we don't have hangingSync. $this->assertEquals(false, $collections->canSendEmptyResponse()); $collections->hangingSync = true; $this->assertEquals(true, $collections->canSendEmptyResponse()); $collections->importedChanges = true; $this->assertEquals(false, $collections->canSendEmptyResponse()); } /** * Tests initiating a partial sync where 1 collection was passed from * client and 2 others had to be loaded from cache. */ protected function _testMissingCollections() { // Need to prime the cache with a synckey for contacts so we have // another one to load for the test. $col = array('id' => '@Contacts@', 'newsynckey' => '{517541cc-b188-478d-aaaa-fa49c0a8015f}35'); $cache = new Horde_ActiveSync_SyncCache(self::$state, 'dev123', 'mike', self::$logger->getLogger()); $cache->updateCollection($col, array('newsynckey' => true)); $cache->save(); $collections = $this->getCollectionHandler(); $col = array( 'id' => '519422f1-4c5c-4547-946a-1701c0a8015f', 'windowsize' => 5, 'truncation' => 0, 'mimesupport' => 0, 'mimetruncation' => 8, 'conflict' => 1, 'bodyprefs' => array( 'wanted' => 2, 2 => array( 'type' => 2, 'truncationsize' => 300000) ), 'synckey' => '{517541cc-b188-478d-9e1a-fa49c0a8015f}3', 'deletesasmoves' => 1, 'filtertype' => 5, ); $collections->addCollection($col); $collections->initPartialSync(); $this->assertEquals(1, $collections->collectionCount()); $collections->getMissingCollectionsFromCache(); $this->assertEquals(2, $collections->collectionCount()); } /** * Test detecting a change in a collection's filtertype. */ protected function _testChangingFilterType() { $collections = $this->getCollectionHandler(); $col = array( 'id' => '519422f1-4c5c-4547-946a-1701c0a8015f', 'windowsize' => 5, 'truncation' => 0, 'mimesupport' => 0, 'mimetruncation' => 8, 'conflict' => 1, 'bodyprefs' => array( 'wanted' => 2, 2 => array( 'type' => 2, 'truncationsize' => 200000) ), 'synckey' => '{517541cc-b188-478d-9e1a-fa49c0a8015f}96', 'deletesasmoves' => 1, 'filtertype' => 4, ); $collections->addCollection($col); $this->assertEquals(false, $collections->checkFilterType($col['id'], $col['filtertype'])); } protected function _testGettingImapId() { $collections = $this->getCollectionHandler(); $this->assertEquals('INBOX', $collections->getBackendIdForFolderUid('519422f1-4c5c-4547-946a-1701c0a8015f')); $this->assertEquals('@Contacts@', $collections->getBackendIdForFolderUid('@Contacts@')); } protected function _testHierarchy() { self::$state->setBackend($this->getMockDriver()); $collections = $this->getCollectionHandler(true); $seen = $collections->initHierarchySync(0); $this->assertEquals(array(), $seen); $expected = array( array( 'type' => 'change', 'flags' => 'NewMessage', 'id' => '@Tasks@', 'serverid' => '@Tasks@' ), array( 'type' => 'change', 'flags' => 'NewMessage', 'id' => '@Notes@', 'serverid' => '@Notes@' ), array( 'type' => 'change', 'flags' => 'NewMessage', 'id' => '@Contacts@', 'serverid' => '@Contacts@' ), array( 'type' => 'change', 'flags' => 'NewMessage', 'id' => '@Calendar@', 'serverid' => '@Calendar@' ) ); $changes = $collections->getHierarchyChanges(); $this->assertEquals($expected, $changes); } public function getMockDriver() { $connector = new Horde_ActiveSync_Driver_MockConnector(); $driver = new Horde_ActiveSync_Driver_Mock(array( 'connector' => $connector, 'auth' => false, 'imap' => false, 'state' => self::$state)); return $driver; } public function getCollectionHandler($addDevice = false) { $as = $this->getMockSkipConstructor('Horde_ActiveSync'); $as->logger = self::$logger->getLogger(); $as->state = self::$state; if ($addDevice) { $as->device = self::$state->loadDeviceInfo('dev123', 'mike'); } $cache = new Horde_ActiveSync_SyncCache(self::$state, 'dev123', 'mike', self::$logger->getLogger()); $collections = new Horde_ActiveSync_Collections($cache, $as); return $collections; } } Horde_ActiveSync-2.31.1/test/Horde/ActiveSync/Stub/ImapFactory.php0000664000076500000240000000527012654565405021663 0ustar * @category Horde * @copyright 2014-2016 Horde LLC * @ignore * @license http://www.horde.org/licenses/gpl GPLv2 * NOTE: According to sec. 8 of the GENERAL PUBLIC LICENSE (GPL), * Version 2, the distribution of the Horde_ActiveSync module in or * to the United States of America is excluded from the scope of this * license. * @package Horde_ActiveSync * @subpackage UnitTests */ class Horde_ActiveSync_Stub_ImapFactory implements Horde_ActiveSync_Interface_ImapFactory { /** * Holds a mock Horde_Imap_Client_Socket object. * @var [type] */ public $fixture; /** * Return a Horde_Imap_Client * * @return Horde_Imap_Client_Base * @throws Horde_ActiveSync_Exception */ public function getImapOb() { return $this->fixture; } /** * Return an array of email folders. * * @param boolean $force If true, will force a refresh of the folder list. * * @return array An array of folder information. Each entry is keyed by * the mailbox UTF-8 name and contains: * - level: How many parents a folder has, 0 is the root. * - label: The display label for the mailbox. * - d: The delimiter. * * @throws Horde_ActiveSync_Exception * @todo */ public function getMailboxes($force = false) { return array(); } /** * Return a list of the special mailboxes available on this server. * * @return array An array of special mailboxes. * @throws Horde_ActiveSync_Exception * @todo */ public function getSpecialMailboxes() { return array(); } /** * Return a list of user-defined flags. * * @return array An array of flag arrays keyed by the RFC 3501 flag name. * @todo */ public function getMsgFlags() { return array(); } }Horde_ActiveSync-2.31.1/test/Horde/ActiveSync/AllTests.php0000664000076500000240000000013212654565405020253 0ustar run(); Horde_ActiveSync-2.31.1/test/Horde/ActiveSync/AppointmentTest.php0000664000076500000240000003454712654565405021677 0ustar * @category Horde * @package ActiveSync */ class Horde_ActiveSync_AppointmentTest extends Horde_Test_Case { protected $_oldtz; public function setUp() { $this->_oldtz = date_default_timezone_get(); date_default_timezone_set('America/New_York'); } public function tearDown() { date_default_timezone_set($this->_oldtz); } /** * Checks that setting/getting non-existant properties throws an exception. */ public function testEncoding() { $this->markTestSkipped('Needs updated fixture.'); $l = new Horde_Test_Log(); $logger = $l->getLogger(); $appt = new Horde_ActiveSync_Message_Appointment(array('logger' => $logger)); $appt->setSubject('Event Title'); $appt->setBody('Event Description'); $appt->setLocation('Philadelphia, PA'); $start = new Horde_Date('2011-12-01T15:00:00'); $appt->setDatetime(array( 'start' => $start, 'end' => new Horde_Date('2011-12-01T16:00:00'), 'allday' => false) ); $appt->setTimezone($start); $appt->setSensitivity(Horde_ActiveSync_Message_Appointment::SENSITIVITY_PERSONAL); $appt->setBusyStatus(Horde_ActiveSync_Message_Appointment::BUSYSTATUS_BUSY); $appt->setDTStamp($start->timestamp()); $stream = fopen('php://memory', 'w+'); $encoder = new Horde_ActiveSync_Wbxml_Encoder($stream); $encoder->setLogger($logger); $encoder->startTag(Horde_ActiveSync::SYNC_DATA); $appt->encodeStream($encoder); $encoder->endTag(); $fixture = file_get_contents(__DIR__ . '/fixtures/appointment.wbxml'); rewind($stream); $results = stream_get_contents($stream); fclose($stream); // TODO $this->assertEquals($fixture, $results); } public function testDecoding() { $l = new Horde_Test_Log(); $logger = $l->getLogger(); $stream = fopen(__DIR__ . '/fixtures/appointment.wbxml', 'r+'); $decoder = new Horde_ActiveSync_Wbxml_Decoder($stream); $decoder->setLogger($logger); $element = $decoder->getElementStartTag(Horde_ActiveSync::SYNC_DATA); $appt = new Horde_ActiveSync_Message_Appointment(array('logger' => $logger)); $appt->decodeStream($decoder); fclose($stream); $decoder->getElementEndTag(); $this->assertEquals('Event Title', $appt->subject); $this->assertEquals('Event Description', $appt->body); $this->assertEquals('Philadelphia, PA', $appt->location); $this->assertEquals(Horde_ActiveSync_Message_Appointment::SENSITIVITY_PERSONAL, (integer)$appt->sensitivity); $this->assertEquals(Horde_ActiveSync_Message_Appointment::BUSYSTATUS_BUSY, (integer)$appt->busystatus); $start = clone($appt->starttime); // Ensure it's UTC $this->assertEquals('UTC', $start->timezone); //...and correct. $start->setTimezone('America/New_York'); $this->assertEquals('2011-12-01 15:00:00', (string)$start); } public function testEncodingRecurrence() { $this->markTestSkipped('Needs updated fixture.'); $l = new Horde_Test_Log(); $logger = $l->getLogger(); // Every other week recurrence, on thursday, no end. $r = new Horde_Date_Recurrence('2011-12-01T15:00:00'); $r->setRecurType(Horde_Date_Recurrence::RECUR_WEEKLY); $r->setRecurInterval(2); $r->setRecurOnDay(Horde_Date::MASK_THURSDAY); $appt = new Horde_ActiveSync_Message_Appointment(array('logger' => $logger)); $appt->setSubject('Event Title'); $appt->setBody('Event Description'); $appt->setLocation('Philadelphia, PA'); $start = new Horde_Date('2011-12-01T15:00:00'); $appt->setDatetime(array( 'start' => $start, 'end' => new Horde_Date('2011-12-01T16:00:00'), 'allday' => false) ); $appt->setTimezone($start); $appt->setSensitivity(Horde_ActiveSync_Message_Appointment::SENSITIVITY_PERSONAL); $appt->setBusyStatus(Horde_ActiveSync_Message_Appointment::BUSYSTATUS_BUSY); $appt->setDTStamp($start->timestamp()); $appt->setRecurrence($r); $stream = fopen('php://memory', 'w+'); $encoder = new Horde_ActiveSync_Wbxml_Encoder($stream); $encoder->setLogger($logger); $encoder->startTag(Horde_ActiveSync::SYNC_DATA); $appt->encodeStream($encoder); $encoder->endTag(); $fixture = file_get_contents(__DIR__ . '/fixtures/recurrence.wbxml'); rewind($stream); $results = stream_get_contents($stream); fclose($stream); $this->assertEquals($fixture, $results); } public function testDecodingRecurrence() { $l = new Horde_Test_Log(); $logger = $l->getLogger(); // Test Decoding $stream = fopen(__DIR__ . '/fixtures/recurrence.wbxml', 'r+'); $decoder = new Horde_ActiveSync_Wbxml_Decoder($stream); $element = $decoder->getElementStartTag(Horde_ActiveSync::SYNC_DATA); $appt = new Horde_ActiveSync_Message_Appointment(array('logger' => $logger)); $appt->decodeStream($decoder); fclose($stream); $decoder->getElementEndTag(); // Same properties that are testing in testDeoding, but test again // here to be sure recurrence doesn't mess up the deocder. $this->assertEquals('Event Title', $appt->subject); $this->assertEquals('Event Description', $appt->body); $this->assertEquals('Philadelphia, PA', $appt->location); $this->assertEquals(Horde_ActiveSync_Message_Appointment::SENSITIVITY_PERSONAL, (integer)$appt->sensitivity); $this->assertEquals(Horde_ActiveSync_Message_Appointment::BUSYSTATUS_BUSY, (integer)$appt->busystatus); $start = clone($appt->starttime); // Ensure it's UTC $this->assertEquals('UTC', $start->timezone); //...and correct. $start->setTimezone('America/New_York'); $this->assertEquals('2011-12-01 15:00:00', (string)$start); // Recurrence properties $rrule = $appt->getRecurrence(); $this->assertEquals('2011-12-01 15:00:00', (string)$rrule->getRecurStart()->setTimezone('America/New_York')); $this->assertEquals('', (string)$rrule->getRecurEnd()); $this->assertEquals(Horde_Date_Recurrence::RECUR_WEEKLY, $rrule->getRecurType()); $this->assertEquals(2, $rrule->getRecurInterval()); $this->assertEquals(Horde_Date::MASK_THURSDAY, $days = $rrule->getRecurOnDays()); } public function testEncodingSimpleExceptions() { $this->markTestSkipped('Needs updated fixture.'); $l = new Horde_Test_Log(); $logger = $l->getLogger(); //$logger = new Horde_Log_Logger(new Horde_Log_Handler_Stream(fopen('/tmp/test.log', 'a'))); // Every other week recurrence, on thursday, no end. $r = new Horde_Date_Recurrence('2011-12-01T15:00:00'); $r->setRecurType(Horde_Date_Recurrence::RECUR_WEEKLY); $r->setRecurInterval(2); $r->setRecurOnDay(Horde_Date::MASK_THURSDAY); $r->addException(2011, 12, 29); $e = new Horde_ActiveSync_Message_Exception(); $d = new Horde_Date('2011-12-29T15:00:00'); $e->setExceptionStartTime($d); $e->deleted = true; $appt = new Horde_ActiveSync_Message_Appointment(array('logger' => $logger)); $appt->setSubject('Event Title'); $appt->setBody('Event Description'); $appt->setLocation('Philadelphia, PA'); $start = new Horde_Date('2011-12-01T15:00:00'); $appt->setDatetime(array( 'start' => $start, 'end' => new Horde_Date('2011-12-01T16:00:00'), 'allday' => false) ); $appt->setTimezone($start); $appt->setSensitivity(Horde_ActiveSync_Message_Appointment::SENSITIVITY_PERSONAL); $appt->setBusyStatus(Horde_ActiveSync_Message_Appointment::BUSYSTATUS_BUSY); $appt->setDTStamp($start->timestamp()); $appt->setRecurrence($r); $appt->addException($e); $stream = fopen('php://memory', 'w+'); $encoder = new Horde_ActiveSync_Wbxml_Encoder($stream); $encoder->setLogger($logger); $encoder->startTag(Horde_ActiveSync::SYNC_DATA); $appt->encodeStream($encoder); $encoder->endTag(); $fixture = file_get_contents(__DIR__ . '/fixtures/simpleexception.wbxml'); rewind($stream); $results = stream_get_contents($stream); fclose($stream); $this->assertEquals($fixture, $results); } public function testDecodingSimpleExceptions() { $l = new Horde_Test_Log(); $logger = $l->getLogger(); // Test Decoding $stream = fopen(__DIR__ . '/fixtures/simpleexception.wbxml', 'r+'); $decoder = new Horde_ActiveSync_Wbxml_Decoder($stream); $element = $decoder->getElementStartTag(Horde_ActiveSync::SYNC_DATA); $appt = new Horde_ActiveSync_Message_Appointment(array('logger' => $logger)); $appt->decodeStream($decoder); fclose($stream); $decoder->getElementEndTag(); // Same properties that are testing in testDeoding, but test again // here to be sure recurrence doesn't mess up the deocder. $this->assertEquals('Event Title', $appt->subject); $this->assertEquals('Event Description', $appt->body); $this->assertEquals('Philadelphia, PA', $appt->location); $this->assertEquals(Horde_ActiveSync_Message_Appointment::SENSITIVITY_PERSONAL, (integer)$appt->sensitivity); $this->assertEquals(Horde_ActiveSync_Message_Appointment::BUSYSTATUS_BUSY, (integer)$appt->busystatus); $start = clone($appt->starttime); // Ensure it's UTC $this->assertEquals('UTC', $start->timezone); //...and correct. $start->setTimezone('America/New_York'); $this->assertEquals('2011-12-01 15:00:00', (string)$start); // Recurrence properties $rrule = $appt->getRecurrence(); $this->assertEquals('2011-12-01 15:00:00', (string)$rrule->getRecurStart()->setTimezone('America/New_York')); $this->assertEquals('', (string)$rrule->getRecurEnd()); $this->assertEquals(Horde_Date_Recurrence::RECUR_WEEKLY, $rrule->getRecurType()); $this->assertEquals(2, $rrule->getRecurInterval()); $this->assertEquals(Horde_Date::MASK_THURSDAY, $days = $rrule->getRecurOnDays()); // Ensure the exception came over (should have one, deleted exception // on 2011-12-29) $exceptions = $appt->getExceptions(); $e = array_pop($exceptions); $this->assertEquals(true, (boolean)$e->deleted); $dt = $e->getExceptionStartTime(); $rrule->addException($dt->format('Y'), $dt->format('m'), $dt->format('d')); // This would normally be 2011-12-29, but that's an exception. $date = $rrule->nextActiveRecurrence(new Horde_Date('2011-12-16')); $this->assertEquals('2012-01-12 15:00:00', (string)$date); } public function testRecurrenceDSTSwitch() { // Recurring event starts 10/1/2011 15:00:00 EDST $l = new Horde_Test_Log(); $logger = $l->getLogger(); // Test Decoding $stream = fopen(__DIR__ . '/fixtures/dst.wbxml', 'r+'); $decoder = new Horde_ActiveSync_Wbxml_Decoder($stream); $element = $decoder->getElementStartTag(Horde_ActiveSync::SYNC_DATA); $appt = new Horde_ActiveSync_Message_Appointment(array('logger' => $logger)); $appt->decodeStream($decoder); fclose($stream); $decoder->getElementEndTag(); $rrule = $appt->getRecurrence(); // Get the next recurrence, still during EDST $next = $rrule->nextActiveRecurrence(new Horde_Date('2011-10-15')); $this->assertEquals('2011-10-15 15:00:00', (string)$next->setTimezone('America/New_York')); // Now get an occurence after the transition to EST. $next = $rrule->nextActiveRecurrence(new Horde_Date('2011-12-01')); $this->assertEquals('2011-12-10 15:00:00', (string)$next->setTimezone('America/New_York')); } public function testMissingSupportedTag() { $state = $this->getMockSkipConstructor('Horde_ActiveSync_State_Base'); $fixture = array( 'userAgent' => 'Apple-iPad3C6/1202.435', 'properties' => array(Horde_ActiveSync_Device::OS => 'iOS 8.1.1') ); $device = new Horde_ActiveSync_Device($state, $fixture); $contact = new Horde_ActiveSync_Message_Appointment(array('device' => $device, 'protocolversion' => Horde_ActiveSync::VERSION_FOURTEEN)); $contact->setSupported(array()); $this->assertEquals(false, $contact->isGhosted('subject')); $this->assertEquals(false, $contact->isGhosted('body')); $device = new Horde_ActiveSync_Device($state, $fixture); $contact = new Horde_ActiveSync_Message_Appointment(array('device' => $device, 'protocolversion' => Horde_ActiveSync::VERSION_SIXTEEN)); $contact->setSupported(array()); $this->assertEquals(true, $contact->isGhosted('subject')); $this->assertEquals(true, $contact->isGhosted('body')); } public function testEmptySupportedTag() { $state = $this->getMockSkipConstructor('Horde_ActiveSync_State_Base'); $fixture = array( 'userAgent' => 'Apple-iPad3C6/1202.435', 'properties' => array(Horde_ActiveSync_Device::OS => 'iOS 8.1.1') ); $device = new Horde_ActiveSync_Device($state, $fixture); $contact = new Horde_ActiveSync_Message_Appointment(array('device' => $device, 'protocolversion' => Horde_ActiveSync::VERSION_FOURTEEN)); $contact->setSupported(array(Horde_ActiveSync::ALL_GHOSTED)); $this->assertEquals(true, $contact->isGhosted('subject')); $this->assertEquals(true, $contact->isGhosted('body')); $device = new Horde_ActiveSync_Device($state, $fixture); $contact = new Horde_ActiveSync_Message_Appointment(array('device' => $device, 'protocolversion' => Horde_ActiveSync::VERSION_SIXTEEN)); $contact->setSupported(array(Horde_ActiveSync::ALL_GHOSTED)); $this->assertEquals(true, $contact->isGhosted('subject')); $this->assertEquals(true, $contact->isGhosted('body')); } } Horde_ActiveSync-2.31.1/test/Horde/ActiveSync/AutodiscoverTest.php0000664000076500000240000001607212654565405022041 0ustar * @category Horde * @package ActiveSync */ class Horde_ActiveSync_AutoDiscoverTest extends Horde_Test_Case { /** * Tests autodiscover functionality when passed a proper XML data structure * containing an email address that needs to be mapped to a username. * */ public function testAutodiscoverWithProperXML() { $factory = new Horde_ActiveSync_Factory_TestServer(); $request = << mike@example.com http://schemas.microsoft.com/exchange/autodiscover/mobilesync/responseschema/2006 EOT; fwrite($factory->input, $request); rewind($factory->input); // Mock the getUsernameFromEmail method to return 'mike' when 'mike@example.com' // is passed. $factory->driver->expects($this->once()) ->method('getUsernameFromEmail') ->will($this->returnValueMap(array(array('mike@example.com', 'mike')))); // Mock authenticate to return true only if mike is passed as username. $factory->driver->expects($this->any()) ->method('authenticate') ->will($this->returnValueMap(array(array('mike', 'password', null, true)))); // Setup is called once, and must return true. $factory->driver->expects($this->once()) ->method('setup') ->will($this->returnValue(true)); // Checks that the correct schema was detected. $mock_driver_parameters = array( 'request_schema' => 'http://schemas.microsoft.com/exchange/autodiscover/mobilesync/requestschema/2006', 'response_schema' => 'http://schemas.microsoft.com/exchange/autodiscover/mobilesync/responseschema/2006'); // ...and will only return this if it was. $mock_driver_results = array( 'display_name' => 'Michael Rubinsky', 'email' => 'mike@example.com', 'culture' => 'en:en', 'username' => 'mike', 'url' => 'https://example.com/Microsoft-Server-ActiveSync' ); $factory->driver->expects($this->once()) ->method('autoDiscover') ->will($this->returnValueMap(array(array($mock_driver_parameters, $mock_driver_results)))); $factory->server->handleRequest('Autodiscover', 'testdevice'); // Test the results $expected = << en:en Michael Rubinsky mike@example.com MobileSync https://example.com/Microsoft-Server-ActiveSync https://example.com/Microsoft-Server-ActiveSync EOT; $factory->server->encoder->getStream()->rewind(); $this->assertEquals($expected, $factory->server->encoder->getStream()->getString()); } /** * Test workarounds for broken clients that don't send proper XML with * autodiscover requests. In this case, the user/email is taken from the * HTTP Basic auth data. */ public function testAutodiscoverWithMissingXML() { // Basic auth: mike:password $auth = 'Basic bWlrZTpwYXNzd29yZA=='; $factory = new Horde_ActiveSync_Factory_TestServer(); $factory->request->expects($this->any()) ->method('getServerVars') ->will($this->returnValue(array('HTTP_AUTHORIZATION' => $auth))); // Mock the getUsernameFromEmail method to return 'mike' when 'mike' // is passed. $factory->driver->expects($this->once()) ->method('getUsernameFromEmail') ->will($this->returnValueMap(array(array('mike', 'mike')))); // Mock authenticate to return true only if 'mike' is passed as username // and 'password' is passed as the password. $factory->driver->expects($this->any()) ->method('authenticate') ->will($this->returnValueMap(array(array('mike', 'password', null, true)))); // Setup is called once, and must return true. $factory->driver->expects($this->once()) ->method('setup') ->will($this->returnValue(true)); // Checks that the correct schema was detected. $mock_driver_parameters = array( 'request_schema' => 'http://schemas.microsoft.com/exchange/autodiscover/mobilesync/requestschema/2006', 'response_schema' => 'http://schemas.microsoft.com/exchange/autodiscover/mobilesync/responseschema/2006'); // ...and will only return this if it was. $mock_driver_results = array( 'display_name' => 'Michael Rubinsky', 'email' => 'mike@example.com', 'culture' => 'en:en', 'username' => 'mike', 'url' => 'https://example.com/Microsoft-Server-ActiveSync' ); $factory->driver->expects($this->once()) ->method('autoDiscover') ->will($this->returnValueMap(array(array($mock_driver_parameters, $mock_driver_results)))); $factory->server->handleRequest('Autodiscover', 'testdevice'); // Test the results $expected = << en:en Michael Rubinsky mike@example.com MobileSync https://example.com/Microsoft-Server-ActiveSync https://example.com/Microsoft-Server-ActiveSync EOT; $factory->server->encoder->getStream()->rewind(); $this->assertEquals($expected, $factory->server->encoder->getStream()->getString()); } } Horde_ActiveSync-2.31.1/test/Horde/ActiveSync/bootstrap.php0000664000076500000240000000014312654565405020537 0ustar * @category Horde * @package ActiveSync */ class Horde_ActiveSync_CacheTest extends Horde_Test_Case { protected $_fixture; protected $_state; public function setUp() { $this->_fixture = unserialize( 'a:12:{s:18:"confirmed_synckeys";a:1:{s:39:"{4fd981c6-51b0-4274-a3ae-0a69c0a8015f}2";b:1;}s:17:"lasthbsyncstarted";b:0;s:17:"lastsyncendnormal";i:1339654598;s:9:"lastuntil";i:1339654598;s:9:"timestamp";i:1339654586;s:4:"wait";b:0;s:10:"hbinterval";b:0;s:7:"folders";a:33:{s:4:"test";a:5:{s:8:"parentid";s:1:"0";s:11:"displayname";s:4:"test";s:5:"class";s:5:"Email";s:4:"type";N;s:10:"filtertype";s:1:"0";}s:11:"spam_folder";a:5:{s:8:"parentid";s:1:"0";s:11:"displayname";s:4:"Spam";s:5:"class";s:5:"Email";s:4:"type";N;s:10:"filtertype";s:1:"0";}s:9:"sent-mail";a:5:{s:8:"parentid";s:1:"0";s:11:"displayname";s:9:"sent-mail";s:5:"class";s:5:"Email";s:4:"type";N;s:10:"filtertype";s:1:"0";}s:12:"bad messages";a:5:{s:8:"parentid";s:1:"0";s:11:"displayname";s:12:"bad messages";s:5:"class";s:5:"Email";s:4:"type";N;s:10:"filtertype";s:1:"0";}s:8:"Verendus";a:5:{s:8:"parentid";s:1:"0";s:11:"displayname";s:8:"Verendus";s:5:"class";s:5:"Email";s:4:"type";N;s:10:"filtertype";s:1:"0";}s:5:"Trash";a:5:{s:8:"parentid";s:1:"0";s:11:"displayname";s:5:"Trash";s:5:"class";s:5:"Email";s:4:"type";N;s:10:"filtertype";s:1:"0";}s:9:"Templates";a:5:{s:8:"parentid";s:1:"0";s:11:"displayname";s:9:"Templates";s:5:"class";s:5:"Email";s:4:"type";N;s:10:"filtertype";s:1:"0";}s:13:"Spam Training";a:5:{s:8:"parentid";s:1:"0";s:11:"displayname";s:13:"Spam Training";s:5:"class";s:5:"Email";s:4:"type";N;s:10:"filtertype";s:1:"0";}s:13:"SiliconMemory";a:5:{s:8:"parentid";s:1:"0";s:11:"displayname";s:13:"SiliconMemory";s:5:"class";s:5:"Email";s:4:"type";N;s:10:"filtertype";s:1:"0";}s:13:"Sent Messages";a:5:{s:8:"parentid";s:1:"0";s:11:"displayname";s:13:"Sent Messages";s:5:"class";s:5:"Email";s:4:"type";N;s:10:"filtertype";s:1:"0";}s:4:"Sent";a:5:{s:8:"parentid";s:1:"0";s:11:"displayname";s:4:"Sent";s:5:"class";s:5:"Email";s:4:"type";N;s:10:"filtertype";s:1:"0";}s:6:"Review";a:5:{s:8:"parentid";s:1:"0";s:11:"displayname";s:6:"Review";s:5:"class";s:5:"Email";s:4:"type";N;s:10:"filtertype";s:1:"0";}s:8:"Pharmacy";a:5:{s:8:"parentid";s:1:"0";s:11:"displayname";s:8:"Pharmacy";s:5:"class";s:5:"Email";s:4:"type";N;s:10:"filtertype";s:1:"0";}s:7:"Medical";a:5:{s:8:"parentid";s:1:"0";s:11:"displayname";s:7:"Medical";s:5:"class";s:5:"Email";s:4:"type";N;s:10:"filtertype";s:1:"0";}s:5:"INBOX";a:5:{s:8:"parentid";s:1:"0";s:11:"displayname";s:5:"Inbox";s:5:"class";s:5:"Email";s:4:"type";N;s:10:"filtertype";s:1:"0";}s:10:"Horde, LLC";a:5:{s:8:"parentid";s:1:"0";s:11:"displayname";s:10:"Horde, LLC";s:5:"class";s:5:"Email";s:4:"type";N;s:10:"filtertype";s:1:"0";}s:13:"Horde Website";a:5:{s:8:"parentid";s:1:"0";s:11:"displayname";s:13:"Horde Website";s:5:"class";s:5:"Email";s:4:"type";N;s:10:"filtertype";s:1:"0";}s:17:"Horde UI Redesign";a:5:{s:8:"parentid";s:1:"0";s:11:"displayname";s:17:"Horde UI Redesign";s:5:"class";s:5:"Email";s:4:"type";N;s:10:"filtertype";s:1:"0";}s:15:"Horde Sent Mail";a:5:{s:8:"parentid";s:1:"0";s:11:"displayname";s:15:"Horde Sent Mail";s:5:"class";s:5:"Email";s:4:"type";N;s:10:"filtertype";s:1:"0";}s:14:"Horde Security";a:5:{s:8:"parentid";s:1:"0";s:11:"displayname";s:14:"Horde Security";s:5:"class";s:5:"Email";s:4:"type";N;s:10:"filtertype";s:1:"0";}s:11:"Horde Lists";a:5:{s:8:"parentid";s:1:"0";s:11:"displayname";s:11:"Horde Lists";s:5:"class";s:5:"Email";s:4:"type";N;s:10:"filtertype";s:1:"0";}s:11:"Horde Ideas";a:5:{s:8:"parentid";s:1:"0";s:11:"displayname";s:11:"Horde Ideas";s:5:"class";s:5:"Email";s:4:"type";N;s:10:"filtertype";s:1:"0";}s:15:"Horde Hackathon";a:5:{s:8:"parentid";s:1:"0";s:11:"displayname";s:15:"Horde Hackathon";s:5:"class";s:5:"Email";s:4:"type";N;s:10:"filtertype";s:1:"0";}s:16:"Horde Consulting";a:5:{s:8:"parentid";s:1:"0";s:11:"displayname";s:16:"Horde Consulting";s:5:"class";s:5:"Email";s:4:"type";N;s:10:"filtertype";s:1:"0";}s:7:"Horde 5";a:5:{s:8:"parentid";s:1:"0";s:11:"displayname";s:7:"Horde 5";s:5:"class";s:5:"Email";s:4:"type";N;s:10:"filtertype";s:1:"0";}s:15:"General Archive";a:5:{s:8:"parentid";s:1:"0";s:11:"displayname";s:15:"General Archive";s:5:"class";s:5:"Email";s:4:"type";N;s:10:"filtertype";s:1:"0";}s:6:"Family";a:5:{s:8:"parentid";s:1:"0";s:11:"displayname";s:6:"Family";s:5:"class";s:5:"Email";s:4:"type";N;s:10:"filtertype";s:1:"0";}s:31:"Code Samples, Personal Web Work";a:5:{s:8:"parentid";s:1:"0";s:11:"displayname";s:31:"Code Samples, Personal Web Work";s:5:"class";s:5:"Email";s:4:"type";N;s:10:"filtertype";s:1:"0";}s:15:"Apple Dev Stuff";a:5:{s:8:"parentid";s:1:"0";s:11:"displayname";s:15:"Apple Dev Stuff";s:5:"class";s:5:"Email";s:4:"type";N;s:10:"filtertype";s:1:"0";}s:10:"ActiveSync";a:5:{s:8:"parentid";s:1:"0";s:11:"displayname";s:10:"ActiveSync";s:5:"class";s:5:"Email";s:4:"type";N;s:10:"filtertype";s:1:"0";}s:7:"@Tasks@";a:5:{s:8:"parentid";i:0;s:11:"displayname";s:5:"Tasks";s:5:"class";s:5:"Tasks";s:4:"type";N;s:10:"filtertype";s:1:"0";}s:10:"@Contacts@";a:5:{s:8:"parentid";i:0;s:11:"displayname";s:8:"Contacts";s:5:"class";s:8:"Contacts";s:4:"type";N;s:10:"filtertype";s:1:"0";}s:10:"@Calendar@";a:5:{s:8:"parentid";i:0;s:11:"displayname";s:8:"Calendar";s:5:"class";s:8:"Calendar";s:4:"type";N;s:10:"filtertype";s:1:"0";}}s:9:"hierarchy";s:39:"{4fd981ba-6e58-440c-8d6f-0a69c0a8015f}1";s:11:"collections";a:2:{s:10:"@Contacts@";a:10:{s:5:"class";s:8:"Contacts";s:10:"windowsize";s:2:"25";s:14:"deletesasmoves";N;s:10:"filtertype";N;s:10:"truncation";i:0;s:13:"rtftruncation";N;s:11:"mimesupport";i:0;s:14:"mimetruncation";N;s:8:"conflict";i:1;s:9:"bodyprefs";a:2:{s:6:"wanted";s:1:"1";i:1;a:2:{s:4:"type";s:1:"1";s:14:"truncationsize";s:5:"32768";}}}s:10:"@Calendar@";a:11:{s:5:"class";s:8:"Calendar";s:10:"windowsize";s:2:"25";s:14:"deletesasmoves";N;s:10:"filtertype";s:1:"6";s:10:"truncation";i:0;s:13:"rtftruncation";N;s:11:"mimesupport";i:0;s:14:"mimetruncation";N;s:8:"conflict";i:1;s:9:"bodyprefs";a:2:{s:6:"wanted";s:1:"1";i:1;a:2:{s:4:"type";s:1:"1";s:14:"truncationsize";s:5:"32768";}}s:7:"synckey";s:39:"{4fd981c6-51b0-4274-a3ae-0a69c0a8015f}2";}}s:13:"pingheartbeat";i:500;s:14:"synckeycounter";a:0:{}}' ); $this->_state = $this->getMockSkipConstructor('Horde_ActiveSync_State_Sql'); $this->_state->expects($this->any())->method('getSyncCache')->will($this->returnValue($this->_fixture)); } public function testPropertyAccess() { $cache = new Horde_ActiveSync_SyncCache($this->_state, 'devid', 'userone'); $this->assertEquals('{4fd981ba-6e58-440c-8d6f-0a69c0a8015f}1', $cache->hierarchy); $this->assertEquals(true, $cache->confirmed_synckeys['{4fd981c6-51b0-4274-a3ae-0a69c0a8015f}2']); } public function testInvalidPropertyAccess() { $this->setExpectedException('InvalidArgumentException'); $cache = new Horde_ActiveSync_SyncCache($this->_state, 'devid', 'userone'); $cache->collections; } public function testValidProperties() { $cache = new Horde_ActiveSync_SyncCache($this->_state, 'devid', 'userone'); $cache->save(); foreach (array('hbinterval', 'wait', 'hierarchy', 'confirmed_synckeys', 'lasthbsyncstarted', 'lastsyncendnormal', 'folders', 'pingheartbeat', 'timestamp') as $p) { $cache->{$p}; } } public function testValidateTimestamps() { $cache = new Horde_ActiveSync_SyncCache($this->_state, 'devid', 'userone'); $this->assertEquals(true, $cache->validateTimestamps()); $cache->lasthbsyncstarted = time(); $this->assertEquals(false, $cache->validateTimestamps()); } }Horde_ActiveSync-2.31.1/test/Horde/ActiveSync/conf.php.dist0000664000076500000240000000305712654565405020420 0ustar * @category Horde * @package ActiveSync */ class Horde_ActiveSync_ContactTest extends Horde_Test_Case { /** * Checks that setting/getting non-existant properties throws an exception. */ public function testNonExistantProperties() { $contact = new Horde_ActiveSync_Message_Contact(); $this->setExpectedException('InvalidArgumentException'); $contact->unknown = 'test'; $test = $contact->unknown; } /** * Tests that properties that are arrays of values work as expected. */ public function testComplexProperties() { $contact = new Horde_ActiveSync_Message_Contact(); $this->assertEquals(0, count($contact->children)); $contact->children[] = 'blah'; $this->assertEquals(1, count($contact->children)); $this->assertEquals('blah', array_pop($contact->children)); } public function testPictureGhosted() { $state = $this->getMockSkipConstructor('Horde_ActiveSync_State_Base'); $fixture = array( 'deviceType' => 'iPod', 'userAgent' => 'Apple-iPod2C1/803.148' ); $device = new Horde_ActiveSync_Device($state, $fixture); $contact = new Horde_ActiveSync_Message_Contact(array('device' => $device)); $contact->setSupported(array()); $this->assertEquals(true, $contact->isGhosted('picture')); $fixture = array( 'deviceType' => 'iPad', 'userAgent' => 'Apple-iPad3C6/1202.435', 'properties' => array(Horde_ActiveSync_Device::OS => 'iOS 8.1.1') ); $device = new Horde_ActiveSync_Device($state, $fixture); $contact = new Horde_ActiveSync_Message_Contact(array('device' => $device)); $contact->setSupported(array()); $this->assertEquals(false, $contact->isGhosted('picture')); } public function testMissingSupportedTag() { $state = $this->getMockSkipConstructor('Horde_ActiveSync_State_Base'); $fixture = array( 'userAgent' => 'Apple-iPad3C6/1202.435', 'properties' => array(Horde_ActiveSync_Device::OS => 'iOS 8.1.1') ); $device = new Horde_ActiveSync_Device($state, $fixture); $contact = new Horde_ActiveSync_Message_Contact(array('device' => $device)); $contact->setSupported(array()); $this->assertEquals(false, $contact->isGhosted('fileas')); } public function testEmptySupportedTag() { $state = $this->getMockSkipConstructor('Horde_ActiveSync_State_Base'); $fixture = array( 'userAgent' => 'Apple-iPad3C6/1202.435', 'properties' => array(Horde_ActiveSync_Device::OS => 'iOS 8.1.1') ); $device = new Horde_ActiveSync_Device($state, $fixture); $contact = new Horde_ActiveSync_Message_Contact(array('device' => $device)); $contact->setSupported(array(Horde_ActiveSync::ALL_GHOSTED)); $this->assertEquals(true, $contact->isGhosted('fileas')); } /** * Test that known properties work as expected. */ public function testKnownPropertiesAreSettable() { $contact = new Horde_ActiveSync_Message_Contact(); $contact->anniversary = '1994-03-06'; $this->assertEquals('1994-03-06', $contact->anniversary); $contact->assistantname = 'I wish'; $this->assertEquals('I wish', $contact->assistantname); $contact->assistnamephonenumber = '555-555-1234'; $this->assertEquals('555-555-1234', $contact->assistnamephonenumber); $contact->birthday = '1970-03-20'; $this->assertEquals('1970-03-20', $contact->birthday); $contact->body = 'This is the body'; $this->assertEquals('This is the body', $contact->body); $contact->bodysize = 16; $this->assertEquals(16, $contact->bodysize); $contact->bodytruncated = 'This is'; $this->assertEquals('This is', $contact->bodytruncated); $contact->business2phonenumber = '555-123-4567'; $this->assertEquals('555-123-4567', $contact->business2phonenumber); $contact->businesscity = 'Philadelphia'; $this->assertEquals('Philadelphia', $contact->businesscity); $contact->businesscountry = 'US'; $this->assertEquals('US', $contact->businesscountry); $contact->businesspostalcode = '19148'; $this->assertEquals('19148', $contact->businesspostalcode); $contact->businessstate = 'PA'; $this->assertEquals('PA', $contact->businessstate); $contact->businessstreet = '123 Market St'; $this->assertEquals('123 Market St', $contact->businessstreet); $contact->businessfaxnumber = '555-122-2222'; $this->assertEquals('555-122-2222', $contact->businessfaxnumber); $contact->businessphonenumber = '555-456-4529'; $this->assertEquals('555-456-4529', $contact->businessphonenumber); $contact->carphonenumber = '555-881-7891'; $this->assertEquals('555-881-7891', $contact->carphonenumber); $contact->children = 'Jordyn'; $this->assertEquals('Jordyn', $contact->children); $contact->companyname = 'Horde'; $this->assertEquals('Horde', $contact->companyname); $contact->department = 'QA'; $this->assertEquals('QA', $contact->department); $contact->email1address = 'mike@theupstairsroom.com'; $this->assertEquals('mike@theupstairsroom.com', $contact->email1address); $contact->email2address = 'mrubinsk@horde.org'; $this->assertEquals('mrubinsk@horde.org', $contact->email2address); $contact->email3address = 'mikerubinsky@gmail.com'; $this->assertEquals('mikerubinsky@gmail.com', $contact->email3address); $contact->fileas = 'Michael Rubinsky'; $this->assertEquals('Michael Rubinsky', $contact->fileas); $contact->firstname = 'Michael'; $this->assertEquals('Michael', $contact->firstname); $contact->home2phonenumber = '555-779-1212'; $this->assertEquals('555-779-1212', $contact->home2phonenumber); $contact->homecity = 'Philadelphia'; $this->assertEquals('Philadelphia', $contact->homecity); $contact->homecountry = 'US'; $this->assertEquals('US', $contact->homecountry); $contact->homepostalcode = '19148'; $this->assertEquals('19148', $contact->homepostalcode); $contact->homestate = 'PA'; $this->assertEquals('PA', $contact->homestate); $contact->homestreet = '123 Center St'; $this->assertEquals('123 Center St', $contact->homestreet); $contact->homefaxnumber = ''; $this->assertEquals('', $contact->homefaxnumber); $contact->homephonenumber = '555-789-7897'; $this->assertEquals('555-789-7897', $contact->homephonenumber); $contact->jobtitle = 'developer'; $this->assertEquals('developer', $contact->jobtitle); $contact->lastname = 'Rubinsky'; $this->assertEquals('Rubinsky', $contact->lastname); $contact->middlename = 'Joseph'; $this->assertEquals('Joseph', $contact->middlename); $contact->mobilephonenumber = '555-122-1234'; $this->assertEquals('555-122-1234', $contact->mobilephonenumber); $contact->officelocation = 'Here'; $this->assertEquals('Here', $contact->officelocation); $contact->othercity = 'SomeCity'; $this->assertEquals('SomeCity', $contact->othercity); $contact->othercountry = 'US'; $this->assertEquals('US', $contact->othercountry); $contact->otherpostalcode = '08080'; $this->assertEquals('08080', $contact->otherpostalcode); $contact->otherstate = 'NJ'; $this->assertEquals('NJ', $contact->otherstate); $contact->otherstreet = 'E. Center St'; $this->assertEquals('E. Center St', $contact->otherstreet); $contact->pagernumber = '555-123-1234'; $this->assertEquals('555-123-1234', $contact->pagernumber); $contact->radiophonenumber = '555-123-4567'; $this->assertEquals('555-123-4567', $contact->radiophonenumber); $contact->spouse = 'Ashley'; $this->assertEquals('Ashley', $contact->spouse); $contact->suffix = 'PharmD'; $this->assertEquals('PharmD', $contact->suffix); $contact->title = 'Dr.'; $this->assertEquals('Dr.', $contact->title); $contact->webpage = 'http://theupstairsroom.com'; $this->assertEquals('http://theupstairsroom.com', $contact->webpage); $contact->yomicompanyname = 'TheUpstairsRoom'; $this->assertEquals('TheUpstairsRoom', $contact->yomicompanyname); $contact->yomifirstname = ''; $this->assertEquals('', $contact->yomifirstname); $contact->yomilastname = ''; $this->assertEquals('', $contact->yomilastname); $contact->rtf = 'test'; $this->assertEquals('test', $contact->rtf); $contact->picture = ''; $this->assertEquals('', $contact->picture); $contact->categories = ''; $this->assertEquals('', $contact->categories); } } Horde_ActiveSync-2.31.1/test/Horde/ActiveSync/DeviceTest.php0000664000076500000240000003130512654565405020565 0ustar * @category Horde * @package ActiveSync */ class Horde_ActiveSync_DeviceTest extends Horde_Test_Case { public function testDeviceDetection() { // // iOS $state = $this->getMockSkipConstructor('Horde_ActiveSync_State_Base'); $fixture = array( 'deviceType' => 'iPod', 'userAgent' => 'Apple-iPod5C1/1102.55400001', 'properties' => array(Horde_ActiveSync_Device::OS => 'iOS 7.0.4') ); $device = new Horde_ActiveSync_Device($state, $fixture); $this->assertEquals(7, $device->getMajorVersion()); $this->assertEquals(0, $device->getMinorVersion()); $this->assertEquals(Horde_ActiveSync_Device::TYPE_IPOD, Horde_String::lower($device->deviceType)); $this->assertEquals(Horde_ActiveSync_Device::MULTIPLEX_NOTES, $device->multiplex); $fixture = array( 'deviceType' => 'iPhone', 'userAgent' => 'iOS/1002.329' ); $device = new Horde_ActiveSync_Device($state, $fixture); $this->assertEquals(6, $device->getMajorVersion()); $this->assertEquals(1, $device->getMinorVersion()); $this->assertEquals(Horde_ActiveSync_Device::TYPE_IPHONE, Horde_String::lower($device->deviceType)); $this->assertEquals(Horde_ActiveSync_Device::MULTIPLEX_NOTES, $device->multiplex); $this->assertEquals(false, $device->hasQuirk(Horde_ActiveSync_Device::QUIRK_NEEDS_SUPPORTED_PICTURE_TAG)); $fixture = array( 'deviceType' => 'iPod', 'userAgent' => 'Apple-iPod2C1/803.148' ); $device = new Horde_ActiveSync_Device($state, $fixture); $this->assertEquals(4, $device->getMajorVersion()); $this->assertEquals(2, $device->getMinorVersion()); $this->assertEquals(true, $device->hasQuirk(Horde_ActiveSync_Device::QUIRK_NEEDS_SUPPORTED_PICTURE_TAG)); $fixture = array( 'deviceType' => 'iPad', 'userAgent' => 'Apple-iPad3C6/1202.435', 'properties' => array(Horde_ActiveSync_Device::OS => 'iOS 8.1.1') ); $device = new Horde_ActiveSync_Device($state, $fixture); $this->assertEquals(8, $device->getMajorVersion()); $this->assertEquals(1, $device->getMinorVersion()); $this->assertEquals(Horde_ActiveSync_Device::TYPE_IPAD, Horde_String::lower($device->deviceType)); $this->assertEquals(Horde_ActiveSync_Device::MULTIPLEX_NOTES, $device->multiplex); $fixture = array( 'deviceType' => 'iPhone', 'userAgent' => 'Apple-iPhone6C1/1104.201', 'properties' => array(Horde_ActiveSync_Device::OS => 'iOS 9.0.2 13A452') ); $device = new Horde_ActiveSync_Device($state, $fixture); $this->assertEquals(9, $device->getMajorVersion()); $this->assertEquals(0, $device->getMinorVersion()); $this->assertEquals(Horde_ActiveSync_Device::TYPE_IPHONE, Horde_String::lower($device->deviceType)); $this->assertEquals(Horde_ActiveSync_Device::MULTIPLEX_NOTES, $device->multiplex); // Old Android. $fixture = array( 'userAgent' => 'Android/0.3', 'deviceType' => 'Android'); $device = new Horde_ActiveSync_Device($state, $fixture); $this->assertEquals(0, $device->getMajorVersion()); $this->assertEquals(3, $device->getMinorVersion()); $this->assertEquals(Horde_ActiveSync_Device::TYPE_ANDROID, Horde_String::lower($device->deviceType)); $this->assertEquals(Horde_ActiveSync_Device::TYPE_ANDROID, Horde_String::lower($device->clientType)); $this->assertEquals(Horde_ActiveSync_Device::MULTIPLEX_CONTACTS | Horde_ActiveSync_Device::MULTIPLEX_CALENDAR | Horde_ActiveSync_Device::MULTIPLEX_NOTES | Horde_ActiveSync_Device::MULTIPLEX_TASKS, $device->multiplex); // Touchdown client on Android. $fixture = array( 'userAgent' => 'TouchDown(MSRPC)/7.1.0005', 'deviceType' => 'Android'); $device = new Horde_ActiveSync_Device($state, $fixture); $this->assertEquals(7, $device->getMajorVersion()); $this->assertEquals(1, $device->getMinorVersion()); $this->assertEquals(Horde_ActiveSync_Device::TYPE_ANDROID, Horde_String::lower($device->deviceType)); $this->assertEquals(Horde_ActiveSync_Device::TYPE_TOUCHDOWN, Horde_String::lower($device->clientType)); $this->assertEquals(0, $device->multiplex); // Not-so-old-but-still-old Android. $fixture = array( 'userAgent' => 'MOTOROLA-Droid(4D6F7869SAM)/2.1707', 'deviceType' => 'Android'); $device = new Horde_ActiveSync_Device($state, $fixture); $this->assertEquals(2, $device->getMajorVersion()); $this->assertEquals(1707, $device->getMinorVersion()); $this->assertEquals(Horde_ActiveSync_Device::TYPE_ANDROID, Horde_String::lower($device->deviceType)); $this->assertEquals(Horde_ActiveSync_Device::TYPE_ANDROID, Horde_String::lower($device->clientType)); $this->assertEquals(15, $device->multiplex); // KK Android (taken from SDK). $fixture = array( 'userAgent' => 'Android/4.4.2-EAS-1.3', 'deviceType' => 'Android', 'properties' => array(Horde_ActiveSync_Device::OS => 'Android 4.4.2') ); $device = new Horde_ActiveSync_Device($state, $fixture); $this->assertEquals(4, $device->getMajorVersion()); $this->assertEquals(4, $device->getMinorVersion()); $this->assertEquals(Horde_ActiveSync_Device::TYPE_ANDROID, Horde_String::lower($device->deviceType)); $this->assertEquals(Horde_ActiveSync_Device::TYPE_ANDROID, Horde_String::lower($device->clientType)); $this->assertEquals(13, $device->multiplex); // Devices like this (from a Note 3) we simply can't sniff multiplex for // since there is no version string. Stuff like this would go in the // hook. $fixture = array( 'deviceType' => 'SAMSUNGSMN900V', 'userAgent' => 'SAMSUNG-SM-N900V/101.403', 'properties' => array(Horde_ActiveSync_Device::OS => 'Android') ); $device = new Horde_ActiveSync_Device($state, $fixture); // These are useless values, but still tests the reliability of the code $this->assertEquals(101, $device->getMajorVersion()); $this->assertEquals(403, $device->getMinorVersion()); $this->assertEquals('samsungsmn900v', Horde_String::lower($device->deviceType)); $this->assertEquals(Horde_ActiveSync_Device::TYPE_ANDROID, Horde_String::lower($device->clientType)); $this->assertEquals(15, $device->multiplex); // Nine (From Note 3 running 4.4.2). $fixture = array( 'deviceType' => 'Android', 'userAgent' => 'hltevzw/KOT49H', 'properties' => array(Horde_ActiveSync_Device::OS => 'Android 4.4.2.N900VVRUCNC4') ); $device = new Horde_ActiveSync_Device($state, $fixture); $device->id = '6E696E656331393035333833303331'; $this->assertEquals(4, $device->getMajorVersion()); $this->assertEquals('android', Horde_String::lower($device->deviceType)); $this->assertEquals(Horde_ActiveSync_Device::TYPE_NINE, Horde_String::lower($device->clientType)); $this->assertEquals(0, $device->multiplex); // HTCOneMini2 $fixture = array( 'userAgent' => 'HTC', // Don't think this matters here. 'deviceType' => 'HTCOnemini2', 'properties' => array(Horde_ActiveSync_Device::OS => 'Android 4.4.2', Horde_ActiveSync_Device::MODEL => 'HTCOnemini2') ); $device = new Horde_ActiveSync_Device($state, $fixture); $this->assertEquals(4, $device->getMajorVersion()); $this->assertEquals(4, $device->getMinorVersion()); $this->assertEquals('htconemini2', Horde_String::lower($device->deviceType)); $this->assertEquals(Horde_ActiveSync_Device::TYPE_ANDROID, Horde_String::lower($device->clientType)); $this->assertEquals(15, $device->multiplex); } public function testPoomContactsDate() { $tz = date_default_timezone_get(); date_default_timezone_set('America/New_York'); $state = $this->getMockSkipConstructor('Horde_ActiveSync_State_Base'); // WindowsPhone. $fixture = array('deviceType' => 'windowsphone'); $device = new Horde_ActiveSync_Device($state, $fixture); $date = new Horde_Date('2003-09-24', 'UTC'); $bday = $device->normalizePoomContactsDates($date); $this->assertEquals('2003-09-24', $bday->setTimezone('America/New_York')->format('Y-m-d')); // iOS (Sends as 00:00:00 localtime converted to UTC). $fixture = array( 'deviceType' => 'iPhone', 'userAgent' => 'Apple-iPhone4C1/1002.329', 'properties' => array(Horde_ActiveSync_Device::OS => 'iOS 6.1.3 10B329')); $device = new Horde_ActiveSync_Device($state, $fixture); $date = new Horde_Date('1970-03-20'); $bday = $device->normalizePoomContactsDates($date, true); $this->assertEquals('1970-03-20 00:00:00', (string)$bday); $date = new Horde_Date('1970-03-20T05:00:00.000Z'); $bday = $device->normalizePoomContactsDates($date); $this->assertEquals('1970-03-20 00:00:00', (string)$bday->setTimezone('America/New_York')); // Try a positive UTC offset timezone date_default_timezone_set('Europe/Berlin'); $fixture = array( 'deviceType' => 'iPhone', 'userAgent' => 'Apple-iPhone4C1/1104.201', 'properties' => array(Horde_ActiveSync_Device::OS => 'iOS 7.1.1 11D201')); $device = new Horde_ActiveSync_Device($state, $fixture); $date = new Horde_Date('1966-07-22T23:00:00.000Z'); $bday = $device->normalizePoomContactsDates($date); $bday->setTimezone(date_default_timezone_get()); $this->assertEquals('1966-07-23', $bday->format('Y-m-d')); // Android date_default_timezone_set('Pacific/Honolulu'); $fixture = array('deviceType' => 'android', 'userAgent' => 'Android/4.3.1-EAS-1.3'); $device = new Horde_ActiveSync_Device($state, $fixture); $date = new Horde_Date('2003-09-24 08:00:00', 'UTC'); $bday = $device->normalizePoomContactsDates($date); $this->assertEquals('2003-09-24', $bday->setTimezone('Pacific/Honolulu')->format('Y-m-d')); // Note 3 date_default_timezone_set('America/Chicago'); $fixture = array( 'deviceType' => 'SAMSUNGSMN900V', 'userAgent' => 'SAMSUNG-SM-N900V/101.403', 'properties' => array(Horde_ActiveSync_Device::OS => 'Android') ); $device = new Horde_ActiveSync_Device($state, $fixture); $date = new Horde_Date('1970-03-20'); $bday = $device->normalizePoomContactsDates($date, true); $this->assertEquals('1970-03-20 00:00:00', (string)$bday); $fixture = array( 'deviceType' => 'Android', 'userAgent' => 'hltevzw/KOT49H', 'properties' => array(Horde_ActiveSync_Device::OS => 'Android 4.4.2.N900VVRUCNC4') ); $device = new Horde_ActiveSync_Device($state, $fixture); $device->id = '6E696E656331393035333833303331'; $date = new Horde_Date('1970-03-20'); $bday = $device->normalizePoomContactsDates($date, true); $this->assertEquals('1970-03-20 00:00:00', (string)$bday); date_default_timezone_set($tz); } public function testOverrideClientType() { $fixture = array( 'deviceType' => 'SAMSUNGSMN900V', 'userAgent' => 'SAMSUNG-SM-N900V/101.403', 'properties' => array(Horde_ActiveSync_Device::OS => 'Android') ); $device = new Horde_ActiveSync_Device($this->getMockSkipConstructor('Horde_ActiveSync_State_Base'), $fixture); $this->assertEquals(Horde_ActiveSync_Device::TYPE_ANDROID, $device->clientType); $device->clientType = 'Samsung'; $this->assertEquals('Samsung', $device->clientType); } public function testSupported() { $fixture = array( 'deviceType' => 'SAMSUNGSMN900V', 'userAgent' => 'SAMSUNG-SM-N900V/101.403', 'properties' => array(Horde_ActiveSync_Device::OS => 'Android') ); $device = new Horde_ActiveSync_Device($this->getMockSkipConstructor('Horde_ActiveSync_State_Base'), $fixture); $this->assertEmpty($device->supported); $device->supported = array(); $device->supported['contacts'] = array('one', 'two'); $this->assertEquals($device->supported, array('contacts' => array('one', 'two'))); } }Horde_ActiveSync-2.31.1/test/Horde/ActiveSync/ImapAdapterTest.php0000664000076500000240000005106612654565405021563 0ustar * @category Horde * @package ActiveSync */ class Horde_ActiveSync_ImapAdapterTest extends Horde_Test_Case { public function testBug13711() { $this->markTestSkipped("Useless test without all the fixtures."); $factory = new Horde_ActiveSync_Factory_TestServer(); $imap_client = $this->getMockSkipConstructor('Horde_Imap_Client_Socket'); $imap_client->expects($this->any()) ->method('fetch') ->will($this->_getFixturesFor13711()); $imap_factory = new Horde_ActiveSync_Stub_ImapFactory(); $imap_factory->fixture = $imap_client; $adapter = new Horde_ActiveSync_Imap_Adapter(array('factory' => $imap_factory)); $adapter->getMessages( 'INBOX', array(462), array( 'protocolversion' => 14.1, 'bodyprefs' => array( 'wanted' => Horde_ActiveSync::BODYPREF_TYPE_MIME, Horde_ActiveSync::BODYPREF_TYPE_MIME => array( 'type' => Horde_ActiveSync::BODYPREF_TYPE_MIME, 'truncationsize' => 200000) ), 'mimesupport' => Horde_ActiveSync::MIME_SUPPORT_ALL, ) ); } protected function _getFixturesFor13711() { $first = "TzozMToiSG9yZGVfSW1hcF9DbGllbnRfRmV0Y2hfUmVzdWx0cyI6Mzp7czo4OiIAKgBfZGF0YSI7YToxOntpOjQ2MjtPOjI4OiJIb3JkZV9JbWFwX0NsaWVudF9EYXRhX0ZldGNoIjoxOntzOjg6IgAqAF9kYXRhIjthOjY6e2k6MTQ7aToxNDtpOjEzO2k6NDYyO2k6MTA7YTowOnt9aToxO0M6MTU6IkhvcmRlX01pbWVfUGFydCI6MzA5OnthOjIwOntpOjA7aToxO2k6MTtzOjQ6InRleHQiO2k6MjtzOjg6ImNhbGVuZGFyIjtpOjM7czo0OiI4Yml0IjtpOjQ7YToxOntpOjA7czoyOiJkZSI7fWk6NTtzOjA6IiI7aTo2O3M6MDoiIjtpOjc7YToxOntzOjQ6InNpemUiO3M6NDoiMTUyMyI7fWk6ODthOjI6e3M6NzoiY2hhcnNldCI7czo1OiJVVEYtOCI7czo2OiJtZXRob2QiO3M6NzoiUkVRVUVTVCI7fWk6OTthOjA6e31pOjEwO3M6MToiMSI7aToxMTtzOjE6IgoiO2k6MTI7YTowOnt9aToxMztOO2k6MTQ7aToxNTIzO2k6MTU7TjtpOjE2O047aToxNztiOjA7aToxODtiOjA7aToxOTtOO319aTo5O0M6MzE6IkhvcmRlX0ltYXBfQ2xpZW50X0RhdGFfRW52ZWxvcGUiOjE2Mzg6e2E6Mjp7czoxOiJkIjtDOjE4OiJIb3JkZV9NaW1lX0hlYWRlcnMiOjE1Nzk6e2E6Mzp7aTowO2k6MztpOjE7YTo1OntzOjQ6IkRhdGUiO086MjM6IkhvcmRlX01pbWVfSGVhZGVyc19EYXRlIjoyOntzOjg6IgAqAF9uYW1lIjtzOjQ6IkRhdGUiO3M6MTA6IgAqAF92YWx1ZXMiO2E6MTp7aTowO3M6MzA6IkZyaSwgNyBOb3YgMjAxNCAxMzozNjo1NCArMDEwMCI7fX1zOjc6IlN1YmplY3QiO086MjY6IkhvcmRlX01pbWVfSGVhZGVyc19TdWJqZWN0IjoyOntzOjg6IgAqAF9uYW1lIjtzOjc6IlN1YmplY3QiO3M6MTA6IgAqAF92YWx1ZXMiO2E6MTp7aTowO3M6Mzk6Ik1BQyA+IEZhaHJzdHVobCA+IEJlc2NocmlmdHVuZyA+IE11c3RlciI7fX1zOjQ6ImZyb20iO086Mjg6IkhvcmRlX01pbWVfSGVhZGVyc19BZGRyZXNzZXMiOjM6e3M6MTE6ImFwcGVuZF9hZGRyIjtiOjE7czo4OiIAKgBfbmFtZSI7czo0OiJmcm9tIjtzOjEwOiIAKgBfdmFsdWVzIjtDOjIyOiJIb3JkZV9NYWlsX1JmYzgyMl9MaXN0IjoxNzQ6e2E6MTp7aTowO086MjU6IkhvcmRlX01haWxfUmZjODIyX0FkZHJlc3MiOjQ6e3M6NzoiY29tbWVudCI7YTowOnt9czo3OiJtYWlsYm94IjtzOjEyOiJtYXJpby5sb3JlbnoiO3M6ODoiACoAX2hvc3QiO3M6MTA6ImRlc2VydmUuZGUiO3M6MTI6IgAqAF9wZXJzb25hbCI7czoxMjoiTWFyaW8gTG9yZW56Ijt9fX19czoyOiJ0byI7TzoyODoiSG9yZGVfTWltZV9IZWFkZXJzX0FkZHJlc3NlcyI6Mzp7czoxMToiYXBwZW5kX2FkZHIiO2I6MTtzOjg6IgAqAF9uYW1lIjtzOjI6InRvIjtzOjEwOiIAKgBfdmFsdWVzIjtDOjIyOiJIb3JkZV9NYWlsX1JmYzgyMl9MaXN0Ijo2MDA6e2E6Mzp7aTowO086MjU6IkhvcmRlX01haWxfUmZjODIyX0FkZHJlc3MiOjQ6e3M6NzoiY29tbWVudCI7YTowOnt9czo3OiJtYWlsYm94IjtzOjE2OiJwYXRyaWNrLmxvZXNjaGVyIjtzOjg6IgAqAF9ob3N0IjtzOjEzOiJiaWxmaW5nZXIuY29tIjtzOjEyOiIAKgBfcGVyc29uYWwiO3M6NTY6IidMw7ZzY2hlciwgUGF0cmljayAoQmlsZmluZ2VyIFJlYWwgRXN0YXRlIEFyZ29uZW8gR21iSCknIjt9aToxO086MjU6IkhvcmRlX01haWxfUmZjODIyX0FkZHJlc3MiOjQ6e3M6NzoiY29tbWVudCI7YTowOnt9czo3OiJtYWlsYm94IjtzOjEzOiJncmVnb3IuYm90enVtIjtzOjg6IgAqAF9ob3N0IjtzOjEzOiJiaWxmaW5nZXIuY29tIjtzOjEyOiIAKgBfcGVyc29uYWwiO3M6NTM6IidCb3R6dW0sIEdyZWdvciAoQmlsZmluZ2VyIFJlYWwgRXN0YXRlIEFyZ29uZW8gR21iSCknIjt9aToyO086MjU6IkhvcmRlX01haWxfUmZjODIyX0FkZHJlc3MiOjQ6e3M6NzoiY29tbWVudCI7YTowOnt9czo3OiJtYWlsYm94IjtzOjQ6InJ1cHAiO3M6ODoiACoAX2hvc3QiO3M6MTE6ImluZGl0ZWMuY29tIjtzOjEyOiIAKgBfcGVyc29uYWwiO3M6MTQ6IidGbG9yaWFuIFJ1cHAnIjt9fX19czoxMDoiTWVzc2FnZS1JRCI7TzoyODoiSG9yZGVfTWltZV9IZWFkZXJzX01lc3NhZ2VpZCI6Mjp7czo4OiIAKgBfbmFtZSI7czoxMDoiTWVzc2FnZS1JRCI7czoxMDoiACoAX3ZhbHVlcyI7YToxOntpOjA7czo0NDoiPDAwOTEwMWNmZmE4NyQ4MzhlOGM0MCQ4YWFiYTRjMCRAZGVzZXJ2ZS5kZT4iO319fWk6MjtzOjE6IgoiO319czoxOiJ2IjtpOjM7fX1pOjM7YToxOntpOjA7czo3MDA6IkZyb206IE1hcmlvIExvcmVueiA8bWFyaW8ubG9yZW56QGRlc2VydmUuZGU+DQpUbzogIj0/dXRmLTg/Yj9KMHpEdG5OamFHVnlMQT09Pz0gUGF0cmljayAoQmlsZmluZ2VyIFJlYWwgRXN0YXRlIEFyZ29uZW8NCiBHbWJIKSciIDxwYXRyaWNrLmxvZXNjaGVyQGJpbGZpbmdlci5jb20+LCAiJ0JvdHp1bSwgR3JlZ29yIChCaWxmaW5nZXIgUmVhbA0KIEVzdGF0ZSBBcmdvbmVvIEdtYkgpJyIgPGdyZWdvci5ib3R6dW1AYmlsZmluZ2VyLmNvbT4sICdGbG9yaWFuIFJ1cHAnDQogPHJ1cHBAaW5kaXRlYy5jb20+DQpTdWJqZWN0OiBNQUMgPiBGYWhyc3R1aGwgPiBCZXNjaHJpZnR1bmcgPiBNdXN0ZXINCkRhdGU6IEZyaSwgNyBOb3YgMjAxNCAxMzozNjo1NCArMDEwMA0KTWVzc2FnZS1JRDogPDAwOTEwMWNmZmE4NyQ4MzhlOGM0MCQ4YWFiYTRjMCRAZGVzZXJ2ZS5kZT4NCk1JTUUtVmVyc2lvbjogMS4wDQpDb250ZW50LVR5cGU6IHRleHQvY2FsZW5kYXI7IGNoYXJzZXQ9VVRGLTg7IG1ldGhvZD1SRVFVRVNUDQpDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiA4Yml0DQpYLU1haWxlcjogTWljcm9zb2Z0IE91dGxvb2sgMTUuMA0KVGhyZWFkLUluZGV4OiBBYy82aDRLMDhSa0Q3UndYUnUyMkprczA1ZHNuTlFBQUFBUFENCkNvbnRlbnQtTGFuZ3VhZ2U6IGRlDQpVc2VyLUFnZW50OiBIb3JkZSBBcHBsaWNhdGlvbiBGcmFtZXdvcmsgNQ0KDQoiO319fX1zOjExOiIAKgBfa2V5VHlwZSI7aToyO3M6MTE6IgAqAF9vYkNsYXNzIjtzOjI4OiJIb3JkZV9JbWFwX0NsaWVudF9EYXRhX0ZldGNoIjt9"; //$first_obj = unserialize(base64_decode($first)); $second = "TzozMToiSG9yZGVfSW1hcF9DbGllbnRfRmV0Y2hfUmVzdWx0cyI6Mzp7czo4OiIAKgBfZGF0YSI7YToxOntpOjQ2MjtPOjI4OiJIb3JkZV9JbWFwX0NsaWVudF9EYXRhX0ZldGNoIjoxOntzOjg6IgAqAF9kYXRhIjthOjI6e2k6MTQ7aToxNDtpOjEzO2k6NDYyO319fXM6MTE6IgAqAF9rZXlUeXBlIjtpOjI7czoxMToiACoAX29iQ2xhc3MiO3M6Mjg6IkhvcmRlX0ltYXBfQ2xpZW50X0RhdGFfRmV0Y2giO30="; $second_obj = unserialize(base64_decode($second)); // $third = "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AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAgID///////////////////////////////////////////////////////////////////////////////+AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAgID///////////////////////////////////////////////////////////////////////////////+AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAgID///////////////////////////////////////////////////////////////////////////////+AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAgID////////////////////////AwMDAwMDAwMD///////////////////////////////////////////+AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAgID////////////////////AwMAAAIAAAIDAwMDAwMD///////////////////////////////////////+AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAgID////////////////AwMAAAIAAAIAAAIAAAIDAwMDAwMD///////////////////////////////////+AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAgID////////////AwMAAAIAAAIAAAIAAAIAAAIAAAIDAwMD///////////////////////////////////+AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAgID////////AwMAAAIAAAIAAAIAAAIAAAIAAAIAAAICAgIDAwMD///////////////////////////////+AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAgID///////8AAIAAAIAAAIAAAID///+AgIAAAIAAAIAAAIDAwMDAwMD///////////////////////////+AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAgID///////8AAIAAAIAAAID////////////AwMAAAIAAAIAAAIDAwMDAwMD///////////////////////+AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAgID///////////8AAID////////////////////AwMAAAIAAAICAgIDAwMD///////////////////////+AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAgID////////////////////////////////////////AwMAAAIAAAICAgIDAwMD///////////////////+AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAgID///////////////////////////////////////////////8AAIAAAIDAwMDAwMD///////////////+AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAgID///////////////////////////////////////////////////8AAIAAAIDAwMDAwMD///////////+AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAgID///////////////////////////////////////////////////////8AAIAAAIDAwMDAwMD///////+AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAgID///////////////////////////////////////////////////////////8AAIAAAIDAwMD///////+AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAgID///////////////////////////////////////////////////////////////8AAIAAAID///////+AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAgID///////////////////////////////////////////////////////////////////8AAIDAwMD///+AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAgID///////////////////////////////////////////////////////////////////////8AAID///+AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAgID///////////////////////////////////////////////////////////////////////////////+AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAgID///////////////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAwMD///////////+AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAgID///////////////////+AgICAgICAgICAgICAgICAgICAgICAgICAgIDAwMAAAADAwMD///////////+AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAgICAgICAgICAgICAgICAgID///+AgICAgICAgICAgICAgICAgICAgIDAwMAAAACAgICAgICAgICAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAgID////AwMDAwMDAwMDAwMDAwMDAwMCAgIDAwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAgID///////////////////////+AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA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"; // $third_obj = unserialize(base64_decode($third)); $fourth = "TzozMToiSG9yZGVfSW1hcF9DbGllbnRfRmV0Y2hfUmVzdWx0cyI6Mzp7czo4OiIAKgBfZGF0YSI7YToxOntpOjQ2MjtPOjI4OiJIb3JkZV9JbWFwX0NsaWVudF9EYXRhX0ZldGNoIjoxOntzOjg6IgAqAF9kYXRhIjthOjM6e2k6MTQ7aToxNDtpOjEzO2k6NDYyO2k6NjthOjE6e2k6MTthOjI6e3M6MToiZCI7czo0OiI4Yml0IjtzOjE6InQiO3M6MTUyMzoiQkVHSU46VkNBTEVOREFSDQpQUk9ESUQ6LS8vTWljcm9zb2Z0IENvcnBvcmF0aW9uLy9PdXRsb29rIDE1LjAgTUlNRURJUi8vRU4NClZFUlNJT046Mi4wDQpNRVRIT0Q6UkVRVUVTVA0KWC1NUy1PTEstRk9SQ0VJTlNQRUNUT1JPUEVOOlRSVUUNCkJFR0lOOlZUSU1FWk9ORQ0KVFpJRDpXLiBFdXJvcGUgU3RhbmRhcmQgVGltZQ0KQkVHSU46U1RBTkRBUkQNCkRUU1RBUlQ6MTYwMTEwMjhUMDMwMDAwDQpSUlVMRTpGUkVRPVlFQVJMWTtCWURBWT0tMVNVO0JZTU9OVEg9MTANClRaT0ZGU0VURlJPTTorMDIwMA0KVFpPRkZTRVRUTzorMDEwMA0KRU5EOlNUQU5EQVJEDQpCRUdJTjpEQVlMSUdIVA0KRFRTVEFSVDoxNjAxMDMyNVQwMjAwMDANClJSVUxFOkZSRVE9WUVBUkxZO0JZREFZPS0xU1U7QllNT05USD0zDQpUWk9GRlNFVEZST006KzAxMDANClRaT0ZGU0VUVE86KzAyMDANCkVORDpEQVlMSUdIVA0KRU5EOlZUSU1FWk9ORQ0KQkVHSU46VkVWRU5UDQpBVFRFTkRFRTtDTj0iTMO2c2NoZXIsIFBhdHJpY2sgKEJpbGZpbmdlciBSZWFsIEVzdGF0ZSBBcmdvbmVvIEdtYkgpIjtSU1ZQPVQNCglSVUU6bWFpbHRvOnBhdHJpY2subG9lc2NoZXJAYmlsZmluZ2VyLmNvbQ0KQVRURU5ERUU7Q049IidCb3R6dW0sIEdyZWdvciAoQmlsZmluZ2VyIFJlYWwgRXN0YXRlIEFyZ29uZW8gR21iSCknIjtSU1ZQPVRSDQoJVUU6bWFpbHRvOmdyZWdvci5ib3R6dW1AYmlsZmluZ2VyLmNvbQ0KQVRURU5ERUU7Q049IkZsb3JpYW4gUnVwcCI7UlNWUD1UUlVFOm1haWx0bzpydXBwQGluZGl0ZWMuY29tDQpDTEFTUzpQVUJMSUMNCkNSRUFURUQ6MjAxNDExMDdUMTIzNjUzWg0KREVTQ1JJUFRJT046XG5cbg0KRFRFTkQ7VFpJRD0iVy4gRXVyb3BlIFN0YW5kYXJkIFRpbWUiOjIwMTQxMTEwVDEzMzAwMA0KRFRTVEFNUDoyMDE0MTEwN1QxMjM2NTNaDQpEVFNUQVJUO1RaSUQ9IlcuIEV1cm9wZSBTdGFuZGFyZCBUaW1lIjoyMDE0MTExMFQxMjAwMDANCkxBU1QtTU9ESUZJRUQ6MjAxNDExMDdUMTIzNjUzWg0KTE9DQVRJT046TUFDDQpPUkdBTklaRVI7Q049Ik1hcmlvIExvcmVueiI6bWFpbHRvOm1hcmlvLmxvcmVuekBkZXNlcnZlLmRlDQpQUklPUklUWTo1DQpTRVFVRU5DRTowDQpTVU1NQVJZO0xBTkdVQUdFPWRlOk1BQyA+IEZhaHJzdHVobCA+IEJlc2NocmlmdHVuZyA+IE11c3Rlcg0KVFJBTlNQOk9QQVFVRQ0KVUlEOjA0MDAwMDAwODIwMEUwMDA3NEM1QjcxMDFBODJFMDA4MDAwMDAwMDBCMDU2RjY2QThGRkFDRjAxMDAwMDAwMDAwMDAwMDAwDQoJMDEwMDAwMDAwNzk4QjRFRDNCNENFMTk0NUFCMEEyNTJGNjhGNkE3M0YNClgtTUlDUk9TT0ZULUNETy1CVVNZU1RBVFVTOlRFTlRBVElWRQ0KWC1NSUNST1NPRlQtQ0RPLUlNUE9SVEFOQ0U6MQ0KWC1NSUNST1NPRlQtQ0RPLUlOVEVOREVEU1RBVFVTOkJVU1kNClgtTUlDUk9TT0ZULURJU0FMTE9XLUNPVU5URVI6RkFMU0UNClgtTVMtT0xLLUFVVE9TVEFSVENIRUNLOkZBTFNFDQpYLU1TLU9MSy1DT05GVFlQRTowDQpFTkQ6VkVWRU5UDQpFTkQ6VkNBTEVOREFSDQoiO319fX19czoxMToiACoAX2tleVR5cGUiO2k6MjtzOjExOiIAKgBfb2JDbGFzcyI7czoyODoiSG9yZGVfSW1hcF9DbGllbnRfRGF0YV9GZXRjaCI7fQ=="; $fourth_obj = unserialize(base64_decode($fourth)); //return $this->onConsecutiveCalls($first_obj, $second_obj, $fourth_obj); return $this->onConsecutiveCalls($second_obj, $fourth_obj); } } Horde_ActiveSync-2.31.1/test/Horde/ActiveSync/ImapFolderTest.php0000664000076500000240000003207612654565405021416 0ustar * @category Horde * @package ActiveSync */ class Horde_ActiveSync_ImapFolderTest extends Horde_Test_Case { public function testInitialState() { $folder = new Horde_ActiveSync_Folder_Imap('INBOX', Horde_ActiveSync::CLASS_EMAIL); $thrown = false; try { $folder->checkValidity(array(Horde_ActiveSync_Folder_Imap::UIDVALIDITY => '123')); } catch (Horde_ActiveSync_Exception $e) { $thrown = true; } $this->assertEquals(true, $thrown); $this->assertEquals(0, $folder->uidnext()); $this->assertEquals(0, $folder->modseq()); $this->assertEquals(array(), $folder->messages()); $this->assertEquals(array(), $folder->flags()); $this->assertEquals(array(), $folder->added()); $this->assertEquals(array(), $folder->changed()); $this->assertEquals(array(), $folder->removed()); $this->assertEquals(0, $folder->minuid()); } public function testNoModseqUpdate() { $folder = new Horde_ActiveSync_Folder_Imap('INBOX', Horde_ActiveSync::CLASS_EMAIL); $status = array(Horde_ActiveSync_Folder_Imap::UIDVALIDITY => 100, Horde_ActiveSync_Folder_Imap::UIDNEXT => 105); // Initial state for nonmodseq $msg_changes = array(100, 101, 102, 103, 104); $flag_changes = array( 100 => array('read' => 0, 'flagged' => 0), 101 => array('read' => 0, 'flagged' => 0), 102 => array('read' => 0, 'flagged' => 0), 103 => array('read' => 0, 'flagged' => 0), 104 => array('read' => 0, 'flagged' => 0), ); $folder->setChanges($msg_changes, $flag_changes); $this->assertEquals($msg_changes, $folder->added()); $this->assertEquals($flag_changes, $folder->flags()); $this->assertEquals(array(), $folder->changed()); $this->assertEquals(array(), $folder->removed()); $this->assertEquals(array(), $folder->messages()); $folder->setStatus($status); $folder->updateState(); $this->assertEquals(array(), $folder->added()); $this->assertEquals(array(), $folder->flags()); $this->assertEquals(array(), $folder->changed()); $this->assertEquals(array(), $folder->removed()); $this->assertEquals($msg_changes, $folder->messages()); // Now simulate some flag changes and new messages. $msg_changes = array(100, 101, 102, 103, 104, 105); $flag_changes = array( 100 => array('read' => 0, 'flagged' => 1), 101 => array('read' => 0, 'flagged' => 0), 102 => array('read' => 0, 'flagged' => 0), 103 => array('read' => 0, 'flagged' => 0), 104 => array('read' => 0, 'flagged' => 0), 105 => array('read' => 1, 'flagged' => 0), ); $folder->setChanges($msg_changes, $flag_changes); $this->assertEquals(array(105), $folder->added()); $this->assertEquals(array(100), $folder->changed()); $status[Horde_ActiveSync_Folder_Imap::UIDNEXT] = 106; $folder->setStatus($status); $folder->updateState(); } public function testModseqUpdate() { $folder = new Horde_ActiveSync_Folder_Imap('INBOX', Horde_ActiveSync::CLASS_EMAIL); $status = array(Horde_ActiveSync_Folder_Imap::UIDVALIDITY => 100, Horde_ActiveSync_Folder_Imap::UIDNEXT => 105, Horde_ActiveSync_Folder_Imap::HIGHESTMODSEQ => 200); // Initial state $msg_changes = array(100, 101, 102, 103, 104); $flag_changes = array( 100 => array('read' => 0, 'flagged' => 0), 101 => array('read' => 0, 'flagged' => 0), 102 => array('read' => 0, 'flagged' => 0), 103 => array('read' => 0, 'flagged' => 0), 104 => array('read' => 0, 'flagged' => 0), ); $folder->setChanges($msg_changes, $flag_changes); $this->assertEquals($msg_changes, $folder->added()); $this->assertEquals($flag_changes, $folder->flags()); $this->assertEquals(array(), $folder->changed()); $this->assertEquals(array(), $folder->removed()); $this->assertEquals(array(), $folder->messages()); $folder->setStatus($status); $folder->updateState(); $this->assertEquals(array(), $folder->added()); $this->assertEquals(array(), $folder->flags()); $this->assertEquals(array(), $folder->changed()); $this->assertEquals(array(), $folder->removed()); $this->assertEquals($msg_changes, $folder->messages()); // Now simulate some flag changes and new messages. $msg_changes = array(100, 105); $flag_changes = array( 100 => array('read' => 0, 'flagged' => 1), 105 => array('read' => 1, 'flagged' => 0), ); $folder->setChanges($msg_changes, $flag_changes); $this->assertEquals(array(105), $folder->added()); $this->assertEquals(array(100), $folder->changed()); $status[Horde_ActiveSync_Folder_Imap::UIDNEXT] = 106; $folder->setStatus($status); $folder->updateState(); } public function testSerializationWithImapCompression() { $folder = new Horde_ActiveSync_Folder_Imap('Trash', Horde_ActiveSync::CLASS_EMAIL); $status = array(Horde_ActiveSync_Folder_Imap::UIDVALIDITY => 100, Horde_ActiveSync_Folder_Imap::UIDNEXT => 47654, Horde_ActiveSync_Folder_Imap::HIGHESTMODSEQ => 200); $fixture = array(46653,46654,46655,46656,46657,46658,46659,46660,46661,46662,46663,46664,46665,46666,46667,46668,46669,46670,46671,46672,46673,46674,46675,46676,46677,46678,46679,46680,46681,46682,46691,46692,46693,46694,46695,46696,46697,46698,46699,46700,46701,46702,46703,46704,46705,46706,46707,46708,46709,46710,46711,46712,46713,46714,46715,46716,46717,46718,46719,46720,46721,46723,46724,46725,46726,46727,46728,46729,46730,46731,46732,46733,46734,46735,46736,46737,46738,46739,46740,46741,46742,46743,46744,46745,46746,46747,46748,46749,46750,46751,46752,46753,46754,46755,46756,46757,46758,46759,46760,46761,46762,46763,46764,46765,46766,46767,46768,46769,46770,46771,46772,46773,46774,46775,46776,46777,46778,46779,46780,46781,46782,46783,46784,46785,46786,46787,46788,46789,46790,46791,46792,46793,46794,46795,46796,46797,46798,46799,46800,46801,46802,46803,46804,46805,46806,46807,46808,46809,46810,46811,46812,46813,46814,46815,46816,46817,46818,46819,46820,46821,46822,46823,46824,46825,46826,46827,46828,46829,46830,46831,46832,46833,46834,46835,46836,46837,46838,46839,46840,46841,46842,46843,46844,46845,46846,46847,46848,46849,46850,46851,46852,46853,46854,46855,46856,46857,46858,46859,46860,46861,46862,46863,46864,46865,46866,46867,46868,46869,46870,46871,46872,46873,46874,46875,46876,46877,46878,46879,46880,46881,46883,46884,46885,46886,46887,46888,46889,46890,46891,46892,46893,46894,46895,46896,46897,46898,46899,46900,46901,46902,46903,46904,46905,46906,46907,46908,46909,46910,46911,46912,46913,46914,46915,46916,46917,46918,46919,46920,46921,46922,46923,46924,46925,46926,46927,46928,46929,46930,46931,46932,46933,46934,46935,46936,46937,46938,46939,46940,46941,46942,46943,46944,46945,46946,46947,46948,46949,46950,46951,46952,46953,46954,46955,46956,46957,46958,46959,46960,46961,46962,46963,46964,46965,46966,46967,46968,46969,46970,46971,46972,46973,46974,46975,46976,46977,46978,46979,46980,46981,46982,46983,46984,46985,46986,46987,46988,46989,46990,46991,46992,46993,46994,46995,46996,46997,46998,46999,47000,47001,47002,47003,47004,47005,47006,47007,47008,47009,47010,47011,47012,47013,47014,47015,47016,47017,47018,47019,47020,47021,47022,47023,47024,47025,47026,47027,47028,47029,47030,47031,47032,47033,47034,47035,47036,47037,47038,47039,47040,47041,47042,47043,47044,47045,47046,47047,47048,47049,47050,47051,47052,47053,47054,47055,47056,47057,47058,47059,47060,47061,47062,47063,47064,47065,47066,47067,47068,47069,47070,47071,47072,47073,47074,47075,47076,47077,47078,47079,47080,47081,47082,47083,47084,47085,47086,47087,47088,47089,47090,47091,47092,47093,47094,47095,47096,47097,47098,47099,47100,47101,47102,47103,47104,47105,47106,47107,47108,47109,47110,47111,47112,47113,47114,47115,47116,47117,47118,47119,47120,47121,47122,47123,47124,47125,47126,47127,47128,47129,47130,47131,47132,47133,47134,47135,47136,47137,47138,47139,47140,47141,47142,47143,47144,47145,47146,47147,47148,47149,47150,47151,47152,47153,47154,47155,47156,47157,47158,47159,47160,47161,47162,47163,47164,47165,47166,47167,47168,47169,47170,47171,47172,47173,47174,47175,47176,47177,47178,47179,47180,47181,47182,47183,47184,47185,47186,47187,47188,47189,47190,47191,47192,47193,47194,47195,47196,47197,47198,47199,47200,47201,47202,47203,47204,47205,47206,47207,47208,47209,47210,47211,47212,47213,47214,47215,47216,47217,47218,47219,47220,47221,47222,47223,47224,47225,47226,47227,47228,47229,47230,47231,47232,47233,47234,47235,47236,47237,47238,47239,47240,47241,47242,47243,47244,47245,47246,47247,47248,47249,47250,47251,47252,47253,47254,47255,47256,47257,47258,47259,47260,47261,47262,47263,47264,47265,47266,47267,47268,47269,47270,47271,47272,47273,47274,47275,47276,47277,47278,47279,47280,47281,47282,47283,47284,47285,47286,47287,47288,47289,47290,47291,47292,47293,47294,47295,47296,47297,47298,47299,47300,47301,47302,47303,47304,47305,47306,47307,47308,47309,47310,47311,47312,47313,47314,47315,47316,47317,47318,47319,47320,47321,47322,47323,47324,47325,47326,47327,47328,47329,47330,47331,47332,47333,47334,47335,47336,47337,47338,47339,47340,47341,47342,47343,47344,47345,47346,47347,47348,47349,47350,47351,47352,47353,47354,47355,47356,47357,47358,47359,47360,47361,47362,47363,47364,47365,47366,47367,47368,47369,47370,47371,47372,47373,47374,47375,47376,47377,47378,47379,47380,47381,47382,47383,47384,47385,47386,47387,47388,47389,47390,47391,47392,47393,47394,47395,47396,47397,47398,47399,47400,47401,47402,47403,47404,47405,47406,47407,47408,47409,47410,47411,47412,47413,47414,47415,47416,47417,47418,47419,47420,47421,47422,47423,47424,47425,47426,47427,47428,47429,47430,47431,47432,47433,47434,47435,47436,47437,47438,47439,47440,47441,47442,47443,47444,47445,47446,47447,47448,47449,47450,47451,47452,47453,47454,47455,47456,47457,47458,47459,47460,47461,47462,47463,47464,47465,47466,47467,47468,47469,47470,47471,47472,47473,47474,47475,47476,47477,47478,47479,47480,47481,47482,47483,47484,47485,47486,47487,47488,47489,47490,47491,47492,47493,47494,47495,47496,47497,47498,47499,47500,47501,47502,47503,47504,47505,47506,47507,47508,47509,47510,47511,47512,47513,47514,47515,47516,47517,47518,47519,47520,47521,47522,47523,47524,47525,47526,47527,47528,47529,47530,47531,47532,47533,47534,47535,47536,47537,47538,47539,47540,47541,47542,47543,47544,47545,47546,47547,47548,47549,47550,47551,47552,47553,47554,47555,47556,47557,47558,47559,47560,47561,47562,47563,47564,47565,47566,47567,47568,47569,47570,47571,47572,47573,47574,47575,47576,47577,47578,47579,47580,47581,47582,47583,47584,47585,47586,47587,47588,47589,47590,47591,47592,47593,47594,47595,47596,47597,47598,47599,47600,47601,47602,47603,47604,47605,47606,47607,47608,47609,47610,47611,47612,47613,47614,47615,47616,47617,47618,47619,47620,47621,47622,47623,47624,47625,47626,47627,47628,47629,47630,47631,47632,47633,47634,47635,47636,47637,47638,47639,47640,47641,47642,47643,47644,47645,47646,47647,47648,47649,47650,47651,47652,47653); $folder->setChanges($fixture); $folder->setStatus($status); $folder->updateState(); $serialized = serialize($folder); // General test that the imap uid compression worked. $this->assertTrue(strlen($serialized) < 300); $folder = unserialize($serialized); // Ensure the values were preserved. $this->assertEquals($fixture, $folder->messages()); } public function testSerializationWithoutImapCompression() { $folder = new Horde_ActiveSync_Folder_Imap('Trash', Horde_ActiveSync::CLASS_EMAIL); $status = array(Horde_ActiveSync_Folder_Imap::UIDVALIDITY => 100, Horde_ActiveSync_Folder_Imap::UIDNEXT => 47654, Horde_ActiveSync_Folder_Imap::HIGHESTMODSEQ => 200); $fixture = array(46653,46654,46655,46656,46657,46658,46659,46660,46661,46662,46663,46664,46665,46666,46667,46668,46669,46670,46671,46672,46673,46674,46675,46676,46677,46678,46679,46680,46681,46682,46691,46692,46693,46694,46695,46696,46697,46698,46699,46700,46701,46702,46703,46704,46705,46706,46707,46708,46709,46710,46711,46712,46713,46714,46715,46716,46717,46718,46719,46720,46721,46723,46724,46725,46726,46727,46728,46729,46730,46731,46732,46733,46734,46735,46736,46737,46738,46739,46740,46741,46742,46743,46744,46745,46746,46747,46748,46749,46750,46751,46752,46753,46754,46755,46756,46757,46758,46759,46760,46761,46762,46763,46764,46765,46766,46767,46768,46769,46770,46771,46772,46773,46774,46775,46776,46777,46778,46779,46780,46781,46782,46783,46784,46785,46786); $folder->setChanges($fixture); $folder->setStatus($status); $folder->updateState(); $serialized = serialize($folder); $folder = unserialize($serialized); $this->assertEquals($fixture, $folder->messages()); } }Horde_ActiveSync-2.31.1/test/Horde/ActiveSync/InviteTest.php0000664000076500000240000000345312654565405020627 0ustar * @category Horde * @package ActiveSync */ class Horde_ActiveSync_InviteTest extends Horde_Test_Case { protected $_oldtz; public function setUp() { $this->_oldtz = date_default_timezone_get(); date_default_timezone_set('America/New_York'); } public function tearDown() { date_default_timezone_set($this->_oldtz); } /** * Test creating a Horde_ActiveSync_Message_MeetingRequest from a MIME Email */ public function testInvite() { $this->markTestIncomplete('Has issues on 32bit systems'); $fixture = file_get_contents(__DIR__ . '/fixtures/invitation_one.eml'); $mime = Horde_Mime_Part::parseMessage($fixture); $msg = new Horde_ActiveSync_Message_MeetingRequest(); foreach ($mime->contentTypeMap() as $id => $type) { if ($type == 'text/calendar') { $vcal = new Horde_Icalendar(); $vcal->parseVcalendar($mime->getPart($id)->getContents()); $msg->fromvEvent($vcal); break; } } $stream = fopen('php://memory', 'wb+'); $encoder = new Horde_ActiveSync_Wbxml_Encoder($stream); $msg->encodeStream($encoder); rewind($stream); $results = stream_get_contents($stream); fclose($stream); $stream = fopen(__DIR__ . '/fixtures/meeting_request_one.wbxml', 'r+'); $expected = ''; // Using file_get_contents or even fread mangles the binary data for some // reason. while ($line = fgets($stream)) { $expected .= $line; } fclose($stream); $this->assertEquals($expected, $results); } }Horde_ActiveSync-2.31.1/test/Horde/ActiveSync/MessageBodyDataTest.php0000664000076500000240000001076512654565405022371 0ustar * @category Horde * @package ActiveSync */ class Horde_ActiveSync_MessageBodyDataTest extends Horde_Test_Case { public function testReturnProperlyTruncatedHtml() { $factory = new Horde_ActiveSync_Factory_TestServer(); $imap_client = $this->getMockSkipConstructor('Horde_Imap_Client_Socket'); $imap_client->expects($this->any()) ->method('fetch') ->will($this->_getFixturesFor13711()); $imap_factory = new Horde_ActiveSync_Stub_ImapFactory(); $imap_factory->fixture = $imap_client; $adapter = new Horde_ActiveSync_Imap_Adapter(array('factory' => $imap_factory)); $this->markTestSkipped("Can't use serialized Horde_Mime_Part"); $horde_mime_fixture = 'TzoyMToiSG9yZGVfQWN0aXZlU3luY19NaW1lIjoyOntzOjg6IgAqAF9iYXNlIjtDOjE1OiJIb3JkZV9NaW1lX1BhcnQiOjI4MDp7YToyMDp7aTowO2k6MTtpOjE7czo0OiJ0ZXh0IjtpOjI7czo0OiJodG1sIjtpOjM7czoxNjoicXVvdGVkLXByaW50YWJsZSI7aTo0O2E6MDp7fWk6NTtzOjA6IiI7aTo2O3M6MDoiIjtpOjc7YToxOntzOjQ6InNpemUiO3M6NToiMzAzMzYiO31pOjg7YToxOntzOjc6ImNoYXJzZXQiO3M6NToidXRmLTgiO31pOjk7YTowOnt9aToxMDtzOjE6IjEiO2k6MTE7czoxOiIKIjtpOjEyO2E6MDp7fWk6MTM7TjtpOjE0O2k6MzAzMzY7aToxNTtOO2k6MTY7TjtpOjE3O2I6MDtpOjE4O2I6MDtpOjE5O047fX1zOjE4OiIAKgBfaGFzQXR0YWNobWVudHMiO047fQ=='; $basePart = unserialize(base64_decode($horde_mime_fixture)); $mbd = new Horde_ActiveSync_Imap_MessageBodyData( array( 'imap' => $imap_client, 'mime' => $basePart, 'uid' => 1, 'mbox' => new Horde_Imap_Client_Mailbox('INBOX')), array( 'protocolversion' => 14.1, 'bodyprefs' => array( Horde_ActiveSync::BODYPREF_TYPE_HTML => array( 'truncationsize' => 10240, 'allornone' => 0), Horde_ActiveSync::BODYPREF_TYPE_PLAIN => array( 'truncationsize' => 10240, 'allornone' => 0) ) ) ); $this->assertEquals(10240, $mbd->html['body']->length(true)); $this->assertEquals(true, $mbd->html['truncated']); } public function testReturnHtmlNoTruncation() { $factory = new Horde_ActiveSync_Factory_TestServer(); $imap_client = $this->getMockSkipConstructor('Horde_Imap_Client_Socket'); $imap_client->expects($this->any()) ->method('fetch') ->will($this->_getFixturesFor13711()); $imap_factory = new Horde_ActiveSync_Stub_ImapFactory(); $imap_factory->fixture = $imap_client; $adapter = new Horde_ActiveSync_Imap_Adapter(array('factory' => $imap_factory)); $this->markTestSkipped("Can't use serialized Horde_Mime_Part"); $horde_mime_fixture = 'TzoyMToiSG9yZGVfQWN0aXZlU3luY19NaW1lIjoyOntzOjg6IgAqAF9iYXNlIjtDOjE1OiJIb3JkZV9NaW1lX1BhcnQiOjI4MDp7YToyMDp7aTowO2k6MTtpOjE7czo0OiJ0ZXh0IjtpOjI7czo0OiJodG1sIjtpOjM7czoxNjoicXVvdGVkLXByaW50YWJsZSI7aTo0O2E6MDp7fWk6NTtzOjA6IiI7aTo2O3M6MDoiIjtpOjc7YToxOntzOjQ6InNpemUiO3M6NToiMzAzMzYiO31pOjg7YToxOntzOjc6ImNoYXJzZXQiO3M6NToidXRmLTgiO31pOjk7YTowOnt9aToxMDtzOjE6IjEiO2k6MTE7czoxOiIKIjtpOjEyO2E6MDp7fWk6MTM7TjtpOjE0O2k6MzAzMzY7aToxNTtOO2k6MTY7TjtpOjE3O2I6MDtpOjE4O2I6MDtpOjE5O047fX1zOjE4OiIAKgBfaGFzQXR0YWNobWVudHMiO047fQ=='; $basePart = unserialize(base64_decode($horde_mime_fixture)); $mbd = new Horde_ActiveSync_Imap_MessageBodyData( array( 'imap' => $imap_client, 'mime' => $basePart, 'uid' => 1, 'mbox' => new Horde_Imap_Client_Mailbox('INBOX')), array( 'protocolversion' => 14.1, 'bodyprefs' => array( Horde_ActiveSync::BODYPREF_TYPE_HTML => array( 'truncationsize' => false, 'allornone' => 0), Horde_ActiveSync::BODYPREF_TYPE_PLAIN => array( 'truncationsize' => false, 'allornone' => 0) ) ) ); $this->assertEquals(26844, $mbd->html['body']->length(true)); $this->assertEquals(false, $mbd->html['truncated']); } protected function _getFixturesFor13711() { $fetch_ret = unserialize(base64_decode(file_get_contents(__DIR__ . '/fixtures/fixture_fetch'))); return $this->onConsecutiveCalls($fetch_ret); } } Horde_ActiveSync-2.31.1/test/Horde/ActiveSync/MimeTest.php0000664000076500000240000001005312654565405020252 0ustar * @category Horde * @package ActiveSync */ class Horde_ActiveSync_MimeTest extends Horde_Test_Case { public function testHasAttachmentsWithNoAttachment() { $fixture = file_get_contents(__DIR__ . '/fixtures/email_plain.eml'); $mime = new Horde_ActiveSync_Mime(Horde_Mime_Part::parseMessage($fixture)); $this->assertEquals(false, $mime->hasAttachments()); $this->assertEquals(false, $mime->isSigned()); $this->assertEquals(false, $mime->hasiCalendar()); $fixture = file_get_contents(__DIR__ . '/fixtures/iOSMultipartAlternative.eml'); $mime = new Horde_ActiveSync_Mime(Horde_Mime_Part::parseMessage($fixture)); $this->assertEquals(false, $mime->hasAttachments()); $this->assertEquals(false, $mime->isSigned()); $this->assertEquals(false, $mime->hasiCalendar()); } public function testSignedNoAttachment() { $fixture = file_get_contents(__DIR__ . '/fixtures/email_signed.eml'); $mime = new Horde_ActiveSync_Mime(Horde_Mime_Part::parseMessage($fixture)); $this->assertEquals(false, $mime->hasAttachments()); $this->assertEquals(true, $mime->isSigned()); $this->assertEquals(false, $mime->hasiCalendar()); $fixture = file_get_contents(__DIR__ . '/fixtures/encrypted.eml'); $mime = new Horde_ActiveSync_Mime(Horde_Mime_Part::parseMessage($fixture)); $this->assertEquals(false, $mime->isSigned()); } public function testIsEncrypted() { $fixture = file_get_contents(__DIR__ . '/fixtures/encrypted.eml'); $mime = new Horde_ActiveSync_Mime(Horde_Mime_Part::parseMessage($fixture)); $this->assertEquals(true, $mime->isEncrypted()); $fixture = file_get_contents(__DIR__ . '/fixtures/email_signed.eml'); $mime = new Horde_ActiveSync_Mime(Horde_Mime_Part::parseMessage($fixture)); $this->assertEquals(false, $mime->isEncrypted()); } public function testHasAttachmentsWithAttachment() { $fixture = file_get_contents(__DIR__ . '/fixtures/signed_attachment.eml'); $mime = new Horde_ActiveSync_Mime(Horde_Mime_Part::parseMessage($fixture)); $this->assertEquals(true, $mime->hasAttachments()); $this->assertEquals(true, $mime->isSigned()); $this->assertEquals(false, $mime->hasiCalendar()); } public function testReplaceMime() { $fixture = file_get_contents(__DIR__ . '/fixtures/signed_attachment.eml'); $mime = new Horde_ActiveSync_Mime(Horde_Mime_Part::parseMessage($fixture)); foreach ($mime->contentTypeMap() as $id => $type) { if ($mime->isAttachment($id, $type)) { $part = new Horde_Mime_Part(); $part->setType('text/plain'); $part->setContents(sprintf( 'An attachment named %s was removed by Horde_ActiveSync_Test', $mime->getPart($id)->getName(true)) ); $mime->removePart($id); $mime->addPart($part); } } $this->assertEquals(true, $mime->hasAttachments()); $this->assertEquals('An attachment named foxtrotjobs.png was removed by Horde_ActiveSync_Test', $mime->getPart('3')->getContents()); } public function testRfc822MessageWithMultipartDoesNotIterate() { $fixture = file_get_contents(__DIR__ . '/fixtures/rfc822_multipart.eml'); $mime = new Horde_ActiveSync_Mime(Horde_Mime_Part::parseMessage($fixture)); $iterator = new Horde_ActiveSync_Mime_Iterator($mime->base); $this->assertEquals(5, $iterator->count()); } public function testHasiCalendar() { $fixture = file_get_contents(__DIR__ . '/fixtures/invitation_one.eml'); $mime = new Horde_ActiveSync_Mime(Horde_Mime_Part::parseMessage($fixture)); $this->assertEquals(true, $mime->hasAttachments()); $this->assertEquals(false, $mime->isSigned()); $this->assertEquals(true, (boolean)$mime->hasiCalendar()); } } Horde_ActiveSync-2.31.1/test/Horde/ActiveSync/phpunit.xml0000664000076500000240000000005612654565405020225 0ustar Horde_ActiveSync-2.31.1/test/Horde/ActiveSync/PolicyTest.php0000664000076500000240000000232612654565405020626 0ustar * @category Horde * @package ActiveSync */ class Horde_ActiveSync_PolicyTest extends Horde_Test_Case { public function testDefaultWbxml() { $this->markTestIncomplete('Needs updated fixture.'); $stream = fopen('php://memory', 'w+'); $encoder = new Horde_ActiveSync_Wbxml_Encoder($stream); $handler = new Horde_ActiveSync_Policies($encoder); $handler->toWbxml(); rewind($stream); $results = stream_get_contents($stream); fclose($stream); $fixture = file_get_contents(__DIR__ . '/fixtures/default_policies.wbxml'); $this->assertEquals($fixture, $results); } public function testDefaultXml() { $stream = fopen('php://memory', 'w+'); $encoder = new Horde_ActiveSync_Wbxml_Encoder($stream); $handler = new Horde_ActiveSync_Policies($encoder); $handler->toXml(); rewind($stream); $results = stream_get_contents($stream); fclose($stream); $fixture = file_get_contents(__DIR__ . '/fixtures/default_policies.xml'); $this->assertEquals($fixture, $results); } }Horde_ActiveSync-2.31.1/test/Horde/ActiveSync/Rfc822Test.php0000664000076500000240000000556012654565405020340 0ustar * @category Horde * @package ActiveSync */ class Horde_ActiveSync_Rfc822Test extends Horde_Test_Case { /** * @dataProvider headersMultipartAlternativeProvider */ public function testHeadersMultipartAlternative($fixture, $expected) { $rfc822 = new Horde_ActiveSync_Rfc822($fixture); $test = array_change_key_case( $rfc822->getHeaders()->toArray(), CASE_LOWER ); ksort($test); $this->assertEquals( $expected, $test ); if (is_resource($fixture)) { fclose($fixture); } } public function headersMultipartAlternativeProvider() { $expected = array_change_key_case(array( 'Subject' => 'Testing', 'From' => 'mrubinsk@horde.org', 'Content-Type' => 'multipart/alternative; boundary=Apple-Mail-B1C01B47-00D8-4AFB-8B65-DF81C4E4B47D', 'Message-Id' => '', 'Date' => 'Tue, 1 Jan 2013 18:10:37 -0500', 'To' => 'Michael Rubinsky ', 'Content-Transfer-Encoding' => '7bit', 'Mime-Version' => '1.0 (1.0)', 'User-Agent' => 'Horde Application Framework 5' ), CASE_LOWER); ksort($expected); return array( array( file_get_contents(__DIR__ . '/fixtures/iOSMultipartAlternative.eml'), $expected ), array( fopen(__DIR__ . '/fixtures/iOSMultipartAlternative.eml', 'r'), $expected ) ); } public function testBaseMimePart() { $fixture = file_get_contents(__DIR__ . '/fixtures/iOSMultipartAlternative.eml'); $rfc822 = new Horde_ActiveSync_Rfc822($fixture); $mimepart = $rfc822->getMimeObject(); $expected = array( 'multipart/alternative', 'text/plain', 'text/html'); $this->assertEquals($expected, $mimepart->contentTypeMap()); $this->assertEquals(1, $mimepart->findBody('plain')); $this->assertEquals(2, $mimepart->findBody('html')); } /** * See Bug #13456 Wnen we add the Message-Id/User-Agent headers, make sure * we don't cause the subject header to not be MIME encoded. */ public function testMIMEEncodingWhenStandardHeadersAreAdded() { $fixture = file_get_contents(__DIR__ . '/fixtures/mime_encoding.eml'); $rfc822 = new Horde_ActiveSync_Rfc822($fixture, true); $hdrs = Horde_Mime_Headers::parseHeaders($rfc822->getString()); $hdr_array = $hdrs->toArray(array('charset' => 'UTF-8')); $this->assertEquals('=?utf-8?b?w4PDhMOjw6s=?=', $hdr_array['Subject']); } } Horde_ActiveSync-2.31.1/test/Horde/ActiveSync/ServerTest.php0000664000076500000240000000321712654565405020635 0ustar * @category Horde * @package ActiveSync */ class Horde_ActiveSync_ServerTest extends Horde_Test_Case { public function testSupportedVersions() { $factory = new Horde_ActiveSync_Factory_TestServer(); $this->assertEquals('2.5,12.0,12.1,14.0,14.1,16.0', $factory->server->getSupportedVersions()); $factory->server->setSupportedVersion(Horde_ActiveSync::VERSION_TWELVEONE); $this->assertEquals('2.5,12.0,12.1', $factory->server->getSupportedVersions()); $factory->server->setSupportedVersion(Horde_ActiveSync::VERSION_FOURTEEN); $this->assertEquals('2.5,12.0,12.1,14.0', $factory->server->getSupportedVersions()); } public function testSupportedCommands() { $factory = new Horde_ActiveSync_Factory_TestServer(); $this->assertEquals('Sync,SendMail,SmartForward,SmartReply,GetAttachment,GetHierarchy,CreateCollection,DeleteCollection,MoveCollection,FolderSync,FolderCreate,FolderDelete,FolderUpdate,MoveItems,GetItemEstimate,MeetingResponse,Search,Settings,Ping,ItemOperations,Provision,ResolveRecipients,ValidateCert', $factory->server->getSupportedCommands()); $factory->server->setSupportedVersion(Horde_ActiveSync::VERSION_TWOFIVE); $this->assertEquals('Sync,SendMail,SmartForward,SmartReply,GetAttachment,GetHierarchy,CreateCollection,DeleteCollection,MoveCollection,FolderSync,FolderCreate,FolderDelete,FolderUpdate,MoveItems,GetItemEstimate,MeetingResponse,ResolveRecipients,ValidateCert,Provision,Search,Ping', $factory->server->getSupportedCommands()); } } Horde_ActiveSync-2.31.1/test/Horde/ActiveSync/UtilsTest.php0000664000076500000240000000601112654565405020462 0ustar * @category Horde * @package ActiveSync */ class Horde_ActiveSync_UtilsTest extends Horde_Test_Case { public function testBase64Uri() { /* Provision Request for version 12.1 */ $url = 'eRQJBBCuTs0Z9ZK6Vldwb/dM8JusBHx8TOgDUFBD'; $results = Horde_ActiveSync_Utils::decodeBase64($url); $fixture = array( 'ProtVer' => '12.1', 'Cmd' => 'Provision', 'Locale' => 1033, 'DeviceId' => 'ae4ecd19f592ba5657706ff74cf09bac', 'PolicyKey' => 3897326716, 'DeviceType' => 'PPC' ); $this->assertEquals($fixture, $results); /* Smart Forward */ $url = 'eQIJBBCuTs0Z9ZK6Vldwb/dM8JusBHVeHIQDUFBDBwEBAwYxMTkyODEBBUlOQk9Y'; $results = Horde_ActiveSync_Utils::decodeBase64($url); // This is binary data, test it separately. $fixture = array( 'ProtVer' => '12.1', 'Cmd' => 'SmartForward', 'Locale' => 1033, 'DeviceId' => 'ae4ecd19f592ba5657706ff74cf09bac', 'PolicyKey' => 2216451701, 'DeviceType' => 'PPC', 'ItemId' => '119281', 'CollectionId' => 'INBOX', 'AcceptMultiPart' => false, 'SaveInSent' => true ); $this->assertEquals($fixture, $results); } public function testBodyTypePref() { $this->markTestIncomplete('Needs refactoring.'); $fixture = array( 'bodyprefs' => array(Horde_ActiveSync::BODYPREF_TYPE_HTML => true, Horde_ActiveSync::BODYPREF_TYPE_MIME => true) ); $this->assertEquals(Horde_ActiveSync::BODYPREF_TYPE_HTML, Horde_ActiveSync_Utils_Mime::getBodyTypePref($fixture)); $this->assertEquals(Horde_ActiveSync::BODYPREF_TYPE_MIME, Horde_ActiveSync_Utils_Mime::getBodyTypePref($fixture, false)); $fixture = array( 'bodyprefs' => array(Horde_ActiveSync::BODYPREF_TYPE_HTML => true) ); $this->assertEquals(Horde_ActiveSync::BODYPREF_TYPE_HTML, Horde_ActiveSync_Utils_Mime::getBodyTypePref($fixture)); $this->assertEquals(Horde_ActiveSync::BODYPREF_TYPE_HTML, Horde_ActiveSync_Utils_Mime::getBodyTypePref($fixture, false)); $fixture = array( 'bodyprefs' => array(Horde_ActiveSync::BODYPREF_TYPE_HTML => true) ); $this->assertEquals(Horde_ActiveSync::BODYPREF_TYPE_HTML, Horde_ActiveSync_Utils_Mime::getBodyTypePref($fixture)); $this->assertEquals(Horde_ActiveSync::BODYPREF_TYPE_HTML, Horde_ActiveSync_Utils_Mime::getBodyTypePref($fixture, false)); $fixture = array( 'bodyprefs' => array(Horde_ActiveSync::BODYPREF_TYPE_MIME => true) ); $this->assertEquals(Horde_ActiveSync::BODYPREF_TYPE_MIME, Horde_ActiveSync_Utils_Mime::getBodyTypePref($fixture)); $this->assertEquals(Horde_ActiveSync::BODYPREF_TYPE_MIME, Horde_ActiveSync_Utils_Mime::getBodyTypePref($fixture, false)); } }