package.xml0000664000076600000240000026111012273362323010120 0ustar Horde_ActiveSync pear.horde.org Horde ActiveSync Server Library Libraries for implementing an ActiveSync server. Michael J Rubinsky mrubinsk mrubinsk@horde.org yes 2014-02-02 2.12.3 2.12.0 stable stable 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. 5.3.0 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.0.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_Util pear.horde.org 2.0.0 3.0.0alpha1 3.0.0alpha1 Horde_Db pear.horde.org 2.0.3 3.0.0alpha1 3.0.0alpha1 Horde_Imap_Client pear.horde.org 2.15.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. Horde_ActiveSync-2.12.3/doc/Horde/ActiveSync/COPYING0000664000076600000240000003610012273362323016625 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.12.3/doc/Horde/ActiveSync/TODO0000664000076600000240000000361312273362323016265 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. - Email categories - possible to map to custom IMAP Flags? - Support EMPTY_FOLDER commands in ITEMOPERATIONS requests. Need a client that sends this request to implement properly. - SCHEMA support in ITEMOPERATIONS requests. Don't have client that supports currently. 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.Horde_ActiveSync-2.12.3/lib/Horde/ActiveSync/Connector/Exporter.php0000664000076600000240000003401012273362323022044 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-2014 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)) { 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; } } else { if ($this->_step < count($this->_changes)) { $change = $this->_changes[$this->_step]; // 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->err('Missing UID value for an entry in: ' . $this->_currentCollection['id']); $this->_step++; $change = $this->_changes[$this->_step]; } // 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'] : 0; $this->messageChange($change['id'], $message); } catch (Horde_Exception_NotFound $e) { $this->_logger->err(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())); } 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; } // 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; } 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->err(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 === false || $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 PIM * * @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.12.3/lib/Horde/ActiveSync/Connector/Importer.php0000664000076600000240000003513512273362323022046 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-2014 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 exporter 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 PIM * on message addition. * @param string $class The collection class (only needed for SMS). * @since 2.6.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 = 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 'IGNORESMS_' . $clientid; } // Changing an existing object if ($id) { $conflict = $this->_isConflict( Horde_ActiveSync::CHANGE_TYPE_CHANGE, $this->_folderId, $id); if ($conflict && $this->_flags == Horde_ActiveSync::CONFLICT_OVERWRITE_PIM) { $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); } } else { if ($uid = $this->_state->isDuplicatePIMAddition($clientid)) { // Already saw this addition, but PIM never received UID $this->_logger->notice(sprintf( '[%s] Duplicate addition for %s', $this->_procid, $uid) ); return $uid; } } // 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) ); if ($id) { // Assume any error means the message was not found. return array($id, Horde_ActiveSync_Request_Sync::STATUS_NOTFOUND); } else { return array(false, Horde_ActiveSync_Request_Sync::STATUS_SERVERERROR); } } $stat['serverid'] = $this->_folderId; // Record the state of the message $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 $collection The server collection type. * * @return array An array containing ids of successfully deleted messages. */ public function importMessageDeletion(array $ids, $collection) { // Don't support SMS, but can't tell client that. if ($collection == Horde_ActiveSync::CLASS_SMS) { return array(); } // 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) { // Update client state. Note this only modifies the various map // tables to prevent mirroring back any changes. $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 PIM * * @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; } $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. if (count($results) != count($uids)) { $missing = array_diff($uids, array_keys($results)); } else { $missing = array(); } // Update client state. For MOVES, we treat it as a delete from the // SRC folder. $mod = $this->_as->driver->getSyncStamp($this->_folderId); foreach ($uids as $uid) { $change = array(); $change['id'] = $uid; $change['mod'] = $mod; $change['serverid'] = $this->_folderId; $change['class'] = Horde_ActiveSync::CLASS_EMAIL; $change['folderuid'] = $this->_folderUid; $this->_state->updateState( Horde_ActiveSync::CHANGE_TYPE_DELETE, $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 serverid if successful. */ 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(); if (!empty($parent)) { $parent_sid = $collections->getBackendIdForFolderUid($parent); } else { $parent_sid = $parent; } if (!empty($uid)) { $folderid = $collections->getBackendIdForFolderUid($uid); } else { // New folder. $folderid = 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 { // 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 PIM * * @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(); if (!empty($parent)) { $parent_sid = $collections->getBackendIdForFolderUid($parent); } else { $parent_sid = $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 */ if ($type == Horde_ActiveSync::CHANGE_TYPE_CHANGE) { return true; } else { return false; } } return $this->_state->isConflict($stat, $type); } } Horde_ActiveSync-2.12.3/lib/Horde/ActiveSync/Driver/Base.php0000664000076600000240000007215412273362323020422 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-2014 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_Support_Stub; } $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 a message from the backend. * * @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 $collection['truncation'] = 0; 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. */ public function addDefaultBodyPrefTruncation(array $bodyprefs) { if (isset($bodyprefs[Horde_ActiveSync::BODYPREF_TYPE_PLAIN]) && !isset($bodyprefs[Horde_ActiveSync::BODYPREF_TYPE_PLAIN]['truncationsize'])) { $bodyprefs[Horde_ActiveSync::BODYPREF_TYPE_PLAIN]['truncationsize'] = 1048576; // 1024 * 1024 } if (isset($bodyprefs[Horde_ActiveSync::BODYPREF_TYPE_HTML]) && !isset($bodyprefs[Horde_ActiveSync::BODYPREF_TYPE_HTML]['truncationsize'])) { $bodyprefs[Horde_ActiveSync::BODYPREF_TYPE_HTML]['truncationsize'] = 1048576; // 1024 * 1024 } if (isset($bodyprefs[Horde_ActiveSync::BODYPREF_TYPE_RTF]) && !isset($bodyprefs[Horde_ActiveSync::BODYPREF_TYPE_RTF]['truncationsize'])) { $bodyprefs[Horde_ActiveSync::BODYPREF_TYPE_RTF]['truncationsize'] = 1048576; // 1024 * 1024 } if (isset($bodyprefs[Horde_ActiveSync::BODYPREF_TYPE_MIME]) && !isset($bodyprefs[Horde_ActiveSync::BODYPREF_TYPE_MIME]['truncationsize'])) { $bodyprefs[Horde_ActiveSync::BODYPREF_TYPE_MIME]['truncationsize'] = 1048576; // 1024 * 1024 } 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 */ static public 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 folder name E.g., INBOX * @param string $type The folder type, a Horde_ActiveSync::FOLDER_TYPE_* * constant. If empty, assumes FOLDER_TYPE_USER_MAIL * * @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) { // Always use 'RI' for Recipient cache. if ($id == 'RI') { return 'RI'; } $map = $this->_state->getFolderUidToBackendIdMap(); 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. */ 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: (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 */ 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 PIM. * * @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.12.3/lib/Horde/ActiveSync/Driver/Mock.php0000664000076600000240000006705112273362323020441 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-2014 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) { 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 PIM. * * @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.12.3/lib/Horde/ActiveSync/Driver/MockConnector.php0000664000076600000240000000413312273362323022304 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-2014 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.12.3/lib/Horde/ActiveSync/Exception/FolderExists.php0000664000076600000240000000205612273362323022660 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-2014 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync */ class Horde_ActiveSync_Exception_FolderExists extends Horde_ActiveSync_Exception { }Horde_ActiveSync-2.12.3/lib/Horde/ActiveSync/Exception/FolderGone.php0000664000076600000240000000205212273362323022265 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-2014 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync */ class Horde_ActiveSync_Exception_FolderGone extends Horde_ActiveSync_Exception { }Horde_ActiveSync-2.12.3/lib/Horde/ActiveSync/Exception/InvalidRequest.php0000664000076600000240000000206212273362323023201 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-2014 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync */ class Horde_ActiveSync_Exception_InvalidRequest extends Horde_ActiveSync_Exception { }Horde_ActiveSync-2.12.3/lib/Horde/ActiveSync/Exception/StaleState.php0000664000076600000240000000205212273362323022312 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-2014 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync */ class Horde_ActiveSync_Exception_StaleState extends Horde_ActiveSync_Exception { }Horde_ActiveSync-2.12.3/lib/Horde/ActiveSync/Exception/StateGone.php0000664000076600000240000000205012273362323022130 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-2014 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync */ class Horde_ActiveSync_Exception_StateGone extends Horde_ActiveSync_Exception { }Horde_ActiveSync-2.12.3/lib/Horde/ActiveSync/Folder/Base.php0000664000076600000240000000770512273362323020402 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-2014 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 = true; /** * 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.12.3/lib/Horde/ActiveSync/Folder/Collection.php0000664000076600000240000000605412273362323021617 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-2014 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; /** * Flag for indicating we have an initial sync for this collection. * * @var boolean */ public $haveInitialSync = false; /** * 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 = (array)@json_decode($data); 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.12.3/lib/Horde/ActiveSync/Folder/Imap.php0000664000076600000240000003056712273362323020420 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-2014 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 folder's current message list. * Note: This represents the folder list on the client and is affected by * the FILTER 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(); /** * Set message changes. * * @param array $messages An array of message UIDs. * @param array $flags A hash of message read flags, keyed by UID. */ public function setChanges(array $messages, array $flags = array()) { foreach ($messages as $uid) { if ($uid >= $this->uidnext()) { $this->_added[] = $uid; } elseif ($uid >= $this->minuid()) { if ($this->modseq() > 0) { $this->_changed[] = $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; } } } /** * 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 { 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(); } /** * 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 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() { return json_encode(array( 's' => $this->_status, 'm' => $this->_messages, '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']; } /** * 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) ); } } Horde_ActiveSync-2.12.3/lib/Horde/ActiveSync/Folder/RI.php0000664000076600000240000001060212273362323020030 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 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 = (array)@json_decode($data); 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.12.3/lib/Horde/ActiveSync/Imap/Adapter.php0000664000076600000240000017721112273362323020563 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-2014 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync */ class Horde_ActiveSync_Imap_Adapter { /** * @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(); } /** * 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()) { $imap = $this->_getImapOb(); $message = array(array('data' => $msg, 'flags' => $flags)); $mbox = new Horde_Imap_Client_Mailbox($folderid); try { $imap->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) { 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); } } /** * 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); $fetch_res = $imap->search($mbox, $search_q); 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 (!$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. */ 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 * * @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()); // 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)) ); $flags = array(); $modseq = $status[Horde_ActiveSync_Folder_Imap::HIGHESTMODSEQ]; if ($modseq && $folder->modseq() > 0 && $folder->modseq() < $modseq) { $this->_logger->info(sprintf( '[%s] CONDSTORE and CHANGES', $this->_procid)); $folder->checkValidity($status); $query = new Horde_Imap_Client_Fetch_Query(); $query->modseq(); $query->flags(); $query->headerText(array('peek' => true)); try { $fetch_ret = $imap->fetch($mbox, $query, array( 'changedsince' => $folder->modseq() )); } catch (Horde_Imap_Client_Exception $e) { $this->_logger->err($e->getMessage()); throw new Horde_ActiveSync_Exception($e); } // Prepare the changes and flags array, ensuring no changes after // $modseq sneak in yet (they will be caught on the next PING or // SYNC). $changes = array(); foreach ($fetch_ret as $uid => $data) { if ($options['sincedate']) { $since = new Horde_Date($options['sincedate']); $headers = Horde_Mime_Headers::parseHeaders($data->getHeaderText()); try { $date = new Horde_Date($headers->getValue('Date')); if ($date->compareDate($since) <= -1) { // Ignore, it's out of the FILTERTYPE range. $this->_logger->info(sprintf( '[%s] Ignoring UID %s since it is outside of the FILTERTYPE (%s)', $this->_procid, $uid, $headers->getValue('Date'))); continue; } } catch (Horde_Date_Exception $e) {} } 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; } } } $folder->setChanges($changes, $flags); 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); if (!empty($options['softdelete']) && !empty($options['sincedate'])) { $this->_logger->info(sprintf( '[%s] Polling for SOFTDELETE in %s before %d', $this->_procid, $folder->serverid(), $options['sincedate'])); $query = new Horde_Imap_Client_Search_Query(); $query->dateSearch( new Horde_Date($options['sincedate']), Horde_Imap_Client_Search_Query::DATE_BEFORE); $query->ids(new Horde_Imap_Client_Ids($folder->messages())); 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); } 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 ($modseq && $folder->modseq() > 0 && $search_ret['count']) { $folder->setChanges($search_ret['match']->ids, array()); } elseif (count($search_ret['match']->ids)) { $query = new Horde_Imap_Client_Fetch_Query(); $query->flags(); $fetch_ret = $imap->fetch($mbox, $query, array('ids' => $search_ret['match'])); 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 ($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); } if (count($search_ret['match']->ids)) { // Update flags. $query = new Horde_Imap_Client_Fetch_Query(); $query->flags(); try { $fetch_ret = $imap->fetch($mbox, $query, array('ids' => $search_ret['match'])); } 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; } } $folder->setChanges($search_ret['match']->ids, $flags); } $folder->setRemoved($imap->vanished($mbox, null, array('ids' => new Horde_Imap_Client_Ids($folder->messages())))->ids); } elseif ($modseq > 0 && $folder->modseq() == 0) { throw new Horde_ActiveSync_Exception_StaleState('Transition to MODSEQ enabled server'); } $folder->setStatus($status); return $folder; } /** * 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). * - 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(); if (!empty($options['truncation'])) { $options['truncation'] = Horde_ActiveSync::getTruncSize($options['truncation']); } 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 */ 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); $results = $this->_getImapOb()->search($mbox, $search_q); $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. If the server does not * support UIDPLUS, then this is a best guess and might fail * on busy folders. * * @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); if (!$imap->queryCapability('UIDPLUS')) { $status = $imap->status($to, Horde_Imap_Client::STATUS_UIDNEXT_FORCE); $uidnext = $status[Horde_Imap_Client::STATUS_UIDNEXT]; } $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 { $copy_res = $imap->copy($from, $to, array('ids' => $ids_obj, 'move' => true)); } catch (Horde_Imap_Client_Exception $e) { // We already got rid of the missing ids, this must be something // else. $this->_logger->err($e->getMessage()); throw new Horde_ActiveSync_Exception($e); } // old_id => new_id if (is_array($copy_res)) { return $copy_res; } // No UIDPLUS $ret = array(); foreach ($ids_obj->ids as $id) { $ret[$id] = $uidnext++; } return $ret; } /** * 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) { $imap = $this->_getImapOb(); $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 = $imap->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 ($old == $new) { return; } $imap = $this->_getImapOb(); if (!empty($parent)) { $ns = $this->_defaultNamespace(); $new = $parent . $ns['delimiter'] . $new; } $new_mbox = new Horde_Imap_Client_Mailbox($new); try { $imap->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); } $imap = $this->_getImapOb(); try { $imap->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); } $imap = $this->_getImapOb(); try { $imap->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). * - 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()) { $imap = $this->_getImapOb(); $version = empty($options['protocolversion']) ? Horde_ActiveSync::VERSION_TWOFIVE : $options['protocolversion']; $imap_message = new Horde_ActiveSync_Imap_Message($imap, $mbox, $data); $eas_message = Horde_ActiveSync::messageFactory('Mail'); // Build To: data (POOMMAIL_TO has a max length of 1024). $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) > 1024) { 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 $eas_message->from = $imap_message->getFromAddress(); $eas_message->subject = $this->_validateUtf8($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); $eas_message->cc = $imap_message->getCc(); $eas_message->reply_to = $imap_message->getReplyTo(); // 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')) { $priorty = preg_replace('/\D+/', '', $priority); } else { $priority = $imap_message->getHeaders()->getValue('Importance'); } $eas_message->importance = $this->_getEASImportance($priority); // Get the body data and ensure we have something to send. $message_body_data = $this->_validateMessageBodyData($imap_message->getMessageBodyData($options)); if ($version == Horde_ActiveSync::VERSION_TWOFIVE) { $eas_message->body = $message_body_data['plain']['body']->stream; $eas_message->bodysize = $message_body_data['plain']['body']->length(true); $eas_message->bodytruncated = $message_body_data['plain']['truncated']; $eas_message->attachments = $imap_message->getAttachments($version); } else { // Get the message body and determine original type. if (!empty($message_body_data['html'])) { $eas_message->airsyncbasenativebodytype = Horde_ActiveSync::BODYPREF_TYPE_HTML; } else { $eas_message->airsyncbasenativebodytype = Horde_ActiveSync::BODYPREF_TYPE_PLAIN; } $airsync_body = Horde_ActiveSync::messageFactory('AirSyncBaseBody'); if (isset($options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_MIME]) && ($options['mimesupport'] == Horde_ActiveSync::MIME_SUPPORT_ALL || ($options['mimesupport'] == Horde_ActiveSync::MIME_SUPPORT_SMIME && $imap_message->isSigned()))) { $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()) { // Sending a non-signed MIME message, start building the // UTF-8 converted structure. $mime = new Horde_Mime_Part(); $mime->setType('multipart/alternative'); // Populate the text/plain part if we have one. if (!empty($message_body_data['plain'])) { $plain_mime = new Horde_Mime_Part(); $plain_mime->setType('text/plain'); $plain_mime->setContents($message_body_data['plain']['body']->stream, array('usestream' => true)); $plain_mime->setCharset('UTF-8'); $mime->addPart($plain_mime); } // Populate the text/html part if we have one. if (!empty($message_body_data['html'])) { $html_mime = new Horde_Mime_Part(); $html_mime->setType('text/html'); $html_mime->setContents($message_body_data['html']['body']->stream, array('usestream' => true)); $html_mime->setCharset('UTF-8'); $mime->addPart($html_mime); } // 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; } // Populate the EAS body structure with the MIME data. $airsync_body->data = $base->toString(array( 'headers' => $imap_message->getHeaders(), '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)); $airsync_body->estimateddatasize = $raw->getBytes(); $airsync_body->data = $raw->getString(); $eas_message->messageclass = 'IPM.Note.SMIME.MultipartSigned'; // Might not know if we have attachments, but take a best // guess. $eas_message->airsyncbaseattachments = $imap_message->getAttachments($version); } // MIME Truncation $airsync_body->type = Horde_ActiveSync::BODYPREF_TYPE_MIME; $this->_logger->info(sprintf( '[%s] Checking MIMETRUNCATION: %s, ServerData: %s', $this->_procid, $options['truncation'], $airsync_body->estimateddatasize)); if (!empty($options['truncation']) && $airsync_body->estimateddatasize > $options['truncation']) { ftruncate($airsync_body->data, $options['truncation']); $airsync_body->truncated = '1'; } else { $airsync_body->truncated = '0'; } $eas_message->airsyncbasebody = $airsync_body; } elseif (isset($options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_HTML]) || isset($options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_RTF])) { // Sending non MIME encoded HTML message text. $this->_logger->info(sprintf( '[%s] Sending HTML Message.', $this->_procid)); if (empty($message_body_data['html'])) { $airsync_body->type = Horde_ActiveSync::BODYPREF_TYPE_PLAIN; $message_body_data['html'] = array( 'body' => $message_body_data['plain']['body'], 'estimated_size' => $message_body_data['plain']['size'], 'truncated' => $message_body_data['plain']['truncated'] ); } else { $airsync_body->type = Horde_ActiveSync::BODYPREF_TYPE_HTML; } $airsync_body->estimateddatasize = $message_body_data['html']['estimated_size']; $airsync_body->truncated = $message_body_data['html']['truncated']; $airsync_body->data = $message_body_data['html']['body']->stream; $eas_message->airsyncbasebody = $airsync_body; $eas_message->airsyncbaseattachments = $imap_message->getAttachments($version); } elseif (isset($options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_PLAIN])) { // Non MIME encoded plaintext $this->_logger->info(sprintf( '[%s] Sending PLAINTEXT Message.', $this->_procid)); $airsync_body->estimateddatasize = $message_body_data['plain']['size']; $airsync_body->truncated = $message_body_data['plain']['truncated']; $airsync_body->data = $message_body_data['plain']['body']->stream; $airsync_body->type = Horde_ActiveSync::BODYPREF_TYPE_PLAIN; $eas_message->airsyncbasebody = $airsync_body; $eas_message->airsyncbaseattachments = $imap_message->getAttachments($version); } } // Preview? if ($version >= Horde_ActiveSync::VERSION_FOURTEEN && !empty($options['bodyprefs']['preview'])) { $message_body_data['plain']['body']->rewind(); $eas_message->airsyncbasebody->preview = $message_body_data['plain']['body']->substring(0, $options['bodyprefs']['preview']); } // Check for special message types. $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; } } } } // 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 = $mime_part->getContents(); $vCal = new Horde_Icalendar(); if ($vCal->parsevCalendar($data, 'VCALENDAR', $mime_part->getCharset())) { 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, $eas_message->from); 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); } return $eas_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(); $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)) ); } 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 (strtolower($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 * @throws Horde_ActiveSync_Exception */ protected function _getiTipStatus($vCal, $from) { foreach ($vCal->getComponents() as $key => $component) { switch ($component->getType()) { case 'vEvent': try { $atname = $component->getAttribute('ATTENDEE'); } catch (Horde_Icalendar_Exception $e) { throw new Horde_ActiveSync_Exception($e); } // EAS only allows a single name per response. if (is_array($atname)) { $atname = current($atname); } try { $version = $component->getAttribute('VERSION'); } catch (Horde_Icalendar_Exception $e) { throw new Horde_ActiveSync_Exception($e); } if ($version < 2) { $addr = new Horde_Mail_Rfc822_Address($atname); if (!$addr->isValid) { throw new Horde_ActiveSync_Exception('Invalid Email Address'); } $attendee = Horde_String::lower($addr->bare_address); } else { $attendee = str_replace('mailto:', '', Horde_String::lower($atname)); } // Require the sender to be the same as the attendee. $addr = new Horde_Mail_Rfc822_Address($from); $from = Horde_String::lower($addr->bare_address); if ($from != $attendee) { throw new Horde_ActiveSync_Exception(sprintf( 'Unmatched sender and attendee: %s, %s', $from, $attendee)); } 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 ); $imap = $this->_getImapOb(); $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)); } $ids = new Horde_Imap_Client_Ids($uids); try { return $imap->fetch($mbox, $query, array('ids' => $ids)); } 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; } /** * 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 function _stripNon7BitChars($text) { return preg_replace('/[^\x09\x0A\x0D\x20-\x7E]/', '', $text); } /** * 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. */ protected function _validateUtf8($data, $from_charset) { $this->_logger->info(sprintf( '[%s] Validating UTF-8 data coming from %s', $this->_procid, $from_charset)); $text = Horde_String::convertCharset($data, $from_charset, 'UTF-8'); if (!Horde_String::validUtf8($text)) { $this->_logger->info(sprintf( '[%s] Found invalid UTF-8 data, try different encodings.', $this->_procid)); $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)) { $this->_logger->info(sprintf( '[%s] Found valid UTF-8 data when using %s', $this->_procid, $charset)); return $text; } } } // Invalid UTF-8 still found. Strip out non 7-bit characters, or if // that fails, force a conersion 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 $this->_logger->info(sprintf( '[%s] Could not encode UTF-8 data. Removing non 7-bit characters.', $this->_procid)); $chunk_size = 4000; $text = ''; while ($data !== false && strlen($data)) { $test = $this->_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; } /** * Converts and validates the message body data structure. * * @param array $data Message body data structure. * * @return array The message body data structure, with the [html][body] and * [plain][body] data converted to UTF-8, EOL normalized and * placed in a stream. */ protected function _validateMessageBodyData($data) { //We will need the eol filter to work around PHP bug 65776. stream_filter_register('horde_eol', 'Horde_Stream_Filter_Eol'); if (!empty($data['plain'])) { $stream = new Horde_Stream_Temp(array('max_memory' => 1048576)); $filter_h = stream_filter_append($stream->stream, 'horde_eol', STREAM_FILTER_WRITE); $stream->add($this->_validateUtf8($data['plain']['body'], $data['plain']['charset']), true); stream_filter_remove($filter_h); $data['plain']['body'] = $stream; } if (!empty($data['html'])) { $stream = new Horde_Stream_Temp(array('max_memory' => 1048576)); $filter_h = stream_filter_append($stream->stream, 'horde_eol', STREAM_FILTER_WRITE); $stream->add($this->_validateUtf8($data['html']['body'], $data['html']['charset']), true); stream_filter_remove($filter_h); $data['html']['body'] = $stream; } return $data; } } Horde_ActiveSync-2.12.3/lib/Horde/ActiveSync/Imap/Message.php0000664000076600000240000007276212273362323020574 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-2014 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync */ class Horde_ActiveSync_Imap_Message { /** * Message data. * * @var Horde_Imap_Client_Fetch_Data */ protected $_data; /** * Message structure. * * @var Horde_Mime_Part */ protected $_message; /** * The imap client. * * @var Horde_Imap_Client_Base */ protected $_imap; /** * The envelope data. * * @var Horde_Imap_Client_Data_Envelope */ protected $_envelope; /** * 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->_message = $data->getStructure(); $this->_uid = $data->getUid(); $this->_flags = $data->getFlags(); $this->_mbox = $mbox; $this->_data = $data; $this->_envelope = $data->getEnvelope(); } /** * 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 (!$full = $this->_data->getFullMsg()) { $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_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->_message; } /** * 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()) { $version = empty($options['protocolversion']) ? Horde_ActiveSync::VERSION_TWOFIVE : $options['protocolversion']; // 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. $text_id = $this->_message->findBody('plain'); $html_id = $this->_message->findBody('html'); // Deduce which part(s) we need to request. $want_html_text = $version >= Horde_ActiveSync::VERSION_TWELVE && (!empty($options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_HTML]) || !empty($options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_MIME]) || !empty($options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_RTF])); $want_plain_text = $version == Horde_ActiveSync::VERSION_TWOFIVE || empty($options['bodyprefs']) || !empty($options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_PLAIN]) || !empty($options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_MIME]) || ($want_html_text && empty($html_id)); $want_html_as_plain = false; if (!empty($text_id) && $want_plain_text) { $text_body_part = $this->_message->getPart($text_id); $charset = $text_body_part->getCharset(); } elseif ($want_plain_text && !empty($html_id) && empty($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->_message->getPart($html_id); $html_charset = $html_body_part->getCharset(); } // Sanity check the truncation stuff if (empty($options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_PLAIN]) && !empty($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. $options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_PLAIN] = $options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_HTML]; } $query = new Horde_Imap_Client_Fetch_Query(); $query_opts = array( 'decode' => true, 'peek' => true ); // Get body information if ($version >= Horde_ActiveSync::VERSION_TWELVE) { if (!empty($html_id)) { $query->bodyPartSize($html_id); $query->bodyPart($html_id, $query_opts); } if (!empty($text_id)) { $query->bodyPart($text_id, $query_opts); $query->bodyPartSize($text_id); } } else { // EAS 2.5 Plaintext body $query->bodyPart($text_id, $query_opts); $query->bodyPartSize($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 = array(); if (!empty($text_id) && $want_plain_text) { $text = $data->getBodyPart($text_id); if (!$data->getBodyPartDecode($text_id)) { $text_body_part->setContents($text); $text = $text_body_part->getContents(); } $text_size = !is_null($data->getBodyPartSize($text_id)) ? $data->getBodyPartSize($text_id) : Horde_String::length($text); if (!empty($options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_PLAIN]['truncationsize'])) { // EAS >= 12.0 truncation $text = Horde_String::substr($text, 0, $options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_PLAIN]['truncationsize'], $charset); } $truncated = $text_size > Horde_String::length($text); if ($version >= Horde_ActiveSync::VERSION_TWELVE && $truncated && !empty($options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_PLAIN]['allornone'])) { $text = ''; } $return ['plain'] = array( 'charset' => $charset, 'body' => $text, 'truncated' => $truncated, 'size' => $text_size); } if (!empty($html_id) && $want_html_text) { $html = $data->getBodyPart($html_id); if (!$data->getBodyPartDecode($html_id)) { $html_body_part->setContents($html); $html = $html_body_part->getContents(); } // Size of the original HTML part. $html_size = !is_null($data->getBodyPartSize($html_id)) ? $data->getBodyPartSize($html_id) : Horde_String::length($html); if (!empty($options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_HTML]['truncationsize'])) { $html = Horde_String::substr( $html, 0, $options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_HTML]['truncationsize'], $html_charset); } elseif ($want_html_as_plain) { $html = Horde_Text_Filter::filter( $html, 'Html2text', array('charset' => $html_charset)); // Get the new size, since it probably changed. $html_size = Horde_String::length($html); if (!empty($options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_PLAIN]['truncationsize'])) { // EAS >= 12.0 truncation $html = Horde_String::substr( $html, 0, $options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_PLAIN]['truncationsize'], $html_charset); } } // Was the part truncated? $truncated = $html_size > Horde_String::length($html); if ($want_html_as_plain) { $return['plain'] = array( 'charset' => $html_charset, 'body' => $html, 'truncated' => $truncated, 'size' => $html_size ); } if ($version >= Horde_ActiveSync::VERSION_TWELVE && !($truncated && !empty($options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_HTML]['allornone']))) { $return['html'] = array( 'charset' => $html_charset, 'body' => $html, 'estimated_size' => $html_size, 'truncated' => $truncated); } } return $return; } /** * 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(); $map = $this->_message->contentTypeMap(); foreach ($map as $id => $type) { 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); } } 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->_message->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'); $tnef_data = $tnef_parser->decompress($mime_part->getContents()); 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 messages attachment. * * @return array An array of Horde_Mime_Part objects. */ public function getAttachmentsMimeParts() { $mime_parts = array(); $map = $this->_message->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; } } 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 (reference). */ public function getMimePart($id, array $options = array()) { $part = $this->_message->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. */ 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_CombineSream( 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. */ public function getCc() { $cc = new Horde_Mail_Rfc822_List($this->_envelope->cc->addresses); return $cc->writeAddress(); } /** * Return the ReplyTo Address * * @return string */ public function getReplyTo() { $r = $this->_envelope->reply_to->addresses; $a = new Horde_Mail_Rfc822_Address(current($r)); return $a->writeAddress(false); } /** * Return the message's From: address. * * @return string The From address of this message. */ public function getFromAddress() { $from = $this->_envelope->from->addresses; $a = new Horde_Mail_Rfc822_Address(current($from)); 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 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->_message->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. */ public function isAttachment($id, $mime_type) { switch ($mime_type) { case 'text/plain': if (!($this->_message->findBody('plain') == $id)) { return true; } return false; case 'text/html': if (!($this->_message->findBody('html') == $id)) { return true; } return false; case 'application/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 the MIME part of the iCalendar attachment, if available. * * @return mixed The mime part, if present, false otherwise. */ public function hasiCalendar() { if (!$this->hasAttachments()) { return false; } foreach ($this->contentTypeMap() as $id => $type) { if ($type == 'text/calendar') { return $this->getMimePart($id); } } return false; } /** * Return the hasAttachments flag * * @return boolean */ public function hasAttachments() { if (isset($this->_hasAttachments)) { return $this->_hasAttachments; } foreach ($this->contentTypeMap() as $id => $type) { if ($this->isAttachment($id, $type)) { $this->_hasAttachments = true; return true; } } $this->_hasAttachments = false; return false; } /** * Return the S/MIME status of this message (RFC2633) * * @param Horde_Mime_Part $message A mime part to check. If omitted, use * $this->_message. * * @return boolean True if message is S/MIME signed or encrypted, * false otherwise. */ public function isSigned($message = null) { if (empty($message)) { $message = $this->_message; } if ($message->getType() == 'application/pkcs7-mime') { return true; } if ($message->getPrimaryType() == 'multipart') { if ($message->getSubType() == 'signed') { return true; } // Signed/encrypted part might be lower in the mime structure foreach ($message->getParts() as $part) { if ($this->isSigned($part)) { return true; } } } return false; } } Horde_ActiveSync-2.12.3/lib/Horde/ActiveSync/Interface/ImapFactory.php0000664000076600000240000000345012273362323022424 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-2014 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.12.3/lib/Horde/ActiveSync/Interface/LoggerFactory.php0000664000076600000240000000274612273362323022764 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-2014 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.12.3/lib/Horde/ActiveSync/Message/AirSyncBaseAttachment.php0000664000076600000240000000703212273362323024046 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-2014 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 contentid The Content-Id of the mime part. * @property contentlocation @todo * @property 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.12.3/lib/Horde/ActiveSync/Message/AirSyncBaseBody.php0000664000076600000240000000673712273362323022666 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-2014 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 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 * * @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) * * @return Horde_ActiveSync_Message_Base */ 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 ); } } /** * Return the message type. * * @return string */ public function getClass() { return 'AirSyncBaseBody'; } }Horde_ActiveSync-2.12.3/lib/Horde/ActiveSync/Message/AirSyncBaseFileAttachment.php0000664000076600000240000000652512273362323024654 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-2014 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 The attachment data. * @property integer total The total size of the attachment. * @property integer range @todo */ 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) { if (is_resource($data)) { stream_filter_append($data, 'convert.base64-encode', STREAM_FILTER_READ); } else { $data = base64_encode($data); } } return $data; } } Horde_ActiveSync-2.12.3/lib/Horde/ActiveSync/Message/Appointment.php0000664000076600000240000006633712273362323022205 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-2014 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'; /* 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; /* 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_LOCATION => array (self::KEY_ATTRIBUTE => 'location'), 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::KEY_TYPE => self::TYPE_HEX), 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, 'location' => false, 'meetingstatus' => self::MEETING_NOT_MEETING, 'recurrence' => false, 'reminder' => false, 'sensitivity' => false, 'starttime' => false, 'subject' => false, 'timezone' => false, 'uid' => false, ); /** * 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) * * @return Horde_ActiveSync_Message_Base */ public function __construct(array $options = array()) { parent::__construct($options); 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 ); } } } /** * 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. */ 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. */ 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 */ 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. */ 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 */ public function setSubject($subject) { $this->_properties['subject'] = $subject; } /** * Get the subject * * @return string The UTF-8 subject string */ public function getSubject() { return $this->_getAttribute('subject'); } /** * Set the appointment uid. Note that this is the PIM's UID value, and not * the value that the server uses for the UID. ActiveSync messages do not * include any server uid value as part of the message natively. * * @param string $uid The server's uid for this appointment */ public function setUid($uid) { $this->_properties['uid'] = $uid; } /** * Get the PIM's UID. See not above regarding server UIDs. * * @return string */ public function getUid() { return $this->_getAttribute('uid'); } /** * Because the PIM 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 */ public function setLocation($location) { $this->_properties['location'] = $location; } /** * Get the location field * * @return string */ 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; 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 */ public function setSensitivity($sensitivity) { $this->_properties['sensitivity'] = $sensitivity; } /** * Return the sensitivity setting for this appointment * * @return integer The SENSITIVITY constant */ public function getSensitivity() { return $this->_getAttribute('sensitivity'); } /** * Sets the busy status for this appointment * * @param integer $busy The BUSYSTATUS constant */ public function setBusyStatus($busy) { $this->_properties['busystatus'] = $busy; } /** * Return the busy status for this appointment. * * @return integer The BUSYSTATUS constant */ 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 */ public function setResponseType($response) { $this->_properties['responsetype'] = $response; } /** * Get response type * * @return integer The responsetype constant */ 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. */ 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. */ 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 */ public function setBody($body) { $this->_properties['body'] = $body; } /** * Get the appointment's body * * @return string UTF-8 encoded string */ 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']; } /** * Return the collection class name the object is for. * * @return string */ public function getClass() { return 'Calendar'; } } Horde_ActiveSync-2.12.3/lib/Horde/ActiveSync/Message/Attachment.php0000664000076600000240000000637012273362323021766 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-2014 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.12.3/lib/Horde/ActiveSync/Message/Attendee.php0000664000076600000240000000526112273362323021425 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-2014 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 ); }Horde_ActiveSync-2.12.3/lib/Horde/ActiveSync/Message/Base.php0000664000076600000240000005321012273362323020543 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-2014 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; /* Types */ const TYPE_DATE = 1; const TYPE_HEX = 2; const TYPE_DATE_DASHES = 3; const TYPE_MAPI_STREAM = 4; /** * 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; /** * Logger * * @var Horde_Log_Logger */ protected $_logger; /** * An array describing the non-ghosted elements this message supports. * * @var array */ protected $_supported = array(); /** * Existance 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 */ protected $_device; /** * 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 * * @return Horde_ActiveSync_Message_Base */ public function __construct(array $options = array()) { if (!empty($options['logger'])) { $this->_logger = $options['logger']; } else { $this->_logger = new Horde_Support_Stub(); } if (!empty($options['protocolversion'])) { $this->_version = $options['protocolversion']; } if (!empty($options['device'])) { $this->_device = $options['device']; } } /** * 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; } /** * 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. */ public function setSupported(array $fields) { $this->_supported = array(); 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 ghosted property 1) IS listed in the supported list and 2) NOT * present in the current message. If it's IN the supported list and NOT * in the current message, then it IS ghosted and the server should keep * the field's current value when performing any change action due to this * message. * * @param string $property The property to check * * @return boolean */ public function isGhosted($property) { if (array_search($property, $this->_supported) === false) { return false; } elseif (empty($this->_exists[$property])) { return true; } return false; } /** * 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('Tag ' . $entity[Horde_ActiveSync_Wbxml::EN_TAG] . ' unexpected in type XML type ' . 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) { if (!$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->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 (!$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()); if (!$decoder->getElementEndTag()) { throw new Horde_ActiveSync_Exception('Missing expected wbxml end tag'); } } elseif ($map[self::KEY_TYPE] == self::TYPE_HEX) { $decoded = self::hex2bin($decoder->getElementContent()); if (!$decoder->getElementEndTag()) { throw new Horde_ActiveSync_Exception('Missing expected wbxml end tag'); } } else { $class = $map[self::KEY_TYPE]; $subdecoder = new $class(array( 'protocolversion' => $this->_version, 'logger' => $this->_logger) ); if ($subdecoder->decodeStream($decoder) === false) { throw new Horde_ActiveSync_Exception('Missing expected wbxml end tag'); } $decoded = $subdecoder; if (!$decoder->getElementEndTag()) { $this->_logger->err('No end tag for ' . $entity[Horde_ActiveSync_Wbxml::EN_TAG]); throw new Horde_ActiveSync_Exception('Missing expected wbxml end tag'); } } } else { // Simple type, just get content $decoded = $decoder->getElementContent(); if ($decoded === false) { $this->_logger->err('Unable to get content for ' . $entity[Horde_ActiveSync_Wbxml::EN_TAG]); } if (!$decoder->getElementEndTag()) { $this->_logger->err('Unable to get end tag for ' . $entity[Horde_ActiveSync_Wbxml::EN_TAG]); throw new Horde_ActiveSync_Exception('Missing expected wbxml end tag'); } } // $decoded now contains data object (or string) $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; } } } /** * 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) { 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)) { // Subobjects 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])) { $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(); } } } $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'); $filter_null = stream_filter_prepend($data, 'horde_null', STREAM_FILTER_READ); $filter_eol = 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. * * @param Horde_Date $dt The datetime to format (assumed to 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. * * @param string $ts The timestamp * * @return Horde_Date The Horde_Date */ 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)) { return new Horde_Date($ts); } throw new Horde_ActiveSync_Exception('Invalid date format'); } /** * Function which converts a hex entryid to a binary entryid. * * @param string $data The hexadecimal string * * @return string The binary data */ static private 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.12.3/lib/Horde/ActiveSync/Message/Contact.php0000664000076600000240000004671112273362323021274 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-2014 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync * * 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 * * @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) * * @return Horde_ActiveSync_Message_Base */ 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 [type] $ts [description] * @return [type] [description] */ protected function _parseDate($ts) { $date = parent::_parseDate($ts); // @todo: Remove this in H6. if (empty($this->_device)) { return $date; } return $this->_device->normalizePoomContactsDates($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); } } Horde_ActiveSync-2.12.3/lib/Horde/ActiveSync/Message/Document.php0000664000076600000240000000375612273362323021461 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 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.12.3/lib/Horde/ActiveSync/Message/DocumentLibrary.php0000664000076600000240000000516212273362323022777 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 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.12.3/lib/Horde/ActiveSync/Message/Exception.php0000664000076600000240000001454712273362323021641 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-2014 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 * @property integer deleted * @property array attendees * @property array categories */ 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_LOCATION => array(self::KEY_ATTRIBUTE => 'location'), 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_EXCEPTIONSTARTTIME => array(self::KEY_ATTRIBUTE => 'exceptionstarttime', self::KEY_TYPE => self::TYPE_DATE), 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, 'location' => false, 'endtime' => false, 'sensitivity' => false, 'busystatus' => false, 'alldayevent' => false, 'reminder' => false, 'meetingstatus' => false, 'exceptionstarttime' => false, 'deleted' => false, 'attendees' => array(), 'categories' => array(), ); /** * 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 */ public function getExceptionStartTime() { return $this->_getAttribute('exceptionstarttime'); } /** * Set the exceptionStartTime value. * * @param Horde_Date $date The exceptionStartTime. */ public function setExceptionStartTime($date) { $this->exceptionstarttime = $date; } } Horde_ActiveSync-2.12.3/lib/Horde/ActiveSync/Message/Flag.php0000664000076600000240000001156312273362323020547 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-2014 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 Horde_Date 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::KEY_TYPE => self::TYPE_DATE_DASHES), 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.12.3/lib/Horde/ActiveSync/Message/Folder.php0000664000076600000240000000535512273362323021113 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-2014 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.12.3/lib/Horde/ActiveSync/Message/GalPicture.php0000664000076600000240000000304112273362323021725 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-2014 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.12.3/lib/Horde/ActiveSync/Message/Mail.php0000664000076600000240000003761012273362323020561 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-2014 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 mixed 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). * * // 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'; const POOMMAIL2_MEETINGMESSAGETYPE = 'POOMMAIL2:MeetingMessageType'; /* 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 * * @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) * * @return Horde_ActiveSync_Message_Base */ 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, ); 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'), self::POOMMAIL2_MEETINGMESSAGETYPE => array(self::KEY_ATTRIBUTE => 'meetingmessagetype') ); $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, 'meetingmessagetype' => false, 'categories' => array(), // Internal use 'messageid' => false, 'answered' => false, 'forwarded' => 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.12.3/lib/Horde/ActiveSync/Message/MeetingRequest.php0000664000076600000240000002437212273362323022641 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-2014 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; /** * 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->_vEvent($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; } } $tz = new Horde_Mapi_Timezone(); $this->timezone = $tz->getSyncTZFromOffsets( $tz->getOffsetsFromDate(new Horde_Date())); $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 _vEvent($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')); $this->endtime = new Horde_Date($vevent->getAttribute('DTEND')); } 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->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.12.3/lib/Horde/ActiveSync/Message/Note.php0000664000076600000240000000533212273362323020600 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-2014 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.12.3/lib/Horde/ActiveSync/Message/RecipientInformation.php0000664000076600000240000000423312273362323024022 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-2014 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.12.3/lib/Horde/ActiveSync/Message/Recurrence.php0000664000076600000240000001305112273362323021765 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-2014 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 * * @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) * * @return Horde_ActiveSync_Message_Base */ 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.12.3/lib/Horde/ActiveSync/Message/ResolveRecipientsPicture.php0000664000076600000240000000321112273362323024666 0ustar * @package ActiveSync */ /** * Horde_ActiveSync_Message_ResolveRecipientsPicture:: Encapsulate the 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-2014 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync * */ 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.12.3/lib/Horde/ActiveSync/Message/SendMail.php0000664000076600000240000001052512273362323021367 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-2014 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync */ 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'; /** * 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, ); 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 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.12.3/lib/Horde/ActiveSync/Message/SendMailSource.php0000664000076600000240000000526712273362323022557 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-2014 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync */ 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.12.3/lib/Horde/ActiveSync/Message/Task.php0000664000076600000240000003607112273362323020601 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-2014 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 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_REGENERATE => array (self::KEY_ATTRIBUTE => 'regenerate'), self::POOMTASKS_DEADOCCUR => array (self::KEY_ATTRIBUTE => 'deadoccur'), 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 * * @param array $options Configuration options for the message: * - logger: (Horde_Log_Logger) A logger instance * DEFAULT: none (No logging). * - protcolversion: (float) The version of EAS to support. * DEFAULT: Horde_ActiveSync::VERSION_TWOFIVE (2.5) * * @return Horde_ActiveSync_Message_Base */ 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, ); if ($this->_version > Horde_ActiveSync::VERSION_TWELVEONE) { $this->_mapping += array( self::POOMTASKS_CALENDARTYPE => array(self::KEY_ATTRIBUTE => 'calendartype'), self::POOMTASKS_ISLEAPMONTH => array(self::KEY_ATTRIBUTE => 'isleapmonth') ); $this->_properties += array( 'calendartype' => false, 'isleapmonth' => 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; 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(); } $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.12.3/lib/Horde/ActiveSync/Message/TaskRecurrence.php0000664000076600000240000000736412273362323022622 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-2014 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_TYPE => array(self::KEY_ATTRIBUTE => 'type'), Horde_ActiveSync_Message_Task::POOMTASKS_UNTIL => array(self::KEY_ATTRIBUTE => 'until', self::KEY_TYPE => self::TYPE_DATE), Horde_ActiveSync_Message_Task::POOMTASKS_OCCURRENCES => array(self::KEY_ATTRIBUTE => 'occurrences'), Horde_ActiveSync_Message_Task::POOMTASKS_INTERVAL => array(self::KEY_ATTRIBUTE => 'interval'), 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_START => array(self::KEY_ATTRIBUTE => 'start', self::KEY_TYPE => self::TYPE_DATE), ); /** * 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, ); }Horde_ActiveSync-2.12.3/lib/Horde/ActiveSync/Request/Autodiscover.php0000664000076600000240000002366012273362323022412 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-2014 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync */ 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(); // 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. if (empty($values) && !empty($server['HTTP_AUTHORIZATION'])) { $hash = base64_decode(str_replace('Basic ', '', $server['HTTP_AUTHORIZATION'])); if (strpos($hash, ':') !== false) { list($email, $pass) = explode(':', $hash, 2); } } elseif (empty($values)) { throw new Horde_Exception_AuthenticationFailure('No username provided.'); } else { $email = $values[2]['value']; } if (!$this->_activeSync->authenticate($email)) { 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.12.3/lib/Horde/ActiveSync/Request/Base.php0000664000076600000240000002205012273362323020605 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-2014 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync */ 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 PIM 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) ); 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.12.3/lib/Horde/ActiveSync/Request/FolderCreate.php0000664000076600000240000002150612273362323022277 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-2014 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync */ 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). $server_uid = false; if ($this->_decoder->getElementStartTag(Horde_ActiveSync::FOLDERHIERARCHY_SERVERENTRYID)) { $server_uid = $this->_decoder->getElementContent(); if (!$this->_decoder->getElementEndTag()) { throw new Horde_ActiveSync_Exception('Protocol Error'); } } 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); $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) { $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) { $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(); $this->_state->setNewSyncKey($newsynckey); $this->_state->save(); return true; } }Horde_ActiveSync-2.12.3/lib/Horde/ActiveSync/Request/FolderSync.php0000664000076600000240000002403512273362323022010 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-2014 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync */ 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 PIM 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 -> PIM 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.12.3/lib/Horde/ActiveSync/Request/GetAttachment.php0000664000076600000240000000534212273362323022470 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-2014 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync */ 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.12.3/lib/Horde/ActiveSync/Request/GetHierarchy.php0000664000076600000240000000373712273362323022324 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-2014 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync */ 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.12.3/lib/Horde/ActiveSync/Request/GetItemEstimate.php0000664000076600000240000002566112273362323023000 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-2014 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync */ 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.12.3/lib/Horde/ActiveSync/Request/ItemOperations.php0000664000076600000240000004246412273362323022710 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-2014 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync */ 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 } } $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(strtolower($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; 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.12.3/lib/Horde/ActiveSync/Request/MeetingResponse.php0000664000076600000240000001605312273362323023050 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-2014 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync */ 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'; // 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.12.3/lib/Horde/ActiveSync/Request/MoveItems.php0000664000076600000240000001412212273362323021644 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-2014 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync */ 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.12.3/lib/Horde/ActiveSync/Request/Ping.php0000664000076600000240000003033512273362323020635 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-2014 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync */ 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 PIM. PING is sent periodically by the PIM * 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(); $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'] : 10; $this->_logger->info(sprintf( '[%s] Using cached heartbeat of %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)); $this->_decoder->getElementEndTag(); } 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(); // Explicitly asked for a collection, make sure we have a key. $collections->addCollection($collection, true); } // 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(); $this->_statusCode = self::STATUS_NEEDSYNC; } } // 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.12.3/lib/Horde/ActiveSync/Request/Provision.php0000664000076600000240000004101412273362323021724 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-2014 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync */ 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 PIM: ' . $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(); while (($field = ($this->_decoder->getElementStartTag(Horde_ActiveSync_Request_Settings::SETTINGS_MODEL) ? Horde_ActiveSync_Request_Settings::SETTINGS_MODEL : ($this->_decoder->getElementStartTag(Horde_ActiveSync_Request_Settings::SETTINGS_IMEI) ? Horde_ActiveSync_Request_Settings::SETTINGS_IMEI : ($this->_decoder->getElementStartTag(Horde_ActiveSync_Request_Settings::SETTINGS_FRIENDLYNAME) ? Horde_ActiveSync_Request_Settings::SETTINGS_FRIENDLYNAME : ($this->_decoder->getElementStartTag(Horde_ActiveSync_Request_Settings::SETTINGS_OS) ? Horde_ActiveSync_Request_Settings::SETTINGS_OS : ($this->_decoder->getElementStartTag(Horde_ActiveSync_Request_Settings::SETTINGS_OSLANGUAGE) ? Horde_ActiveSync_Request_Settings::SETTINGS_OSLANGUAGE : ($this->_decoder->getElementStartTag(Horde_ActiveSync_Request_Settings::SETTINGS_PHONENUMBER) ? Horde_ActiveSync_Request_Settings::SETTINGS_PHONENUMBER : ($this->_decoder->getElementStartTag(Horde_ActiveSync_Request_Settings::SETTINGS_USERAGENT) ? Horde_ActiveSync_Request_Settings::SETTINGS_USERAGENT : ($this->_decoder->getElementStartTag(Horde_ActiveSync_Request_Settings::SETTINGS_MOBILEOPERATOR) ? Horde_ActiveSync_Request_Settings::SETTINGS_MOBILEOPERATOR : ($this->_decoder->getElementStartTag(Horde_ActiveSync_Request_Settings::SETTINGS_ENABLEOUTBOUNDSMS) ? Horde_ActiveSync_Request_Settings::SETTINGS_ENABLEOUTBOUNDSMS : -1)))))))))) != -1) { if (($di[$field] = $this->_decoder->getElementContent()) !== false) { $this->_decoder->getElementEndTag(); // end $field } } $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.12.3/lib/Horde/ActiveSync/Request/ResolveRecipients.php0000664000076600000240000003365712273362323023417 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-2014 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync */ 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(); $this->_encoder->startTag(self::TAG_RECIPIENTCOUNT); $this->_encoder->content(count($results[$item])); $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.12.3/lib/Horde/ActiveSync/Request/Search.php0000664000076600000240000005250012273362323021143 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-2014 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync */ 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 (strtolower($search_name)) { case 'documentlibrary': $search_query['query'] = $this->_parseQuery(); break; 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(strtolower($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 (strtolower($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.12.3/lib/Horde/ActiveSync/Request/SendMail.php0000664000076600000240000001240212273362323021427 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-2014 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync */ 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 { $this->_logger->info(sprintf( '[%s] Handling SENDMAIL command with Wbxml.', $this->_procid)); return $this->_handleWbxmlRequest(); } } /** * Handle EAS 14+ SendMail/SmartReply/SmartForward requests. * * @return boolean */ protected function _handleWbxmlRequest() { // 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 $ex) { $this->_logger->err($ex->getMessage()); $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->_enocder->endTag(); } }Horde_ActiveSync-2.12.3/lib/Horde/ActiveSync/Request/Settings.php0000664000076600000240000005023512273362323021541 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-2014 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync */ class Horde_ActiveSync_Request_Settings extends Horde_ActiveSync_Request_Base { 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'; // 14.0 const SETTINGS_ENABLEOUTBOUNDSMS = 'Settings:EnableOutboundSMS'; const SETTINGS_MOBILEOPERATOR = 'Settings:MobileOperator'; const STATUS_SUCCESS = 1; const STATUS_ERROR = 2; const STATUS_UNAVAILABLE = 4; const OOF_STATE_ENABLED = 1; const OOF_STATE_DISABLED = 0; /** * Handle the request. * * @return boolean */ 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 : -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: if ($this->_decoder->getElementStartTag(self::SETTINGS_BODYTYPE)) { if (($bodytype = $this->_decoder->getElementContent()) !== false) { if (!$this->_decoder->getElementEndTag()) { throw new Horde_ActiveSync_Exception('Protocol Error'); // end self::SETTINGS BODYTYPE } } } if (!$this->_decoder->getElementEndTag()) { throw new Horde_ActiveSync_Exception('Protocol Error'); // end self::SETTINGS_OOF } $request['get']['oof']['bodytype'] = $bodytype; break; case self::SETTINGS_USERINFORMATION: $request['get']['userinformation'] = array(); break; } if (!$this->_decoder->getElementEndTag()) { throw new Horde_ActiveSync_Exception('Protocol Error'); // end self::SETTINGS GET } break; case self::SETTINGS_SET: switch ($reqtype) { case self::SETTINGS_OOF: while (($type = ($this->_decoder->getElementStartTag(self::SETTINGS_OOFSTATE) ? self::SETTINGS_OOFSTATE : ($this->_decoder->getElementStartTag(self::SETTINGS_STARTTIME) ? self::SETTINGS_STARTTIME : ($this->_decoder->getElementStartTag(self::SETTINGS_ENDTIME) ? self::SETTINGS_ENDTIME : ($this->_decoder->getElementStartTag(self::SETTINGS_OOFMESSAGE) ? self::SETTINGS_OOFMESSAGE : -1))))) != -1) { switch ($type) { case self::SETTINGS_OOFSTATE: if (($oofstate = $this->_decoder->getElementContent()) !== false) { $this->_decoder->getElementEndTag(); } $request['set']['oof']['oofstate'] = $oofstate; break; case self::SETTINGS_STARTTIME: if (($starttime = $this->_decoder->getElementContent()) !== false) { $this->_decoder->getElementEndTag(); } $request['set']['oof']['starttime'] = $starttime; break; case self::SETTINGS_ENDTIME: if (($endtime = $this->_decoder->getElementContent()) !== false) { $this->_decoder->getElementEndTag(); } $request['set']['oof']['endtime'] = $endtime; break; case self::SETTINGS_OOFMESSAGE: while (($type = ($this->_decoder->getElementStartTag(self::SETTINGS_APPLIESTOINTERNAL) ? self::SETTINGS_APPLIESTOINTERNAL : ($this->_decoder->getElementStartTag(self::SETTINGS_APPLIESTOEXTERNALKNOWN) ? self::SETTINGS_APPLIESTOEXTERNALKNOWN : ($this->_decoder->getElementStartTag(self::SETTINGS_APPLIESTOEXTERNALUNKNOWN) ? self::SETTINGS_APPLIESTOEXTERNALUNKNOWN : -1)))) != -1) { $oof = array(); $oof['appliesto'] = $type; while (($type = ($this->_decoder->getElementStartTag(self::SETTINGS_ENABLED) ? self::SETTINGS_ENABLED : ($this->_decoder->getElementStartTag(self::SETTINGS_REPLYMESSAGE) ? self::SETTINGS_REPLYMESSAGE : ($this->_decoder->getElementStartTag(self::SETTINGS_BODYTYPE) ? self::SETTINGS_BODYTYPE : -1)))) != -1) { switch ($type) { case self::SETTINGS_ENABLED: if (($oof['enabled'] = $this->_decoder->getElementContent()) !== false) { $this->_decoder->getElementEndTag(); // end self::SETTINGS_ENABLED } break; case self::SETTINGS_REPLYMESSAGE: if (($oof['replymessage'] = $this->_decoder->getElementContent()) !== false) { $this->_decoder->getElementEndTag(); // end self::SETTINGS_REPLYMESSAGE } break; case self::SETTINGS_BODYTYPE: if (($oof['bodytype'] = $this->_decoder->getElementContent()) != false) { $this->_decoder->getElementEndTag(); // end self::SETTINGS_BODYTYPE } break; } } } if (!isset($request['set']['oof']['oofmsgs'])) { $request['set']['oof']['oofmsgs'] = array(); } $request['set']['oof']['oofmsgs'][] = $oof; $this->_decoder->getElementEndTag(); // end self::SETTINGS_OOFMESSAGE break; } } $this->_decoder->getElementEndTag(); // end self::SETTINGS_OOF break; case self::SETTINGS_DEVICEINFORMATION : $device_properties = $this->_device->properties; while (($field = ($this->_decoder->getElementStartTag(self::SETTINGS_MODEL) ? self::SETTINGS_MODEL : ($this->_decoder->getElementStartTag(self::SETTINGS_IMEI) ? self::SETTINGS_IMEI : ($this->_decoder->getElementStartTag(self::SETTINGS_FRIENDLYNAME) ? self::SETTINGS_FRIENDLYNAME : ($this->_decoder->getElementStartTag(self::SETTINGS_OS) ? self::SETTINGS_OS : ($this->_decoder->getElementStartTag(self::SETTINGS_OSLANGUAGE) ? self::SETTINGS_OSLANGUAGE : ($this->_decoder->getElementStartTag(self::SETTINGS_PHONENUMBER) ? self::SETTINGS_PHONENUMBER : ($this->_decoder->getElementStartTag(self::SETTINGS_USERAGENT) ? self::SETTINGS_USERAGENT : ($this->_decoder->getElementStartTag(self::SETTINGS_MOBILEOPERATOR) ? self::SETTINGS_MOBILEOPERATOR : ($this->_decoder->getElementStartTag(self::SETTINGS_ENABLEOUTBOUNDSMS) ? self::SETTINGS_ENABLEOUTBOUNDSMS : -1)))))))))) != -1) { if (($device_properties[$field] = $this->_decoder->getElementContent()) !== false) { $this->_decoder->getElementEndTag(); // end $field } } 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); } $this->_decoder->getElementEndTag(); // end self::SETTINGS_DEVICEINFORMATION 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; $this->_decoder->getElementEndTag(); // end self::SETTINGS_DEVICEPASSWORD break; } // end self::SETTINGS_SET if (!$this->_decoder->getElementEndTag()) { throw new Horde_ActiveSync_Exception('Protocol Error'); } break; } } } $this->_decoder->getElementEndTag(); // end self::SETTINGS_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); if (!isset($result['set']['deviceinformation'])) { $this->_encoder->content(0); } else { $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); $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'])) { $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); $this->_encoder->startTag(self::SETTINGS_OOFSTATE); $this->_encoder->content($result['get']['oof']['oofstate']); $this->_encoder->endTag(); // end self::SETTINGS_OOFSTATE // This we maybe need later on (OOFSTATE=2). It shows that OOF // Messages could be send depending on Time being set in here. // Unfortunately cannot proof it working on my device. if ($result['get']['oof']['oofstate'] == 2) { $this->_encoder->startTag(self::SETTINGS_STARTTIME); $this->_encoder->content(gmdate('Y-m-d\TH:i:s.000', $result['get']['oof']['starttime'])); $this->_encoder->endTag(); // end self::SETTINGS_STARTTIME $this->_encoder->startTag(self::SETTINGS_ENDTIME); $this->_encoder->content(gmdate('Y-m-d\TH:i:s.000', $result['get']['oof']['endtime'])); $this->_encoder->endTag(); // end self::SETTINGS_ENDTIME } foreach($result['get']['oof']['oofmsgs'] as $oofentry) { $this->_encoder->startTag(self::SETTINGS_OOFMESSAGE); $this->_encoder->startTag($oofentry['appliesto'],false,true); $this->_encoder->startTag(self::SETTINGS_ENABLED); $this->_encoder->content($oofentry['enabled']); $this->_encoder->endTag(); // end self::SETTINGS_ENABLED $this->_encoder->startTag(self::SETTINGS_REPLYMESSAGE); $this->_encoder->content($oofentry['replymessage']); $this->_encoder->endTag(); // end self::SETTINGS_REPLYMESSAGE $this->_encoder->startTag(self::SETTINGS_BODYTYPE); switch (strtolower($oofentry['bodytype'])) { case 'text': $this->_encoder->content('Text'); break; case 'HTML': $this->_encoder->content('HTML'); } $this->_encoder->endTag(); // end self::SETTINGS_BODYTYPE $this->_encoder->endTag(); // end self::SETTINGS_OOFMESSAGE } $this->_encoder->endTag(); // end self::SETTINGS_GET $this->_encoder->endTag(); // end self::SETTINGS_OOF } } $this->_encoder->endTag(); // end self::SETTINGS_SETTINGS return true; } }Horde_ActiveSync-2.12.3/lib/Horde/ActiveSync/Request/SmartForward.php0000664000076600000240000000551312273362323022353 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-2014 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync */ 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.12.3/lib/Horde/ActiveSync/Request/SmartReply.php0000664000076600000240000000562312273362323022044 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-2014 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync */ 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.12.3/lib/Horde/ActiveSync/Request/Sync.php0000664000076600000240000016207012273362323020656 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-2014 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync */ 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; /** * Collection of all collection arrays for the current SYNC request. * * @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 { $this->_collections->loadCollectionsFromCache(); $csk = $this->_collections->confirmed_synckeys; if (count($csk) > 0) { $this->_logger->warn(sprintf( '[%s] Unconfirmed synckeys, but handling a short request. Request full SYNC.', $this->_procid)); $this->_statusCode = self::STATUS_REQUEST_INCOMPLETE; $this->_handleGlobalSyncError(); return true; } $this->_collections->shortSyncRequest = true; $this->_collections->save(); } } else { $this->_statusCode = self::STATUS_REQUEST_INCOMPLETE; $this->_handleGlobalSyncError(); $this->_logger->err('Empty Sync request and protocolversion < 12.1'); return true; } } else { if ($this->_device->version >= Horde_ActiveSync::VERSION_TWELVEONE) { $this->_collections->setHeartbeat(array('wait' => false, 'hbinterval' => false)); } // Start decoding request. 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->_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->_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; } } } // Preparation for EAS >= 12.1 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 information from cache. $this->_collections->validateFromCache(); } // 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(); $this->_logger->info(sprintf( '[%s] All synckeys confirmed. Continuing with SYNC', $this->_procid)); } // 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 $pingSettings = $this->_driver->getHeartbeatConfig(); 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(); return true; } // Start output to PIM $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; foreach ($this->_collections as $id => $collection) { $statusCode = self::STATUS_SUCCESS; $changecount = 0; $cnt_collection = 0; 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 PIM 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(); // Check the mimesupport because we need it for advanced emails if (!isset($collection['mimesupport'])) { $collection['mimesupport'] = 0; } $ensure_sent = array(); if ($statusCode == self::STATUS_SUCCESS) { if (!empty($collection['clientids']) || !empty($collection['fetchids']) || !empty($collection['missing']) || !empty($collection['importfailures'])) { $this->_encoder->startTag(Horde_ActiveSync::SYNC_REPLIES); // 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(); } } // Output server IDs for new items we received and added from PIM 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 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_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(); // If we have a conflict, ensure we send the new server // data in the response, if possible. Some android // clients require this, or never accept the response. if ($reason == self::STATUS_CONFLICT) { $ensure_sent[] = $id; } } } 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(); } // Send server changes to PIM if ($statusCode == self::STATUS_SUCCESS && empty($forceChanges) && (!empty($collection['getchanges']) || (!isset($collection['getchanges']) && !empty($collection['synckey'])))) { if ((!empty($changecount) && $changecount > $collection['windowsize']) || ($cnt_global + $changecount > $this->_collections->getDefaultWindowSize())) { $this->_logger->info(sprintf( '[%s] Sending MOREAVAILABLE. $changecount = %d, $cnt_global = %d', $this->_procid, $changecount, $cnt_global)); $this->_encoder->startTag(Horde_ActiveSync::SYNC_MOREAVAILABLE, false, true); } if (!empty($changecount)) { $exporter->setChanges($this->_collections->getCollectionChanges(false, $ensure_sent), $collection); $this->_encoder->startTag(Horde_ActiveSync::SYNC_COMMANDS); $cnt_collection = 0; while ($cnt_collection < $collection['windowsize'] && $cnt_global < $this->_collections->getDefaultWindowSize() && $progress = $exporter->sendNextChange()) { if ($progress === true) { ++$cnt_collection; ++$cnt_global; } } $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->_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(); } } else { $this->_collections->save(); } return true; } /** * 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 (!$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 ($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; } try { $this->_collections->addCollection($collection); } 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; } if (isset($collection['filtertype']) && !$this->_collections->checkFilterType($collection['id'], $collection['filtertype'])) { $this->_statusCode = self::STATUS_KEYMISM; $this->_handleError($collection); } 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)); } if (empty($collection['class'])) { $collection['class'] = $this->_collections->getCollectionClass($collection['id']); } if (empty($collection['serverid'])) { $collection['serverid'] = $this->_collections->getBackendIdForFolderUid($collection['id']); } 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->_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++; // Only sent during SYNC_MODIFY/SYNC_REMOVE/SYNC_FETCH if (($element[Horde_ActiveSync_Wbxml::EN_TAG] == Horde_ActiveSync::SYNC_MODIFY || $element[Horde_ActiveSync_Wbxml::EN_TAG] == Horde_ActiveSync::SYNC_REMOVE || $element[Horde_ActiveSync_Wbxml::EN_TAG] == 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; } } 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 && $element[Horde_ActiveSync_Wbxml::EN_TAG] == 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 ($element[Horde_ActiveSync_Wbxml::EN_TAG] == 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 PIM. // Only passed during SYNC_ADD or SYNC_MODIFY if (($element[Horde_ActiveSync_Wbxml::EN_TAG] == Horde_ActiveSync::SYNC_ADD || $element[Horde_ActiveSync_Wbxml::EN_TAG] == 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); 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; } } if (!empty($collection['synckey'])) { switch ($element[Horde_ActiveSync_Wbxml::EN_TAG]) { case Horde_ActiveSync::SYNC_MODIFY: if (isset($appdata)) { $id = $importer->importMessageChange( $serverid, $appdata, $this->_device, false); 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: // Work around broken clients that send empty $serverid. if ($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; } $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(); while(1) { if ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_FILTERTYPE)) { $options['filtertype'] = $this->_decoder->getElementContent(); 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->_device->version > Horde_ActiveSync::VERSION_TWELVEONE && $this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_FOLDERTYPE)) { $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)) { $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)) { $this->_mimeSupport($options); } if ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_MIMETRUNCATION)) { $options['mimetruncation'] = $this->_decoder->getElementContent(); if (!$this->_decoder->getElementEndTag()) { $this->_statusCode = self::STATUS_PROTERROR; $this->_handleError($collection); exit; } } if ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_TRUNCATION)) { $options['truncation'] = $this->_decoder->getElementContent(); if (!$this->_decoder->getElementEndTag()) { $this->_statusCode = self::STATUS_PROTERROR; $this->_handleError($collection); exit; } } if ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_RTFTRUNCATION)) { $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)) { $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)) { $this->_rightsManagement($options); } if ($this->_decoder->getElementStartTag(Horde_ActiveSync::AIRSYNCBASE_BODYPARTPREFERENCE)) { $this->_bodyPartPrefs($options); } } $e = $this->_decoder->peek(); if ($e[Horde_ActiveSync_Wbxml::EN_TYPE] == Horde_ActiveSync_Wbxml::EN_TYPE_ENDTAG) { $this->_decoder->getElementEndTag(); 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.12.3/lib/Horde/ActiveSync/Request/SyncBase.php0000664000076600000240000001515612273362323021453 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-2014 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync */ abstract class Horde_ActiveSync_Request_SyncBase extends Horde_ActiveSync_Request_Base { /** * Parse incoming BODYPARTPREFERENCE options. * * @param array $collection An array structure to parse the data into. */ protected function _bodyPartPrefs(&$collection) { $collection['bodypartprefs'] = array(); if ($this->_decoder->getElementStartTag(Horde_ActiveSync::AIRSYNCBASE_TYPE)) { $collection['bodypartprefs']['type'] = $this->_decoder->getElementContent(); // MS-ASAIRS 2.2.2.22.3 type MUST be BODYPREF_TYPE_HTML if (!$this->_decoder->getElementEndTag() || $collection['bodypartprefs']['type'] != Horde_ActiveSync::BODYPREF_TYPE_HTML) { $this->_statusCode = self::STATUS_PROTERROR; $this->_handleError($collection); exit; } } if ($this->_decoder->getElementStartTag(Horde_ActiveSync::AIRSYNCBASE_TRUNCATIONSIZE)) { $collection['bodypartprefs']['truncationsize'] = $this->_decoder->getElementContent(); if (!$this->_decoder->getElementEndTag()) { $this->_statusCode = self::STATUS_PROTERROR; $this->_handleError($collection); exit; } } if ($this->_decoder->getElementStartTag(Horde_ActiveSync::AIRSYNCBASE_ALLORNONE)) { $collection['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($collection['bodypartprefs']['truncationsize'])) { unset($collection['bodypartprefs']['allornone']); } if (!$this->_decoder->getElementEndTag()) { $this->_statusCode = self::STATUS_PROTERROR; $this->_handleError($collection); exit; } } if ($this->_decoder->getElementStartTag(Horde_ActiveSync::AIRSYNCBASE_PREVIEW)) { $collection['bodypartprefs']['preview'] = $this->_decoder->getElementContent(); // MS-ASAIRS 2.2.2.18.3 - Max size of preview is 255. if (!$this->_decoder->getElementEndTag() || $collection['bodypartprefs']['preview'] > 255) { $this->_statusCode = self::STATUS_PROTERROR; $this->_handleError($collection); exit; } } if (!$this->_decoder->getElementEndTag()) { $this->_statusCode = self::STATUS_PROTERROR; $this->_handleError($collection); exit; } } /** * Parse incoming BODYPREFERENCE options. * * @param array An array structure to parse the values into. */ protected function _bodyPrefs(&$collection) { $body_pref = array(); if (empty($collection['bodyprefs'])) { $collection['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($collection); 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($collection); 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($collection); 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($collection); exit; } } $e = $this->_decoder->peek(); if ($e[Horde_ActiveSync_Wbxml::EN_TYPE] == Horde_ActiveSync_Wbxml::EN_TYPE_ENDTAG) { $this->_decoder->getElementEndTag(); $collection['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.12.3/lib/Horde/ActiveSync/Request/ValidateCert.php0000664000076600000240000001614412273362323022311 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-2014 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync */ 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) { if ($checkcrl = $this->_decoder->getElementContent()) { $this->_logger->info('CRL: ' . $checkcrl); } 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.12.3/lib/Horde/ActiveSync/State/Base.php0000664000076600000240000010544112273362323020243 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-2014 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 PIM * - id: Server folder id * - filtertype: Filter * - conflict: Conflicts * - truncation: Truncation * * @var array */ protected $_collection; /** * Logger instance * * @var Horde_Log_Logger */ protected $_logger; /** * Device info structure. Contains the following properties: * 'rwstatus' - Device RemoteWipe status. * 'deviceType' - The device type. * 'userAgent' - The device's userAgent string. * 'id' - Device id. * 'user' - The user associated with the current account. * 'supported' - The SUPPORTED response for the device's collections. * 'policykey' - The device's current POLICYKEY. * 'version' - The currently requested EAS version. * * @var Horde_ActiveSync_Device */ protected $_deviceInfo; /** * Local cache for changes to *send* to PIM * (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_Support_Stub(); } else { $this->_logger = $params['logger']; } $this->_procid = getmypid(); } /** * Destructor */ public function __destruct() { unset($this->_backend); } /** * 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 * * @return integer */ public function getDeviceRWStatus($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->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 PIM 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 * PIM. * * @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 ); // 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 PIM initiated changes.', $this->_procid)); switch ($this->_collection['class']) { case Horde_ActiveSync::CLASS_EMAIL: $mailmap = $this->_getMailMapChanges($changes); foreach ($changes as $change) { switch ($change['type']) { case Horde_ActiveSync::CHANGE_TYPE_FLAGS: if (!empty($mailmap[$change['id']][$change['type']])) { $this->_logger->info(sprintf( '[%s] Ignoring PIM initiated flag change for %s', $this->_procid, $change['id'])); $change['ignore'] = true; } $this->_changes[] = $change; break; case Horde_ActiveSync::CHANGE_TYPE_DELETE: if (!empty($mailmap[$change['id']][$change['type']])) { $this->_logger->info(sprintf( '[%s] Ignoring PIM initiated deletion for %s', $this->_procid, $change['id'])); $change['ignore'] = true; } $this->_changes[] = $change; break; default: // New message. $this->_changes[] = $change; } } break; default: $pim_timestamps = $this->_getPIMChangeTS($changes); foreach ($changes as $change) { if (empty($pim_timestamps[$change['id']])) { $this->_changes[] = $change; continue; } if ($change['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(), $change['id']); } if ($pim_timestamps[$change['id']] >= $stat['mod']) { $this->_logger->info(sprintf( '[%s] Ignoring PIM initiated change for %s (PIM TS: %s Stat TS: %s)', $this->_procid, $change['id'], $pim_timestamps[$change['id']], $stat['mod'])); } else { $this->_changes[] = $change; } } } } elseif (count($changes)) { $this->_logger->info(sprintf( '[%s] No PIM 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 */ static public 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. */ static public 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. */ static public 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 */ static protected function _getCutOffDate($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 PIM state and * server state FOLDERSYNC arrays. * * @param array $old The PIM 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 */ static public 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 PIM if there are no * PIM 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 PIM * * @param string $user The current sync user, only needed if change * origin is CHANGE_ORIGIN_PIM * @param string $clientid PIM 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 * * @return Horde_ActiveSync_Device */ abstract public function loadDeviceInfo($device, $user = null); /** * 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 PIM. * This would happen e.g., if the PIM 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.12.3/lib/Horde/ActiveSync/State/Mongo.php0000664000076600000240000015263312273362323020455 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-2014 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 { /** * Mongo connection * * @var MongoClient */ protected $_mongo; /** * Mongo database * * @var MongoDB */ protected $_db; /** * Mongo Indexes * * @var array */ protected $_indexes = array( 'HAS_device' => array( 'index_id_user' => array( '_id' => 1, 'users.device_user' => 1 ) ), 'HAS_state' => array( 'index_devid_folderid' => array( 'sync_devid' => 1, 'sync_folderid' => 1 ) ), 'HAS_map' => array( 'index_folder_dev_uid_user' => array( 'sync_devid' => 1, 'sync_user' => 1, 'sync_folderid' => 1, 'message_uid' => 1 ), 'index_dev_user_uid_key' => array( 'sync_devid' => 1, 'sync_user' => 1, 'message_uid' => 1, 'sync_key' => 1, 'sync_deleted' => 1, ), 'index_client_user_dev' => array( 'sync_clientid' => 1, 'sync_user' => 1, 'sync_devid' => 1 ) ), 'HAS_mailmap' => array( 'index_folder_dev_uid_user' => array( 'sync_devid' => 1, 'sync_user' => 1, 'sync_folderid' => 1, 'message_uid' => 1 ) ), 'HAS_cache' => array( 'index_dev_user' => array( 'cache_devid' => 1, 'cache_user' => 1 ) ) ); protected $_propertyMap = array( 'deviceType' => 'device_type', 'userAgent' => 'device_agent', 'rwstatus' => 'device_rwstatus', 'supported' => 'device_supported', 'properties' => 'device_properties', 'id' => '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. * @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( 'sync_devid' => $this->_deviceInfo->id, 'sync_user' => $this->_deviceInfo->user, 'sync_folderid' => $uid ); try { $cursor = $this->_db->HAS_state->find($query, array('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['sync_data']); $folder->setServerId($serverid); $folder = serialize($folder); try { $this->_db->HAS_state->update( $query, array('$set' => array('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) { // Load the previous syncState from storage try { $results = $this->_db->HAS_state->findOne( array('_id' => $this->_syncKey), array('sync_data', 'sync_devid', 'sync_mod', '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->err(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 $data = unserialize($results['sync_data']); $pending = $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) { $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( '_id' => $this->_syncKey, 'sync_key' => $this->_syncKey, 'sync_data' => $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 for sync_key %s: %s', $this->_procid, $this->_syncKey, serialize($document))); try { $this->_db->HAS_state->insert($document); } catch (Exception $e) { // Might exist already if the last sync attempt failed. $this->_logger->notice( sprintf('[%s] Error saving state for synckey %s: %s - removing previous sync state and trying again.', $this->_procid, $this->_syncKey, $e->getMessage())); try { $this->_db->HAS_state->remove(array('_id' => $this->_syncKey)); $this->_db->HAS_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 PIM 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 PIM * * @param string $user The current sync user, only needed if change * origin is CHANGE_ORIGIN_PIM * @param string $clientid PIM clientid sent when adding a new message */ 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 PIM, 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( 'message_uid' => $change['id'], 'sync_key' => $syncKey, 'sync_devid' => $this->_deviceInfo->id, 'sync_folderid' => $change['serverid'], 'sync_user' => $user ); if ($type == Horde_ActiveSync::CHANGE_TYPE_FLAGS) { if (isset($change['flags']['read'])) { $document['sync_read'] = !empty($change['flags']['read']); } else { $document['sync_flagged'] = $flag_value = !empty($change['flags']['flagged']); } } else { $document['sync_deleted'] = true; } try { $this->_db->HAS_mailmap->insert($document); } catch (Exception $e) { throw Horde_ActiveSync_Exception($e); } break; default: $document = array( 'message_uid' => $change['id'], 'sync_modtime' => $change['mod'], 'sync_key' => $syncKey, 'sync_devid' => $this->_deviceInfo->id, 'sync_folderid' => $change['serverid'], 'sync_user' => $user, 'sync_clientid' => $clientid, 'sync_deleted' => $type == Horde_ActiveSync::CHANGE_TYPE_DELETE ); try { $this->_db->HAS_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 * * @return Horde_ActiveSync_Device The device object * @throws Horde_ActiveSync_Exception */ public function loadDeviceInfo($devId, $user = null) { // See if we already have this device, for this user loaded if (!empty($this->_deviceInfo) && $this->_deviceInfo->id == $devId && !empty($this->_deviceInfo) && $user == $this->_deviceInfo->user) { return $this->_deviceInfo; } $query = array('_id' => $devId); if (!empty($user)) { $query['users.device_user'] = $user; } try { $device_data = $this->_db->HAS_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['device_user'] == $user) { $device['policykey'] = $user_entry['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->HAS_device->update( array('_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( 'device_user' => $data->user, 'device_policykey' => (string)$data->policykey ); try { $this->_db->HAS_device->update( array('_id' => $data->id), array('$pull' => array('users' => array('device_user' => $data->user))) ); $this->_db->HAS_device->update( array('_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('_id' => $deviceId); $update = array( '$set' => array( 'device_properties' => $data ) ); try { $this->_db->HAS_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('_id' => $devId); if (!empty($user)) { $query['users.device_user'] = $user; } try { return $this->_db->HAS_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['users.device_user'] = $user; } $explicit_fields = array('device_id', 'device_type', 'device_agent', '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->HAS_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->HAS_device->update( // array(), // array('$set' => array('users.device_policykey' => 0)), // array('multiple' => true) // ); // } catch (Exception $e) { // $this->_logger->err($e->getMessage()); // throw new Horde_ActiveSync_Exception($e); // } $cursor = $this->_db->HAS_device->find(array(), array('users')); foreach ($cursor as $row) { foreach ($row['users'] as $user) { $this->_db->HAS_device->update( array('users.device_user' => $user['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('_id' => $devId); $new_data = array('device_rwstatus' => $status); if ($status == Horde_ActiveSync::RWSTATUS_PENDING) { $new_data['users.device_policykey'] = 0; } $update = array('$set' => $new_data); try { $this->_db->HAS_device->update($query, $update); } 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( 'sync_devid' => $this->_deviceInfo->id, 'sync_folderid' => $id, 'sync_user' => $this->_deviceInfo->user ); try { $this->_db->HAS_state->remove($query); $this->_db->HAS_map->remove($query); $this->_db->HAS_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('sync_devid' => $id); if (!empty($user)) { $match['sync_user'] = $user; } try { $results = $this->_db->HAS_state->aggregate( array('$match' => $match), array('$group' => array('_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']); } if (empty($results) || empty($results['ok'])) { return 0; } $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( '_id' => $options['devId'], '$or' => array(array('device_rwstatus' => Horde_ActiveSync::RWSTATUS_PENDING), array('device_rwstatus' => Horde_ActiveSync::RWSTATUS_WIPED)) ); try { $results = $this->_db->HAS_device->findOne($query, array('_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( 'sync_devid' => $options['devId'], 'sync_user' => $options['user'] ); if (!empty($options['id'])) { $query['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->HAS_device->update( array('_id' => $options['devId'], 'users.device_user' => $options['user']), array('$pull' => array('users' => array('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('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->HAS_device->remove(array('_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('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->HAS_device->update( array('users.device_user'), array('$pull' => array('users' => array('device_user' => $options['user']))) ); } catch (Exception $e) { $this->_logger->err($e->getMessage()); throw new Horde_ActiveSync_Exception($e); } } elseif (!empty($options['synckey'])) { $query = array('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->HAS_state->remove($query); $this->_db->HAS_map->remove($query); $this->_db->HAS_mailmap->remove($query); $this->_db->HAS_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 PIM. * This would happen e.g., if the PIM 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( 'sync_clientid' => $id, 'sync_user' => $this->_deviceInfo->user, 'sync_devid' => $this->_deviceInfo->id ); try { $result = $this->_db->HAS_map->findOne( $query, array('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['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( 'cache_devid' => $devid, 'cache_user' => $user ); $projection = array(); if (!is_null($fields)) { foreach ($fields as $field) { $projection[] = 'cache_data.' . $field; } } else { $projection = array('cache_data'); } try { $data = $this->_db->HAS_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['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['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()) { $cache['timestamp'] = strval($cache['timestamp']); $this->_logger->info( sprintf('[%s] Saving SYNC_CACHE entry fields %s for user %s and device %s.', $this->_procid, serialize($dirty), $user, $devid)); $query = array( 'cache_devid' => $devid, 'cache_user' => $user ); if (empty($cache['collections'])) { $cache['collections'] = new stdClass(); } $update = array(); foreach ($dirty as $property => $value) { if ($property == 'collections' && is_array($value)) { foreach (array_keys($value) as $collection) { $update['cache_data.collections.' . $collection] = $cache['collections'][$collection]; } } else { $update['cache_data.' . $property] = $cache[$property]; } } try { $this->_db->HAS_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['cache_devid'] = $devid; } if (!empty($user)) { $params['cache_user'] = $user; } try { $this->_db->HAS_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 * PIM-initiated change for the provided uid. Used to avoid mirroring back * changes to the PIM 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 PIM-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( 'sync_devid' => $this->_deviceInfo->id, 'sync_user' => $this->_deviceInfo->user, 'sync_key' => array('$in' => $keys) ); $uids = array(); $match['$or'] = array(); foreach ($changes as $change) { $match['$or'][] = array( '$and' => array( array('message_uid' => $change['id']), array('sync_deleted' => $change['type'] == Horde_ActiveSync::CHANGE_TYPE_DELETE) ) ); } try { $rows = $this->_db->HAS_map->aggregate( array('$match' => $match), array('$group' => array('_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['_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 PIM if there are no * PIM generated changes for this sync period. * * @return boolean * @throws Horde_ActiveSync_Exception */ protected function _havePIMChanges() { $this->_logger->info(sprintf( '[%s] Horde_ActiveSync_State_Mongo::_havePIMChanges() for %s', $this->_procid, $this->_collection['serverid'])); $c = $this->_collection['class'] == Horde_ActiveSync::CLASS_EMAIL ? $this->_db->HAS_mailmap : $this->_db->HAS_map; $query = array( 'sync_devid' => $this->_deviceInfo->id, 'sync_user' => $this->_deviceInfo->user, 'sync_folderid' => $this->_collection['serverid'] ); try { return (bool)$c->find($query, array('_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( 'sync_folderid' => $this->_collection['serverid'], 'sync_devid' => $this->_deviceInfo->id, 'sync_user' => $this->_deviceInfo->user, 'message_uid' => array('$in' => $ids) ); $rows = $this->_db->HAS_mailmap->find( $query, array('message_uid', 'sync_read', 'sync_flagged', 'sync_deleted') ); $results = array(); foreach ($rows as $row) { foreach ($changes as $change) { if ($change['id'] == $row['message_uid']) { if ($change['type'] == 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 2; } elseif ($change['type'] == Horde_ActiveSync::CHANGE_TYPE_DELETE) { $results[$row['message_uid']][$change['type']] = !is_null($row['sync_deleted']) && $row['sync_deleted'] == true; continue 2; } } } } 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 PIM never received the new key in a SYNC response. $js = << $this->_deviceInfo->id, 'sync_folderid' => !empty($this->_collection['id']) ? $this->_collection['id'] : Horde_ActiveSync::CHANGE_TYPE_FOLDERSYNC, '$where' => $js ); try { $this->_db->HAS_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->HAS_map, $this->_db->HAS_mailmap) as $c) { $query = array( 'sync_devid' => $this->_deviceInfo->id, '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.12.3/lib/Horde/ActiveSync/State/Sql.php0000664000076600000240000015645512273362323020143 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 PIM 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 PIM 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-2014 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_data FROM ' . $this->_syncStateTable . ' WHERE ' . 'sync_devid = ? AND sync_user = ? AND sync_folderid = ?'; try { $results = $this->_db->selectValues($sql, array($this->_deviceInfo->id, $this->_devInfo->user, $uid)); } 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 = ?'; foreach ($results as $folder) { $folder = unserialize($folder); $folder->setServerId($serverid); $folder = serialize($folder); try { $this->_db->update($update, array($folder, $this->_deviceInfo->id, $this->_devInfo->user, $uid)); } 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->err(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() { // Update state table to remember this last synctime and key $sql = 'INSERT INTO ' . $this->_syncStateTable . ' (sync_key, sync_data, sync_devid, sync_mod, sync_folderid, sync_user, sync_pending, sync_timestamp)' . ' VALUES (?, ?, ?, ?, ?, ?, ?, ?)'; // 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( $this->_syncKey, new Horde_Db_Value_Binary($data), $this->_deviceInfo->id, (self::getSyncKeyCounter($this->_syncKey) == 1 ? 0 : $this->_thisSyncStamp), (!empty($this->_collection['id']) ? $this->_collection['id'] : Horde_ActiveSync::REQUEST_TYPE_FOLDERSYNC), $this->_deviceInfo->user, $pending, time()); $this->_logger->info( sprintf('[%s] Saving state: %s', $this->_procid, serialize(array( $params[0], $params[1], $params[2], $params[3], $params[4], $params[5], count($this->_changes), time())) ) ); try { $this->_db->insert($sql, $params); } catch (Horde_Db_Exception $e) { // Might exist already if the last sync attempt failed. $this->_logger->notice( sprintf('[%s] Error saving state for synckey %s: %s - removing previous sync state and trying again.', $this->_procid, $this->_syncKey, $e->getMessage())); $this->_db->delete('DELETE FROM ' . $this->_syncStateTable . ' WHERE sync_key = ?', array($this->_syncKey)); $this->_db->insert($sql, $params); } } /** * Update the state to reflect changes * * Notes: If we are importing PIM 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 PIM * * @param string $user The current sync user, only needed if change * origin is CHANGE_ORIGIN_PIM * @param string $clientid PIM clientid sent when adding a new message */ 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 PIM, 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; } if ($type == 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']); } } else { $sql = 'INSERT INTO ' . $this->_syncMailMapTable . ' (message_uid, sync_key, sync_devid,' . ' sync_folderid, sync_user, sync_deleted)' . ' VALUES (?, ?, ?, ?, ?, ?)'; } $params = array( $change['id'], $syncKey, $this->_deviceInfo->id, $change['serverid'], $user, ($type == Horde_ActiveSync::CHANGE_TYPE_FLAGS) ? $flag_value : true ); 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. 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 * * @return Horde_ActiveSync_Device The device object * @throws Horde_ActiveSync_Exception */ public function loadDeviceInfo($devId, $user = null) { // See if we already have this device, for this user loaded if (!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.'); } } 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 and version.', $this->_procid, $data->id))); $query = 'UPDATE ' . $this->_syncDeviceTable . ' SET device_agent = ?, device_properties = ?' . ' WHERE device_id = ?'; $values = array( (!empty($data->userAgent) ? $data->userAgent : ''), serialize($data->properties), $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->_syncUsersTable . ' WHERE 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) { unset($options['user']); return $this->removeState($options); } } 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) && empty($options['devId'])) { // If there was a user_deletion, check if we should remove the // device entry as well $sql = 'SELECT COUNT(*) FROM ' . $this->_syncUsersTable . ' WHERE device_id = ?'; if (!$this->_db->selectValue($sql, array($options['devId']))) { $query = 'DELETE FROM ' . $this->_syncDeviceTable . ' WHERE device_id = ?'; $this->_db->delete($query, array($options['devId'])); } } } 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 PIM. * This would happen e.g., if the PIM 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); } } /** * 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)); } 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 * PIM-initiated change for the provided uid. Used to avoid mirroring back * changes to the PIM 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 PIM-initiated * change for the specified uid, 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 PIM if there are no * PIM generated changes for this sync period. * * @return boolean * @throws Horde_ActiveSync_Exception */ protected function _havePIMChanges() { $class = $this->_collection['class']; $table = $class == Horde_ActiveSync::CLASS_EMAIL ? $this->_syncMailMapTable : $this->_syncMapTable; $sql = 'SELECT COUNT(*) FROM ' . $table . ' 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 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']) { if ($change['type'] == 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 2; } elseif ($change['type'] == Horde_ActiveSync::CHANGE_TYPE_DELETE) { $results[$row['message_uid']][$change['type']] = !is_null($row['sync_deleted']) && $row['sync_deleted'] == true; continue 2; } } } } 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 PIM 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 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 - 1)) { $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.12.3/lib/Horde/ActiveSync/Wbxml/Decoder.php0000664000076600000240000005347612273362323020761 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-2014 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(); $this->_buffer->add($this->_stream); $this->_buffer->rewind(); return $this->_buffer->stream; } /** * Returns either start, content or end, and auto-concatenates successive * content. * * @return mixed The element 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.12.3/lib/Horde/ActiveSync/Wbxml/Encoder.php0000664000076600000240000003322312273362323020757 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-2014 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 * * @return Horde_ActiveSync_Wbxml_Encoder */ function __construct($output) { parent::__construct($output); /* 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); } } /** * 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.12.3/lib/Horde/ActiveSync/Collections.php0000664000076600000240000011716112273362323020571 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-2014 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync */ 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; /** * Count of confirmed collections calculated for PARTIAL sync. * * @var integer */ protected $_confirmedCount = 0; /** * Global WINDOWSIZE * * @var integer */ protected $_globalWindowSize = 100; /** * 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; /** * 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': $p = '_' . $property; return $this->$p; } throw new InvalidArgumentException('Unknown property: ' . $property); } /** * Property setter. */ public function __set($property, $value) { switch ($property) { case 'importedChanges': case 'shortSyncRequest': $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( 'truncation' => Horde_ActiveSync::TRUNCATION_ALL, 'clientids' => array(), 'fetchids' => array(), 'windowsize' => 100, 'conflict' => Horde_ActiveSync::CONFLICT_OVERWRITE_PIM, 'bodyprefs' => array(), 'mimesupport' => Horde_ActiveSync::MIME_SUPPORT_NONE, 'mimetruncation' => Horde_ActiveSync::TRUNCATION_8, ); } /** * Add a new populated collection array to this collection. * * @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)); } $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']); } $collection['serverid'] = $this->getBackendIdForFolderUid($collection['id']); $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 */ 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 { throw new Horde_ActiveSync_Exception('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 */ public function setDefaultWindowSize($window) { $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; } 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; } /** * 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->_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); 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(); } /** * 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() { if (empty($this->_collections)) { $this->_logger->err('No collections in collection handler, no PARTIAL allowed.'); return false; } $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->_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->_confirmedCount++; } $this->_synckeyCount++; } } } $csk = $this->_cache->confirmed_synckeys; if ($csk) { $this->_logger->info(sprintf( '[%s] Confirmed Synckeys contains %s', $this->_procid, serialize($csk)) ); $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; } /** * Return if we can do an empty response * * @return boolean */ public function canSendEmptyResponse() { return !$this->_importedChanges && ($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->_cache->removeCollection($id); $this->_cache->save(); $this->_logger->info('Invalidating SYNCKEY - found updated filtertype'); 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. */ public function save() { $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 */ public function initCollectionState(array $collection, $requireSyncKey = false) { // Clear the changes cache. $this->_changes = null; if (empty($collection['class'])) { if (!empty($this->_collections[$collection['id']])) { $collection['class'] = $this->_collections[$collection['id']]['class']; } else { throw new Horde_ActiveSync_Exception_FolderGone('Could not load collection class for ' . $collection['id']); } } // Get the backend serverid. if (empty($collection['serverid'])) { $f = $this->_cache->getFolder($collection['id']); $collection['serverid'] = $f['serverid']; } 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(); 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. If so, force a foldersync so it is // performed. if ($this->_as->provisioning != Horde_ActiveSync::PROVISIONING_NONE) { $rwstatus = $this->_as->state->getDeviceRWStatus($this->_as->device->id); 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()) ); 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); } } } /** * Return the any changes for the current collection, and cache them if * we are not PINGing. * * @param boolean $ping True if this is a PING request, false otherwise. * @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. * * @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)); } if (!empty($ensure)) { $this->_changes = $this->_reorderChanges($ensure); } return $this->_changes; } protected function _reorderChanges(array $ensure) { $changes = array(); foreach ($this->_changes as $change) { if (array_search($change['id'], $ensure) !== false) { $this->_logger->info(sprintf( 'Placing %s at beginning of changes array.', $change['id'])); array_unshift($changes, $change); } else { $changes[] = $change; } } return $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.12.3/lib/Horde/ActiveSync/Device.php0000664000076600000240000004754512273362323017522 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-2014 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 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'; /** * 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; /** * 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': $type = $this->_getClientType(); return $type; case self::VERSION: if (isset($this->_properties['properties'][self::VERSION])) { return $this->_properties['properties'][self::VERSION]; } break; case 'properties': if (!isset($this->_properties['properties'])) { $return = array(); return $return; } // Fall through. default: if (isset($this->_properties[$property])) { return $this->_properties[$property]; } else { $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: $properties = $this->properties; if (empty($properties)) { $properties = array(); } $properties[$property] = $value; $this->setDeviceProperties($properties); 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 sent 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::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. */ public function save() { $this->_state->setDeviceInfo($this, $this->_dirty); if (!empty($this->_dirty['properties'])) { $this->_state->setDeviceProperties($this->properties, $this->id); } $this->_dirty = array(); } public function getMajorVersion() { switch (strtolower($this->clientType)) { case self::TYPE_BLACKBERRY: if (preg_match('/(.+)\/(.+)/', $this->userAgent, $matches)) { return $matches[2]; } break; case self::TYPE_IPOD: case self::TYPE_IPAD: if (preg_match('/(\d+)\.(\d+)/', $this->properties[self::OS], $matches)) { return $matches[1]; } break; case self::TYPE_IPHONE: if (preg_match('/(.+)\/(\d+)\.(\d+)/', $this->userAgent, $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[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; } /** * 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. * * @param Horde_Date $date The date. * @param boolean $toEas Convert from local to device if true. * * @return Horde_Date The date of the birthday/anniversary, in UTC, with * any fixes applied for the current device. */ public function normalizePoomContactsDates($date, $toEas = false) { // WP devices seem to send the birthdays at the entered date, with // a time of 00:00:00 UTC. // // 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. // // Native Android 4 ALWAYS sends it as 08:00:00 UTC // // BB 10+ expects it at 12:00:00 UTC switch (strtolower($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 { return new Horde_Date($date->format('Y-m-d')); } case self::TYPE_ANDROID: if ($this->getMajorVersion() >= 4) { if ($toEas) { return new Horde_Date($date->format('Y-m-d 08:00:00'), 'UTC'); } else { return new Horde_Date($date->format('Y-m-d')); } } 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. 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_BLACKBERRY: if ($toEas) { return new Horde_Date($date->format('Y-m-d 12:00:00'), 'UTC'); } else { return new Horde_Date($date->format('Y-m-d')); } case self::TYPE_TOUCHDOWN: case self::TYPE_UNKNOWN: default: return $date; } } /** * Attempt to determine the *client* application as opposed to the device, * which may or may not be the client. * * @return string The client name, or self::TYPE_UNKNOWN if unable to * determine. */ protected function _getClientType() { // Differentiate between the deviceType and the client app. if ((!empty($this->properties[self::OS]) && strpos($this->properties[self::OS], 'Android') !== false) || strtolower($this->deviceType) == self::TYPE_ANDROID) { // We can detect native android and TouchDown so far. // Moxier does not distinguish itself, so we can't sniff it. if (strpos($this->userAgent, 'TouchDown') !== false) { return self::TYPE_TOUCHDOWN; } else if (strpos($this->userAgent, 'Android') !== false) { return $this->deviceType; } else { return self::TYPE_UNKNOWN; } } else { return $this->deviceType; } } /** * Basic sniffing for determining if devices can support non-multiplexed * collections. */ protected function _sniffMultiplex() { $clientType = strtolower($this->clientType); if (strpos($this->userAgent, 'iOS') === 0 || in_array($clientType, array(self::TYPE_IPAD, self::TYPE_IPOD, self::TYPE_IPHONE))) { // 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) { // All android before 4.4 KitKat requires multiplex. KitKat 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') < 1) { $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.0') !== false || $this->deviceType == 'WP8') { // Windows Phone. For the devices I've tested, it seems that // only multiple tasklists are accepted. 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 { $this->_properties['properties']['multiplex'] = 0; } } } Horde_ActiveSync-2.12.3/lib/Horde/ActiveSync/Exception.php0000664000076600000240000000251412273362323020244 0ustar * @package ActiveSync */ /** * Base exception class for Horde_ActiveSync * * Copyright 2010-2014 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-2014 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.12.3/lib/Horde/ActiveSync/Policies.php0000664000076600000240000003157612273362323020067 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-2014 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], true); $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.12.3/lib/Horde/ActiveSync/Rfc822.php0000664000076600000240000001074712273362323017263 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-2014 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 */ static public $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; /** * Constructor. * * @param mixed $rfc822 The incoming message. Either a string or a * stream resource. */ public function __construct($rfc822) { if (is_resource($rfc822)) { $this->_stream = new Horde_Stream_Existing(array('stream' => $rfc822)); rewind($this->_stream->stream); } else { $this->_stream = new Horde_Stream_Temp(array('max_memory' => self::$memoryLimit)); $this->_stream->add($rfc822, true); } 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; } /** * Return the raw message data. * * @return stream resource */ public function getString() { $this->_stream->rewind(); return $this->_stream->stream; } /** * Return the message headers. * * @return Horde_Mime_Headers The header object. */ public function getHeaders() { $this->_stream->rewind(); $hdr_text = $this->_stream->substring(0, $this->_hdr_pos); return Horde_Mime_Headers::parseHeaders($hdr_text); } /** * 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() { $i = 0; while (!$this->_stream->eof()) { $data = $this->_stream->substring(0, 8192); $hdr_pos = strpos($data, "\r\n\r\n"); if ($hdr_pos !== false) { return array($hdr_pos + ($i * 8192), 4); } $hdr_pos = strpos($data, "\n\n"); if ($hdr_pos !== false) { return array($hdr_pos + ($i * 8192), 2); } $i++; } $this->_stream->end(); return array($this->_stream->pos(), 0); } } Horde_ActiveSync-2.12.3/lib/Horde/ActiveSync/Status.php0000664000076600000240000001047612273362323017577 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-2014 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 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.12.3/lib/Horde/ActiveSync/SyncCache.php0000664000076600000240000006544312273362323020160 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-2014 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync * * @property array folders The folders cache. * @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 * * @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(); } 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']) || ($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 = $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); } /** * 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]); } /** * 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['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['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); } // in case the maxitems (windowsize) is above 512 or 0 it should be // interpreted as 512 according to specs. 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; } /** * 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->_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.12.3/lib/Horde/ActiveSync/Timezone.php0000664000076600000240000004164112273362323020104 0ustar * @package ActiveSync */ /** * Utility functions for dealing with Microsoft ActiveSync's Timezone format. * * Copyright 2009-2014 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-2014 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 */ static public function getOffsetsFromSyncTZ($data) { if (version_compare(PHP_VERSION, '5.5.0-dev', '>=')) { $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 (!self::_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. */ static public function getSyncTZFromOffsets(array $offsets) { if (!self::_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. */ static public 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 */ static protected 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 */ static protected 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 */ static protected 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; } } /** * Determine if the current machine is little endian. * * @return boolean True if endianness is little endian, otherwise false. */ static protected function _isLittleEndian() { $testint = 0x00FF; $p = pack('S', $testint); return ($testint === current(unpack('v', $p))); } /** * 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. */ static protected function _chbo($num) { $u = unpack('l', strrev(pack('l', $num))); return $u[1]; } } Horde_ActiveSync-2.12.3/lib/Horde/ActiveSync/Translation.php0000664000076600000240000000451712273362323020611 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-2014 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync */ class Horde_ActiveSync_Translation extends Horde_Translation { /** * Returns the translation of a message. * * @var string $message The string to translate. * * @return string The string translation, or the original string if no * translation exists. */ static public function t($message) { self::$_domain = 'Horde_ActiveSync'; self::$_directory = '@data_dir@' == '@'.'data_dir'.'@' ? dirname(__FILE__) . '/../../../locale' : '@data_dir@/Horde_ActiveSync/locale'; return parent::t($message); } /** * Returns the plural translation of a message. * * @param string $singular The singular version to translate. * @param string $plural The plural version to translate. * @param integer $number The number that determines singular vs. plural. * * @return string The string translation, or the original string if no * translation exists. */ static public function ngettext($singular, $plural, $number) { self::$_domain = 'Horde_ActiveSync'; self::$_directory = '@data_dir@' == '@'.'data_dir'.'@' ? dirname(__FILE__) . '/../../../locale' : '@data_dir@/Horde_ActiveSync/locale'; return parent::ngettext($singular, $plural, $number); } } Horde_ActiveSync-2.12.3/lib/Horde/ActiveSync/Utils.php0000664000076600000240000001664212273362323017415 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-2014 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 */ static public 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)); 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 */ static public 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 */ static public 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); } }Horde_ActiveSync-2.12.3/lib/Horde/ActiveSync/Wbxml.php0000664000076600000240000007406012273362323017404 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-2014 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', ), /* 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', ), /* 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', ), /* 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', ), /* 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', ), /* 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' ) ), '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', ) ); /** * 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_Support_Stub(); $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.12.3/lib/Horde/ActiveSync.php0000664000076600000240000012313112273362323016305 0ustar * @package ActiveSync */ /** * Horde_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-2014 Horde LLC (http://www.horde.org) * @author Michael J Rubinsky * @package ActiveSync * * @property Horde_ActiveSync_Wbxml_Encoder encoder The Wbxml encoder. * @property Horde_ActiveSync_Wbxml_Decoder decoder The Wbxml decoder. * @property Horde_ActiveSync_State_Base state The state object. * @property Horde_Controller_Reqeust_Http request The HTTP request object. * @property Horde_ActiveSync_Driver_Base driver * @property boolean provisioning * @property boolean multipart * @property string certPath * @property Horde_ActiveSync_Device device * @property Horde_Log_Logger logger */ 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_NONE = 9; /* 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'; // 14.0 const AIRSYNCBASE_PREVIEW = 'AirSyncBase:Preview'; // 14.1 const AIRSYNCBASE_BODYPARTPREFERENCE = 'AirSyncBase:BodyPartPreference'; const AIRSYNCBASE_BODYPART = 'AirSyncBase:BodyPart'; const AIRSYNCBASE_STATUS = 'AirSyncBase:Status'; /* 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; 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 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'; const LIBRARY_VERSION = '2.12.3'; /** * Logger * * @var Horde_ActiveSync_Interface_LoggerFactory */ protected $_loggerFactory; /** * The logger for this class. * * @var Horde_Log_Logger */ static protected $_logger; /** * Provisioning support * * @var string (TODO _constant this) */ protected $_provisioning; /** * Highest version to support. * * @var float */ protected $_maxVersion = self::VERSION_FOURTEENONE; /** * The actual version we are supporting. * * @var float */ static protected $_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 */ static protected $_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; /** * Supported EAS versions. * * @var array */ static protected $_supportedVersions = array( self::VERSION_TWOFIVE, self::VERSION_TWELVE, self::VERSION_TWELVEONE, self::VERSION_FOURTEEN, self::VERSION_FOURTEENONE ); /** * 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() */ static public 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. * * @return boolean True on successful authentication to the backend. * @throws Horde_ActiveSync_Exception */ public function authenticate($username = '') { // Get credentials $serverVars = $this->_request->getServerVars(); $user = $pass = ''; if (!empty($serverVars['PHP_AUTH_PW'])) { $user = empty($username) ? $serverVars['PHP_AUTH_USER'] : $username; $pass = $serverVars['PHP_AUTH_PW']; } elseif (!empty($serverVars['HTTP_AUTHORIZATION']) || !empty($serverVars['Authorization'])) { // Some clients use the non-standard 'Authorization' header. $authorization = !empty($serverVars['HTTP_AUTHORIZATION']) ? $serverVars['HTTP_AUTHORIZATION'] : $serverVars['Authorization']; $hash = base64_decode(str_replace('Basic ', '', $authorization)); if (strpos($hash, ':') !== false) { list($user, $pass) = explode(':', $hash, 2); } $user = !empty($username) ? $username : $user; } elseif (!empty($username)) { // Might not need a password, could be using X509 cert. $user = $username; } else { // 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($user); $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, $pass, $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']) ? strtoupper($get['DeviceId']) : null; } else { $devId = strtoupper($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(!empty($get['User']) ? $get['User'] : '')) { $this->activeSyncHeader(); $this->versionHeader(); $this->commandsHeader(); throw new Horde_Exception_AuthenticationFailure(); } // Set provisioning support now that we are authenticated. $this->setProvisioning($this->_driver->getProvisioning()); self::$_logger->info(sprintf( '[%s] %s request received for user %s', $this->_procid, strtoupper($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(); // 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); if ($version > self::VERSION_TWELVEONE) { $this->_globalError = $callback_ret; } else { throw new Horde_ActiveSync_Exception($msg); } } 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())) { $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); } } 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) { $this->_globalError = $callback_ret; } else { throw new Horde_ActiveSync_Exception($msg); } } } // 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; } else { throw new Horde_ActiveSync_Exception($msg); } } // Don't bother with everything else if all we want are Options if ($cmd == 'Options') { $this->_doOptionsRequest(); $this->_driver->clearAuthentication(); return true; } // 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($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()) ); return $result; } $this->_driver->clearAuthentication(); throw new Horde_ActiveSync_Exception_InvalidRequest(basename($cmd) . ' not supported.'); } /** * 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.2'); } } /** * 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: 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 long */ public function getProtocolVersion() { if (isset(self::$_version)) { return 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. * * @param integer $truncation The constant. * * @return integer|boolean Either the size, in bytes, to truncate or * falso if no truncation. */ static public function getTruncSize($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: case Horde_ActiveSync::TRUNCATION_NONE: return false; default: return 1024; // Default to 1Kb } } } Horde_ActiveSync-2.12.3/locale/de/LC_MESSAGES/Horde_ActiveSync.mo0000664000076600000240000000263012273362323021057 0ustar Þ•Ü%œ01 9D GS Xdin q |‰ž ¡ ­ ºÅÎ Öà ã î€ùz£¦· ½ÉÍÒÕêú  )7 NYavy‚    %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-10-29 10:13+0100 PO-Revision-Date: 2013-05-22 14:48+0200 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-NachrichtenteilNachrichtenteilModellMultipart-NachrichtenteilOSOS-SpracheTelefonnummerRichtlinien-SchlüsselAntwort anBetreffText-NachrichtenteilAnProgrammVideo-NachrichtenteilHorde_ActiveSync-2.12.3/locale/de/LC_MESSAGES/Horde_ActiveSync.po0000664000076600000240000000501512273362323021062 0ustar # German translations for Horde_ActiveSync package. # Copyright 2012-2014 Horde LLC (http://www.horde.org/) # This file is distributed under the same license as the Horde_ActiveSync package. # Jan Schneider , 2012-2014. # msgid "" msgstr "" "Project-Id-Version: Horde_ActiveSync \n" "Report-Msgid-Bugs-To: dev@lists.horde.org\n" "POT-Creation-Date: 2013-10-29 10:13+0100\n" "PO-Revision-Date: 2013-05-22 14:48+0200\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:543 #, php-format msgid "%s part" msgstr "%s-Nachrichtenteil" #: lib/Horde/ActiveSync/Imap/Message.php:522 msgid "Audio part" msgstr "Audio-Nachrichtenteil" #: lib/Horde/ActiveSync/Imap/Message.php:139 msgid "Cc" msgstr "Cc" #: lib/Horde/ActiveSync/Device.php:211 msgid "Common Name" msgstr "Eindeutiger Name" #: lib/Horde/ActiveSync/Imap/Message.php:119 msgid "Date" msgstr "Datum" #: lib/Horde/ActiveSync/Device.php:223 msgid "EAS Version" msgstr "EAS-Version" #: lib/Horde/ActiveSync/Imap/Message.php:123 msgid "From" msgstr "Von" #: lib/Horde/ActiveSync/Device.php:208 msgid "IMEI" msgstr "IMEI" #: lib/Horde/ActiveSync/Device.php:199 msgid "Id" msgstr "Id" #: lib/Horde/ActiveSync/Imap/Message.php:525 msgid "Image part" msgstr "Bild-Nachrichtenteil" #: lib/Horde/ActiveSync/Imap/Message.php:529 msgid "Message part" msgstr "Nachrichtenteil" #: lib/Horde/ActiveSync/Device.php:205 msgid "Model" msgstr "Modell" #: lib/Horde/ActiveSync/Imap/Message.php:532 msgid "Multipart part" msgstr "Multipart-Nachrichtenteil" #: lib/Horde/ActiveSync/Device.php:214 msgid "OS" msgstr "OS" #: lib/Horde/ActiveSync/Device.php:217 msgid "OS Language" msgstr "OS-Sprache" #: lib/Horde/ActiveSync/Device.php:220 msgid "Phone Number" msgstr "Telefonnummer" #: lib/Horde/ActiveSync/Device.php:200 msgid "Policy Key" msgstr "Richtlinien-Schlüssel" #: lib/Horde/ActiveSync/Imap/Message.php:127 msgid "Reply-To" msgstr "Antwort an" #: lib/Horde/ActiveSync/Imap/Message.php:131 msgid "Subject" msgstr "Betreff" #: lib/Horde/ActiveSync/Imap/Message.php:535 msgid "Text part" msgstr "Text-Nachrichtenteil" #: lib/Horde/ActiveSync/Imap/Message.php:135 msgid "To" msgstr "An" #: lib/Horde/ActiveSync/Device.php:201 msgid "User Agent" msgstr "Programm" #: lib/Horde/ActiveSync/Imap/Message.php:538 msgid "Video part" msgstr "Video-Nachrichtenteil" Horde_ActiveSync-2.12.3/locale/es/LC_MESSAGES/Horde_ActiveSync.mo0000664000076600000240000000256412273362323021104 0ustar Þ•Ü%œ01 9D GS Xdin q |‰ž ¡ ­ ºÅÎ Öà ã î®ù¨±¸ »É ÏÜßäçîö ý . =IPV[m    %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-18 14:03+0200 PO-Revision-Date: 2013-06-11 20:26+0200 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 EASDeIMEIIdImagenMensajeModeloMultiparteSOIdioma del SONúmero de teléfonoComportamientoResponder-AAsuntoTextoParaAgente de usuarioVídeoHorde_ActiveSync-2.12.3/locale/es/LC_MESSAGES/Horde_ActiveSync.po0000664000076600000240000000474112273362323021106 0ustar # Spanish 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. # Automatically generated, 2013. # msgid "" msgstr "" "Project-Id-Version: Horde_ActiveSync \n" "Report-Msgid-Bugs-To: dev@lists.horde.org\n" "POT-Creation-Date: 2013-06-18 14:03+0200\n" "PO-Revision-Date: 2013-06-11 20:26+0200\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:537 #, php-format msgid "%s part" msgstr "parte %s" #: lib/Horde/ActiveSync/Imap/Message.php:516 msgid "Audio part" msgstr "Sonido" #: lib/Horde/ActiveSync/Imap/Message.php:139 msgid "Cc" msgstr "Cc" #: lib/Horde/ActiveSync/Device.php:171 msgid "Common Name" msgstr "Nombre común" #: lib/Horde/ActiveSync/Imap/Message.php:119 msgid "Date" msgstr "Fecha" #: lib/Horde/ActiveSync/Device.php:183 msgid "EAS Version" msgstr "Versión EAS" #: lib/Horde/ActiveSync/Imap/Message.php:123 msgid "From" msgstr "De" #: 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:519 msgid "Image part" msgstr "Imagen" #: lib/Horde/ActiveSync/Imap/Message.php:523 msgid "Message part" msgstr "Mensaje" #: lib/Horde/ActiveSync/Device.php:165 msgid "Model" msgstr "Modelo" #: lib/Horde/ActiveSync/Imap/Message.php:526 msgid "Multipart part" msgstr "Multiparte" #: lib/Horde/ActiveSync/Device.php:174 msgid "OS" msgstr "SO" #: lib/Horde/ActiveSync/Device.php:177 msgid "OS Language" msgstr "Idioma del SO" #: lib/Horde/ActiveSync/Device.php:180 msgid "Phone Number" msgstr "Número de teléfono" #: lib/Horde/ActiveSync/Device.php:160 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:529 msgid "Text part" msgstr "Texto" #: lib/Horde/ActiveSync/Imap/Message.php:135 msgid "To" msgstr "Para" #: lib/Horde/ActiveSync/Device.php:161 msgid "User Agent" msgstr "Agente de usuario" #: lib/Horde/ActiveSync/Imap/Message.php:532 msgid "Video part" msgstr "Vídeo" Horde_ActiveSync-2.12.3/locale/eu/LC_MESSAGES/Horde_ActiveSync.mo0000664000076600000240000000172512273362323021104 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.12.3/locale/eu/LC_MESSAGES/Horde_ActiveSync.po0000664000076600000240000000335012273362323021103 0ustar # Basque translations for bcwhord package. # Copyright 2012-2014 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.12.3/locale/fr/LC_MESSAGES/Horde_ActiveSync.mo0000664000076600000240000000200212273362323021067 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.12.3/locale/fr/LC_MESSAGES/Horde_ActiveSync.po0000664000076600000240000000350312273362323021101 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.12.3/locale/ja/LC_MESSAGES/Horde_ActiveSync.mo0000664000076600000240000000267312273362323021070 0ustar Þ•Ü%œ01 9D GS Xdin q |‰ž ¡ ­ ºÅÎ Öà ã î£ù  ª· ºÄËÞãèëû:= FScls‰Œ¨    %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-05 10:33+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.5.4 %s パート音パートCcä¸€èˆ¬åæ—¥ä»˜EASãƒãƒ¼ã‚¸ãƒ§ãƒ³FromIMEIIdç”»åƒãƒ‘ートメッセージパート型番マルãƒãƒ‘ート・パートOSOS言語電話番å·ãƒãƒªã‚·ãƒ¼éµReply-Toä»¶åテキストパートToユーザエージェントビデオパートHorde_ActiveSync-2.12.3/locale/ja/LC_MESSAGES/Horde_ActiveSync.po0000664000076600000240000000504512273362323021067 0ustar # Japanese translation for Horde. # Copyright (C) 2012-2014 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: 2013-06-04 11:48+0200\n" "PO-Revision-Date: 2013-06-05 10:33+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.5.4\n" #: lib/Horde/ActiveSync/Imap/Message.php:520 #, php-format msgid "%s part" msgstr "%s パート" #: lib/Horde/ActiveSync/Imap/Message.php:499 msgid "Audio part" msgstr "音パート" #: lib/Horde/ActiveSync/Imap/Message.php:139 msgid "Cc" msgstr "Cc" #: lib/Horde/ActiveSync/Device.php:171 msgid "Common Name" msgstr "一般å" #: lib/Horde/ActiveSync/Imap/Message.php:119 msgid "Date" msgstr "日付" #: lib/Horde/ActiveSync/Device.php:183 msgid "EAS Version" msgstr "EASãƒãƒ¼ã‚¸ãƒ§ãƒ³" #: lib/Horde/ActiveSync/Imap/Message.php:123 msgid "From" msgstr "From" #: 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 "ç”»åƒãƒ‘ート" #: lib/Horde/ActiveSync/Imap/Message.php:506 msgid "Message part" msgstr "メッセージパート" #: lib/Horde/ActiveSync/Device.php:165 msgid "Model" msgstr "型番" #: lib/Horde/ActiveSync/Imap/Message.php:509 msgid "Multipart part" msgstr "マルãƒãƒ‘ート・パート" #: lib/Horde/ActiveSync/Device.php:174 msgid "OS" msgstr "OS" #: lib/Horde/ActiveSync/Device.php:177 msgid "OS Language" msgstr "OS言語" #: lib/Horde/ActiveSync/Device.php:180 msgid "Phone Number" msgstr "電話番å·" #: lib/Horde/ActiveSync/Device.php:160 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:512 msgid "Text part" msgstr "テキストパート" #: lib/Horde/ActiveSync/Imap/Message.php:135 msgid "To" msgstr "To" #: lib/Horde/ActiveSync/Device.php:161 msgid "User Agent" msgstr "ユーザエージェント" #: lib/Horde/ActiveSync/Imap/Message.php:515 msgid "Video part" msgstr "ビデオパート" Horde_ActiveSync-2.12.3/locale/nl/LC_MESSAGES/Horde_ActiveSync.mo0000664000076600000240000000264712273362323021110 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.12.3/locale/nl/LC_MESSAGES/Horde_ActiveSync.po0000664000076600000240000000511512273362323021104 0ustar # Dutch translations for Horde_ActiveSync package. # Copyright 2012-2014 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.12.3/locale/Horde_ActiveSync.pot0000664000076600000240000000430112273362323017066 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: 2013-10-29 10:13+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:543 #, php-format msgid "%s part" msgstr "" #: lib/Horde/ActiveSync/Imap/Message.php:522 msgid "Audio part" msgstr "" #: lib/Horde/ActiveSync/Imap/Message.php:139 msgid "Cc" msgstr "" #: lib/Horde/ActiveSync/Device.php:211 msgid "Common Name" msgstr "" #: lib/Horde/ActiveSync/Imap/Message.php:119 msgid "Date" msgstr "" #: lib/Horde/ActiveSync/Device.php:223 msgid "EAS Version" msgstr "" #: lib/Horde/ActiveSync/Imap/Message.php:123 msgid "From" msgstr "" #: lib/Horde/ActiveSync/Device.php:208 msgid "IMEI" msgstr "" #: lib/Horde/ActiveSync/Device.php:199 msgid "Id" msgstr "" #: lib/Horde/ActiveSync/Imap/Message.php:525 msgid "Image part" msgstr "" #: lib/Horde/ActiveSync/Imap/Message.php:529 msgid "Message part" msgstr "" #: lib/Horde/ActiveSync/Device.php:205 msgid "Model" msgstr "" #: lib/Horde/ActiveSync/Imap/Message.php:532 msgid "Multipart part" msgstr "" #: lib/Horde/ActiveSync/Device.php:214 msgid "OS" msgstr "" #: lib/Horde/ActiveSync/Device.php:217 msgid "OS Language" msgstr "" #: lib/Horde/ActiveSync/Device.php:220 msgid "Phone Number" msgstr "" #: lib/Horde/ActiveSync/Device.php:200 msgid "Policy Key" msgstr "" #: lib/Horde/ActiveSync/Imap/Message.php:127 msgid "Reply-To" msgstr "" #: lib/Horde/ActiveSync/Imap/Message.php:131 msgid "Subject" msgstr "" #: lib/Horde/ActiveSync/Imap/Message.php:535 msgid "Text part" msgstr "" #: lib/Horde/ActiveSync/Imap/Message.php:135 msgid "To" msgstr "" #: lib/Horde/ActiveSync/Device.php:201 msgid "User Agent" msgstr "" #: lib/Horde/ActiveSync/Imap/Message.php:538 msgid "Video part" msgstr "" Horde_ActiveSync-2.12.3/migration/Horde/ActiveSync/1_horde_activesync_base_tables.php0000664000076600000240000000641312273362323025630 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.12.3/migration/Horde/ActiveSync/2_horde_activesync_peruserpolicykey.php0000664000076600000240000000117012273362323026776 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.12.3/migration/Horde/ActiveSync/3_horde_activesync_clientidmap.php0000664000076600000240000000056312273362323025657 0ustar addColumn( 'horde_activesync_map', 'sync_clientid', 'string', array('limit' => 255)); } public function down() { $this->removeColumn('horde_activesync_map', 'sync_clientid'); } }Horde_ActiveSync-2.12.3/migration/Horde/ActiveSync/4_horde_activesync_longtextstatefield.php0000664000076600000240000000060712273362323027277 0ustar changeColumn( 'horde_activesync_state', 'sync_data', 'mediumtext'); } public function down() { $this->changeColumn( 'horde_activesync_state', 'sync_data', 'text'); } }Horde_ActiveSync-2.12.3/migration/Horde/ActiveSync/5_horde_activesync_addpendingfield.php0000664000076600000240000000053212273362323026465 0ustar addColumn( 'horde_activesync_state', 'sync_pending', 'mediumtext'); } public function down() { $this->removeColumn('horde_activesync_state', 'sync_pending'); } }Horde_ActiveSync-2.12.3/migration/Horde/ActiveSync/6_horde_activesync_addmailmap.php0000664000076600000240000000202312273362323025453 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.12.3/migration/Horde/ActiveSync/7_horde_activesync_clearstate.php0000664000076600000240000000073712273362323025524 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.12.3/migration/Horde/ActiveSync/8_horde_activesync_addmailflagged.php0000664000076600000240000000053212273362323026274 0ustar addColumn( 'horde_activesync_mailmap', 'sync_flagged', 'integer'); } public function down() { $this->removeColumn('horde_activesync_mailmap', 'sync_flagged'); } }Horde_ActiveSync-2.12.3/migration/Horde/ActiveSync/9_horde_activesync_add_cache.php0000664000076600000240000000117712273362323025251 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.12.3/migration/Horde/ActiveSync/10_horde_activesync_add_deviceproperties.php0000664000076600000240000000054512273362323027630 0ustar addColumn( 'horde_activesync_device', 'device_properties', 'text'); } public function down() { $this->removeColumn('horde_activesync_device', 'device_properties'); } } Horde_ActiveSync-2.12.3/migration/Horde/ActiveSync/11_horde_activesync_removepingstate.php0000664000076600000240000000105312273362323026654 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.12.3/migration/Horde/ActiveSync/12_horde_activesync_longtextcachefield.php0000664000076600000240000000061112273362323027274 0ustar changeColumn( 'horde_activesync_cache', 'cache_data', 'mediumtext'); } public function down() { $this->changeColumn( 'horde_activesync_cache', 'cache_data', 'text'); } }Horde_ActiveSync-2.12.3/migration/Horde/ActiveSync/13_horde_activesync_booleanfields.php0000664000076600000240000000164012273362323026252 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.12.3/migration/Horde/ActiveSync/14_horde_activesync_binarystatefield.php0000664000076600000240000000060712273362323027000 0ustar changeColumn( 'horde_activesync_state', 'sync_data', 'binary'); } public function down() { $this->changeColumn( 'horde_activesync_state', 'sync_data', 'mediumtext'); } }Horde_ActiveSync-2.12.3/migration/Horde/ActiveSync/15_horde_activesync_integerimapuidfield.php0000664000076600000240000000100012273362323027446 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.12.3/migration/Horde/ActiveSync/16_horde_activesync_fix_blob_length.php0000664000076600000240000000035012273362323026571 0ustar changeColumn('horde_activesync_state', 'sync_data', 'binary'); } public function down() { } } Horde_ActiveSync-2.12.3/migration/Horde/ActiveSync/17_horde_activesync_clearallstate.php0000664000076600000240000000133412273362323026270 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.12.3/migration/Horde/ActiveSync/18_horde_activesync_addmapdeleteflag.php0000664000076600000240000000052512273362323026715 0ustar addColumn( 'horde_activesync_map', 'sync_deleted', 'boolean'); } public function down() { $this->removeColumn('horde_activesync_map', 'sync_deleted'); } } Horde_ActiveSync-2.12.3/migration/Horde/ActiveSync/19_horde_activesync_addtimestamp.php0000664000076600000240000000072412273362323026130 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.12.3/migration/Horde/ActiveSync/20_horde_activesync_removesynccounters.php0000664000076600000240000000143512273362323027421 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.12.3/test/Horde/ActiveSync/fixtures/appointment.wbxml0000664000076600000240000000057012273362323023270 0ustar ]ELAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsAAAABAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAACAAIAAAAAAAAAxP///w==g20111201T200000ZR20111201T210000ZfEvent TitleWPhiladelphia, PAM2e1Q20111201T200000ZX0KEvent DescriptionL0Horde_ActiveSync-2.12.3/test/Horde/ActiveSync/fixtures/default_policies.wbxml0000664000076600000240000000003612273362323024242 0ustar MN0P0S1W5000000Horde_ActiveSync-2.12.3/test/Horde/ActiveSync/fixtures/default_policies.xml0000664000076600000240000000020112273362323023703 0ustar Horde_ActiveSync-2.12.3/test/Horde/ActiveSync/fixtures/dst.wbxml0000664000076600000240000000060512273362323021523 0ustar ]ELAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsAAAABAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAACAAIAAAAAAAAAxP///w==Q20111001T190000Zg20111001T190000ZfEvent TitleWPhiladelphia, PAR20111001T200000Z[\1_2`64e1M2KEvent DescriptionL0Horde_ActiveSync-2.12.3/test/Horde/ActiveSync/fixtures/invitation_one.eml0000664000076600000240000002745612273362323023417 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.12.3/test/Horde/ActiveSync/fixtures/iOSMultipartAlternative.eml0000664000076600000240000000164412273362323025154 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.12.3/test/Horde/ActiveSync/fixtures/meeting_request_one.wbxml0000664000076600000240000000077712273362323025004 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.12.3/test/Horde/ActiveSync/fixtures/recurrence.wbxml0000664000076600000240000000061212273362323023064 0ustar ]ELAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsAAAABAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAACAAIAAAAAAAAAxP///w==g20111201T200000ZR20111201T210000ZfEvent TitleWPhiladelphia, PAM2[\1_2`16e1Q20111201T200000ZX0KEvent DescriptionL0Horde_ActiveSync-2.12.3/test/Horde/ActiveSync/fixtures/simpleexception.wbxml0000664000076600000240000000065412273362323024145 0ustar ]ELAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsAAAABAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAACAAIAAAAAAAAAxP///w==g20111201T200000ZR20111201T210000ZfEvent TitleWPhiladelphia, PAM2[\1_2`16e1Q20111201T200000ZX0TSV20111229T200000ZU1L0KEvent DescriptionL0Horde_ActiveSync-2.12.3/test/Horde/ActiveSync/StateTest/Mongo/BaseTest.php0000664000076600000240000001253012273362323023227 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) {} } public function loadStateTest() { $this->_loadStateTest(); } /** * @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() { 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.12.3/test/Horde/ActiveSync/StateTest/Sql/Pdo/MysqlTest.php0000664000076600000240000000170012273362323023661 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.12.3/test/Horde/ActiveSync/StateTest/Sql/Base.php0000664000076600000240000001107612273362323022053 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 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(); self::$migrator = new Horde_Db_Migration_Migrator( self::$db, self::$logger->getLogger(), array('migrationsPath' => $dir, 'schemaTableName' => 'horde_activesync_test_schema')); self::$migrator->up(); } public static function tearDownAfterClass() { if (self::$migrator) { self::$migrator->down(); } if (self::$db) { self::$db->disconnect(); self::$db = null; } parent::tearDownAfterClass(); } public function setUp() { if (!self::$db) { $this->markTestSkipped(self::$reason); } self::$state = new Horde_ActiveSync_State_Sql(array('db' => self::$db)); } }Horde_ActiveSync-2.12.3/test/Horde/ActiveSync/StateTest/Sql/MysqliTest.php0000664000076600000240000000153312273362323023314 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.12.3/test/Horde/ActiveSync/StateTest/Sql/MysqlTest.php0000664000076600000240000000152312273362323023142 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.12.3/test/Horde/ActiveSync/StateTest/Base.php0000664000076600000240000005456212273362323021323 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() { $collection = array( 'folderid' => '@Contacts@', 'class' => Horde_ActiveSync::CLASS_CONTACTS); 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@', ), '519422f1-4c5c-4547-946a-1701c0a8015f' => array( 'class' => 'Email', 'serverid' => 'INBOX' ) ); $this->assertEquals($expected, $cache->getFolders()); $expected = array( 'class' => 'Email', 'serverid' => 'INBOX' ); $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@', ), '519422f1-4c5c-4547-946a-1701c0a8015f' => array( 'class' => 'Email', 'serverid' => 'INBOX' ) ); $this->assertEquals($expected, $cache->getFolders()); $expected = array( 'class' => 'Email', 'serverid' => 'INBOX' ); $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')); $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(); $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.12.3/test/Horde/ActiveSync/AllTests.php0000664000076600000240000000013212273362323020244 0ustar run(); Horde_ActiveSync-2.12.3/test/Horde/ActiveSync/AppointmentTest.php0000664000076600000240000003031512273362323021655 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')); } } Horde_ActiveSync-2.12.3/test/Horde/ActiveSync/AutodiscoverTest.php0000664000076600000240000001722512273362323022033 0ustar * @category Horde * @package ActiveSync */ class Horde_ActiveSync_AutoDiscoverTest extends Horde_Test_Case { static protected $_server; static protected $_input; static protected $_driver; static protected $_request; public function setup() { self::$_driver = $this->getMockSkipConstructor('Horde_ActiveSync_Driver_Base'); self::$_input = fopen('php://memory', 'wb+'); $decoder = new Horde_ActiveSync_Wbxml_Decoder(self::$_input); $output = fopen('php://memory', 'wb+'); $encoder = new Horde_ActiveSync_Wbxml_Encoder($output); $state = $this->getMockSkipConstructor('Horde_ActiveSync_State_Base'); self::$_request = $this->getMockSkipConstructor('Horde_Controller_Request_Http'); self::$_server = new Horde_ActiveSync(self::$_driver, $decoder, $encoder, $state, self::$_request); } /** * 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() { $request = << mike@example.com http://schemas.microsoft.com/exchange/autodiscover/mobilesync/responseschema/2006 EOT; fwrite(self::$_input, $request); rewind(self::$_input); // Mock the getUsernameFromEmail method to return 'mike' when 'mike@example.com' // is passed. self::$_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. self::$_driver->expects($this->any()) ->method('authenticate') ->will($this->returnValueMap(array(array('mike', '', null, true)))); // Setup is called once, and must return true. self::$_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' ); self::$_driver->expects($this->once()) ->method('autoDiscover') ->will($this->returnValueMap(array(array($mock_driver_parameters, $mock_driver_results)))); self::$_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; self::$_server->encoder->getStream()->rewind(); $this->assertEquals($expected, self::$_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=='; self::$_request->expects($this->any()) ->method('getServerVars') ->will($this->returnValue(array('HTTP_AUTHORIZATION' => $auth))); // Mock the getUsernameFromEmail method to return 'mike' when 'mike' // is passed. self::$_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. self::$_driver->expects($this->any()) ->method('authenticate') ->will($this->returnValueMap(array(array('mike', 'password', null, true)))); // Setup is called once, and must return true. self::$_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' ); self::$_driver->expects($this->once()) ->method('autoDiscover') ->will($this->returnValueMap(array(array($mock_driver_parameters, $mock_driver_results)))); self::$_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; self::$_server->encoder->getStream()->rewind(); $this->assertEquals($expected, self::$_server->encoder->getStream()->getString()); } } Horde_ActiveSync-2.12.3/test/Horde/ActiveSync/bootstrap.php0000664000076600000240000000014312273362323020530 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.12.3/test/Horde/ActiveSync/conf.php.dist0000664000076600000240000000252212273362323020405 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)); } /** * 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.12.3/test/Horde/ActiveSync/DeviceTest.php0000664000076600000240000001151012273362323020552 0ustar * @category Horde * @package ActiveSync */ class Horde_ActiveSync_DeviceTest extends Horde_Test_Case { public function testDeviceGetMajorVersion() { $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(Horde_ActiveSync_Device::TYPE_IPOD, strtolower($device->deviceType)); $this->assertEquals(Horde_ActiveSync_Device::MULTIPLEX_NOTES, $device->multiplex); $fixture = array( 'deviceType' => 'iPhone', 'userAgent' => 'iOS/6.1.3 (10B329)' ); $device = new Horde_ActiveSync_Device($state, $fixture); $this->assertEquals(6, $device->getMajorVersion()); $this->assertEquals(Horde_ActiveSync_Device::TYPE_IPHONE, strtolower($device->deviceType)); $this->assertEquals(Horde_ActiveSync_Device::MULTIPLEX_NOTES, $device->multiplex); $fixture = array( 'userAgent' => 'Android/0.3', 'deviceType' => 'Android'); $device = new Horde_ActiveSync_Device($state, $fixture); $this->assertEquals(0, $device->getMajorVersion()); $this->assertEquals(Horde_ActiveSync_Device::TYPE_ANDROID, strtolower($device->deviceType)); $this->assertEquals(Horde_ActiveSync_Device::TYPE_ANDROID, strtolower($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); $fixture = array( 'userAgent' => 'TouchDown(MSRPC)/7.1.0005', 'deviceType' => 'Android'); $device = new Horde_ActiveSync_Device($state, $fixture); $this->assertEquals(7, $device->getMajorVersion()); $this->assertEquals(Horde_ActiveSync_Device::TYPE_ANDROID, strtolower($device->deviceType)); $this->assertEquals(Horde_ActiveSync_Device::TYPE_TOUCHDOWN, strtolower($device->clientType)); $this->assertEquals(0, $device->multiplex); $fixture = array( 'userAgent' => 'MOTOROLA-Droid(4D6F7869SAM)/2.1707', 'deviceType' => 'Android'); $device = new Horde_ActiveSync_Device($state, $fixture); $this->assertEquals(0, $device->getMajorVersion()); $this->assertEquals(Horde_ActiveSync_Device::TYPE_ANDROID, strtolower($device->deviceType)); $this->assertEquals(Horde_ActiveSync_Device::TYPE_UNKNOWN, strtolower($device->clientType)); $this->assertEquals(0, $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); $this->assertEquals(0, $device->getMajorVersion()); $this->assertEquals('samsungsmn900v', strtolower($device->deviceType)); $this->assertEquals(Horde_ActiveSync_Device::TYPE_UNKNOWN, strtolower($device->clientType)); $this->assertEquals(0, $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 00:00:00', (string)$bday); $this->assertEquals('America/New_York', $bday->timezone); // 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 00:00:00', (string)$bday); $this->assertEquals('Pacific/Honolulu', $bday->timezone); date_default_timezone_set($tz); } }Horde_ActiveSync-2.12.3/test/Horde/ActiveSync/ImapFolderTest.php0000664000076600000240000000631412273362323021403 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(); } }Horde_ActiveSync-2.12.3/test/Horde/ActiveSync/InviteTest.php0000664000076600000240000000345312273362323020620 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.12.3/test/Horde/ActiveSync/phpunit.xml0000664000076600000240000000005612273362323020216 0ustar Horde_ActiveSync-2.12.3/test/Horde/ActiveSync/PolicyTest.php0000664000076600000240000000232612273362323020617 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.12.3/test/Horde/ActiveSync/Rfc822Test.php0000664000076600000240000000464212273362323020331 0ustar * @category Horde * @package ActiveSync */ class Horde_ActiveSync_Rfc822Test extends Horde_Test_Case { public function testHeadersMultipartAlternativeAsString() { $fixture = file_get_contents(__DIR__ . '/fixtures/iOSMultipartAlternative.eml'); $rfc822 = new Horde_ActiveSync_Rfc822($fixture); $test = $rfc822->getHeaders(); $expected = 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)'); $this->assertEquals($expected, $rfc822->getHeaders()->toArray()); } public function testHeadersMultipartAlternativeAsStream() { $fixture = fopen(__DIR__ . '/fixtures/iOSMultipartAlternative.eml', 'r'); $rfc822 = new Horde_ActiveSync_Rfc822($fixture); $test = $rfc822->getHeaders(); $expected = 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)'); $this->assertEquals($expected, $rfc822->getHeaders()->toArray()); fclose($fixture); } 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')); } }Horde_ActiveSync-2.12.3/test/Horde/ActiveSync/ServerTest.php0000664000076600000240000000416512273362323020631 0ustar * @category Horde * @package ActiveSync */ class Horde_ActiveSync_ServerTest extends Horde_Test_Case { static protected $_server; public function setup() { $driver = $this->getMockSkipConstructor('Horde_ActiveSync_Driver_Base'); $input = fopen('php://memory', 'wb+'); $decoder = new Horde_ActiveSync_Wbxml_Decoder($input); $output = fopen('php://memory', 'wb+'); $encoder = new Horde_ActiveSync_Wbxml_Encoder($output); $state = $this->getMockSkipConstructor('Horde_ActiveSync_State_Base'); $request = $this->getMockSkipConstructor('Horde_Controller_Request_Http'); self::$_server = new Horde_ActiveSync($driver, $decoder, $encoder, $state, $request); } public function testSupportedVersions() { $this->assertEquals('2.5,12.0,12.1,14.0,14.1', self::$_server->getSupportedVersions()); self::$_server->setSupportedVersion(Horde_ActiveSync::VERSION_TWELVEONE); $this->assertEquals('2.5,12.0,12.1', self::$_server->getSupportedVersions()); self::$_server->setSupportedVersion(Horde_ActiveSync::VERSION_FOURTEEN); $this->assertEquals('2.5,12.0,12.1,14.0', self::$_server->getSupportedVersions()); } public function testSupportedCommands() { $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', self::$_server->getSupportedCommands()); self::$_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', self::$_server->getSupportedCommands()); } } Horde_ActiveSync-2.12.3/test/Horde/ActiveSync/UtilsTest.php0000664000076600000240000000264712273362323020466 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); } }