pax_global_header00006660000000000000000000000064136234637010014517gustar00rootroot0000000000000052 comment=73afb5a6b545e5298ff4d50c108c8fdcd8a18357 EAS-4-TbSync-1.12/000077500000000000000000000000001362346370100134135ustar00rootroot00000000000000EAS-4-TbSync-1.12/.github/000077500000000000000000000000001362346370100147535ustar00rootroot00000000000000EAS-4-TbSync-1.12/.github/issue_template.md000066400000000000000000000010631362346370100203200ustar00rootroot00000000000000## Your environment TbSync version: EAS-4-TbSync version: Thunderbird version: [ ] Yes, I have installed the latest available (beta) version from - https://github.com/jobisoft/TbSync/releases - https://github.com/jobisoft/EAS-4-TbSync/releases and my issue is not yet fixed, I can still reproduce it. ## Expected behavior ... ## Actual behavior ... ## Steps to reproduce ... To help resolving your issue, enable debug logging (TbSync Account Manager -> Help) and send me the debug.log via e-mail (use the title of your issue as subject of the email). EAS-4-TbSync-1.12/.gitignore000066400000000000000000000000061362346370100153770ustar00rootroot00000000000000*.xpi EAS-4-TbSync-1.12/CONTRIBUTORS.md000066400000000000000000000005501362346370100156720ustar00rootroot00000000000000## Creator * John Bieling ## Contributors * Chris Allan (recurring events) * John Bieling * Emil Ljungdahl * Mark Nethersole (initial implementation of contact sync) * Dzamo Norton * Puran2 * Tijuca ## Translators * John Bieling (de, en-US) * Wanderlei Hüttel (pt-BR) * Alessandro Menti (it) * Óvári (hu) * Alexey Sinitsyn (ru) * Daniel Wróblewski (pl) EAS-4-TbSync-1.12/LICENSE000066400000000000000000000405251362346370100144260ustar00rootroot00000000000000Mozilla Public License Version 2.0 ================================== 1. Definitions -------------- 1.1. "Contributor" means each individual or legal entity that creates, contributes to the creation of, or owns Covered Software. 1.2. "Contributor Version" means the combination of the Contributions of others (if any) used by a Contributor and that particular Contributor's Contribution. 1.3. "Contribution" means Covered Software of a particular Contributor. 1.4. "Covered Software" means Source Code Form to which the initial Contributor has attached the notice in Exhibit A, the Executable Form of such Source Code Form, and Modifications of such Source Code Form, in each case including portions thereof. 1.5. "Incompatible With Secondary Licenses" means (a) that the initial Contributor has attached the notice described in Exhibit B to the Covered Software; or (b) that the Covered Software was made available under the terms of version 1.1 or earlier of the License, but not also under the terms of a Secondary License. 1.6. "Executable Form" means any form of the work other than Source Code Form. 1.7. "Larger Work" means a work that combines Covered Software with other material, in a separate file or files, that is not Covered Software. 1.8. "License" means this document. 1.9. "Licensable" means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently, any and all of the rights conveyed by this License. 1.10. "Modifications" means any of the following: (a) any file in Source Code Form that results from an addition to, deletion from, or modification of the contents of Covered Software; or (b) any new file in Source Code Form that contains any Covered Software. 1.11. "Patent Claims" of a Contributor means any patent claim(s), including without limitation, method, process, and apparatus claims, in any patent Licensable by such Contributor that would be infringed, but for the grant of the License, by the making, using, selling, offering for sale, having made, import, or transfer of either its Contributions or its Contributor Version. 1.12. "Secondary License" means either the GNU General Public License, Version 2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General Public License, Version 3.0, or any later versions of those licenses. 1.13. "Source Code Form" means the form of the work preferred for making modifications. 1.14. "You" (or "Your") means an individual or a legal entity exercising rights under this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with You. For purposes of this definition, "control" means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity. 2. License Grants and Conditions -------------------------------- 2.1. Grants Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license: (a) under intellectual property rights (other than patent or trademark) Licensable by such Contributor to use, reproduce, make available, modify, display, perform, distribute, and otherwise exploit its Contributions, either on an unmodified basis, with Modifications, or as part of a Larger Work; and (b) under Patent Claims of such Contributor to make, use, sell, offer for sale, have made, import, and otherwise transfer either its Contributions or its Contributor Version. 2.2. Effective Date The licenses granted in Section 2.1 with respect to any Contribution become effective for each Contribution on the date the Contributor first distributes such Contribution. 2.3. Limitations on Grant Scope The licenses granted in this Section 2 are the only rights granted under this License. No additional rights or licenses will be implied from the distribution or licensing of Covered Software under this License. Notwithstanding Section 2.1(b) above, no patent license is granted by a Contributor: (a) for any code that a Contributor has removed from Covered Software; or (b) for infringements caused by: (i) Your and any other third party's modifications of Covered Software, or (ii) the combination of its Contributions with other software (except as part of its Contributor Version); or (c) under Patent Claims infringed by Covered Software in the absence of its Contributions. This License does not grant any rights in the trademarks, service marks, or logos of any Contributor (except as may be necessary to comply with the notice requirements in Section 3.4). 2.4. Subsequent Licenses No Contributor makes additional grants as a result of Your choice to distribute the Covered Software under a subsequent version of this License (see Section 10.2) or under the terms of a Secondary License (if permitted under the terms of Section 3.3). 2.5. Representation Each Contributor represents that the Contributor believes its Contributions are its original creation(s) or it has sufficient rights to grant the rights to its Contributions conveyed by this License. 2.6. Fair Use This License is not intended to limit any rights You have under applicable copyright doctrines of fair use, fair dealing, or other equivalents. 2.7. Conditions Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in Section 2.1. 3. Responsibilities ------------------- 3.1. Distribution of Source Form All distribution of Covered Software in Source Code Form, including any Modifications that You create or to which You contribute, must be under the terms of this License. You must inform recipients that the Source Code Form of the Covered Software is governed by the terms of this License, and how they can obtain a copy of this License. You may not attempt to alter or restrict the recipients' rights in the Source Code Form. 3.2. Distribution of Executable Form If You distribute Covered Software in Executable Form then: (a) such Covered Software must also be made available in Source Code Form, as described in Section 3.1, and You must inform recipients of the Executable Form how they can obtain a copy of such Source Code Form by reasonable means in a timely manner, at a charge no more than the cost of distribution to the recipient; and (b) You may distribute such Executable Form under the terms of this License, or sublicense it under different terms, provided that the license for the Executable Form does not attempt to limit or alter the recipients' rights in the Source Code Form under this License. 3.3. Distribution of a Larger Work You may create and distribute a Larger Work under terms of Your choice, provided that You also comply with the requirements of this License for the Covered Software. If the Larger Work is a combination of Covered Software with a work governed by one or more Secondary Licenses, and the Covered Software is not Incompatible With Secondary Licenses, this License permits You to additionally distribute such Covered Software under the terms of such Secondary License(s), so that the recipient of the Larger Work may, at their option, further distribute the Covered Software under the terms of either this License or such Secondary License(s). 3.4. Notices You may not remove or alter the substance of any license notices (including copyright notices, patent notices, disclaimers of warranty, or limitations of liability) contained within the Source Code Form of the Covered Software, except that You may alter any license notices to the extent required to remedy known factual inaccuracies. 3.5. Application of Additional Terms You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, You may do so only on Your own behalf, and not on behalf of any Contributor. You must make it absolutely clear that any such warranty, support, indemnity, or liability obligation is offered by You alone, and You hereby agree to indemnify every Contributor for any liability incurred by such Contributor as a result of warranty, support, indemnity or liability terms You offer. You may include additional disclaimers of warranty and limitations of liability specific to any jurisdiction. 4. Inability to Comply Due to Statute or Regulation --------------------------------------------------- If it is impossible for You to comply with any of the terms of this License with respect to some or all of the Covered Software due to statute, judicial order, or regulation then You must: (a) comply with the terms of this License to the maximum extent possible; and (b) describe the limitations and the code they affect. Such description must be placed in a text file included with all distributions of the Covered Software under this License. Except to the extent prohibited by statute or regulation, such description must be sufficiently detailed for a recipient of ordinary skill to be able to understand it. 5. Termination -------------- 5.1. The rights granted under this License will terminate automatically if You fail to comply with any of its terms. However, if You become compliant, then the rights granted under this License from a particular Contributor are reinstated (a) provisionally, unless and until such Contributor explicitly and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor fails to notify You of the non-compliance by some reasonable means prior to 60 days after You have come back into compliance. Moreover, Your grants from a particular Contributor are reinstated on an ongoing basis if such Contributor notifies You of the non-compliance by some reasonable means, this is the first time You have received notice of non-compliance with this License from such Contributor, and You become compliant prior to 30 days after Your receipt of the notice. 5.2. If You initiate litigation against any entity by asserting a patent infringement claim (excluding declaratory judgment actions, counter-claims, and cross-claims) alleging that a Contributor Version directly or indirectly infringes any patent, then the rights granted to You by any and all Contributors for the Covered Software under Section 2.1 of this License shall terminate. 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user license agreements (excluding distributors and resellers) which have been validly granted by You or Your distributors under this License prior to termination shall survive termination. ************************************************************************ * * * 6. Disclaimer of Warranty * * ------------------------- * * * * Covered Software is provided under this License on an "as is" * * basis, without warranty of any kind, either expressed, implied, or * * statutory, including, without limitation, warranties that the * * Covered Software is free of defects, merchantable, fit for a * * particular purpose or non-infringing. The entire risk as to the * * quality and performance of the Covered Software is with You. * * Should any Covered Software prove defective in any respect, You * * (not any Contributor) assume the cost of any necessary servicing, * * repair, or correction. This disclaimer of warranty constitutes an * * essential part of this License. No use of any Covered Software is * * authorized under this License except under this disclaimer. * * * ************************************************************************ ************************************************************************ * * * 7. Limitation of Liability * * -------------------------- * * * * Under no circumstances and under no legal theory, whether tort * * (including negligence), contract, or otherwise, shall any * * Contributor, or anyone who distributes Covered Software as * * permitted above, be liable to You for any direct, indirect, * * special, incidental, or consequential damages of any character * * including, without limitation, damages for lost profits, loss of * * goodwill, work stoppage, computer failure or malfunction, or any * * and all other commercial damages or losses, even if such party * * shall have been informed of the possibility of such damages. This * * limitation of liability shall not apply to liability for death or * * personal injury resulting from such party's negligence to the * * extent applicable law prohibits such limitation. Some * * jurisdictions do not allow the exclusion or limitation of * * incidental or consequential damages, so this exclusion and * * limitation may not apply to You. * * * ************************************************************************ 8. Litigation ------------- Any litigation relating to this License may be brought only in the courts of a jurisdiction where the defendant maintains its principal place of business and such litigation shall be governed by laws of that jurisdiction, without reference to its conflict-of-law provisions. Nothing in this Section shall prevent a party's ability to bring cross-claims or counter-claims. 9. Miscellaneous ---------------- This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not be used to construe this License against a Contributor. 10. Versions of the License --------------------------- 10.1. New Versions Mozilla Foundation is the license steward. Except as provided in Section 10.3, no one other than the license steward has the right to modify or publish new versions of this License. Each version will be given a distinguishing version number. 10.2. Effect of New Versions You may distribute the Covered Software under the terms of the version of the License under which You originally received the Covered Software, or under the terms of any subsequent version published by the license steward. 10.3. Modified Versions If you create software not governed by this License, and you want to create a new license for such software, you may create and use a modified version of this License if you rename the license and remove any references to the name of the license steward (except to note that such modified license differs from this License). 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses If You choose to distribute Source Code Form that is Incompatible With Secondary Licenses under the terms of this version of the License, the notice described in Exhibit B of this License must be attached. Exhibit A - Source Code Form License Notice ------------------------------------------- This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. You may add additional accurate notices of copyright ownership. Exhibit B - "Incompatible With Secondary Licenses" Notice --------------------------------------------------------- This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0. EAS-4-TbSync-1.12/Makebeta000066400000000000000000000026141362346370100150520ustar00rootroot00000000000000#!/bin/bash # This file is part of EAS-4-TbSync. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. # $1 link to base web server : https://tbsync.jobisoft.de/beta # $2 local path of base web server : /var/www/jobisoft.de/tbsync/beta # $3 name of XPI : EAS-4-TbSync.xpi git clean -df git checkout -- . git pull version=$(cat manifest.json | jq -r .version) updatefile=update-eas.json sed -i "s/%VERSION%/$version/g" "beta-release-channel-update.json" sed -i "s|%LINK%|$1/$3|g" "beta-release-channel-update.json" sed -i "s/static getApiVersion() { return \"/static getApiVersion() { return \"Beta /g" "content/provider.js" echo "Releasing version $version via beta release channel (will include updateURL)" sed -i "s|\"name\": \"__MSG_extensionName__\",|\"name\": \"EAS for TbSync [Beta Release Channel]\",|g" "manifest.json" sed -i "s|\"gecko\": {|\"gecko\": {\n \"update_url\": \"$1/$updatefile\",|g" "manifest.json" cp beta-release-channel-update.json $2/$updatefile rm -f $3 rm -f $3.tar.gz zip -r $3 content _locales skin chrome.manifest manifest.json bootstrap.js LICENSE CONTRIBUTORS.md tar cfvz $3.tar.gz content _locales skin chrome.manifest manifest.json bootstrap.js LICENSE CONTRIBUTORS.md cp $3 $2/$3 cp $3.tar.gz $2/$3.tar.gz EAS-4-TbSync-1.12/Makefile.bat000066400000000000000000000006671362346370100156310ustar00rootroot00000000000000@echo off REM This file is part of EAS-4-TbSync. REM REM This Source Code Form is subject to the terms of the Mozilla Public REM License, v. 2.0. If a copy of the MPL was not distributed with this REM file, You can obtain one at http://mozilla.org/MPL/2.0/. del EAS-4-TbSync.xpi "C:\Program Files\7-Zip\7zG.exe" a -tzip EAS-4-TbSync.xpi content _locales skin chrome.manifest manifest.json CONTRIBUTORS.md LICENSE README.md bootstrap.js EAS-4-TbSync-1.12/README.md000066400000000000000000000023721362346370100146760ustar00rootroot00000000000000# EAS-4-TbSync This provider add-on adds Exchange ActiveSync (EAS v2.5 & v14.0) synchronization capabilities to [TbSync](https://github.com/jobisoft/TbSync/). More information can be found in the [wiki](https://github.com/jobisoft/EAS-4-TbSync/wiki/About:-Provider-for-Exchange-ActiveSync) of this repository ## Want to add or fix a localization? To help translating this project, please visit [crowdin.com](https://crowdin.com/profile/jobisoft), where the localizations are managed. If you want to add a new language, just contact me and I will set it up. ## External data sources * TbSync uses a [timezone mapping](https://github.com/mj1856/TimeZoneConverter/blob/master/src/TimeZoneConverter/Data/Mapping.csv.gz) provided by [Matt Johnson](https://github.com/mj1856) ## Icon sources and attributions #### CC0 Public Domain * [365_*.png] by [Microsoft / Wikimedia](https://commons.wikimedia.org/w/index.php?curid=21546299), converted from [SVG to PNG](https://ezgif.com/svg-to-png) #### CC-BY 3.0 * [eas*.png] by [FatCow Web Hosting](https://www.iconfinder.com/icons/64484/exchange_ms_icon) * [exchange_300.png] derived from [Microsoft Exchange Icon #270871](https://icon-library.net/icon/microsoft-exchange-icon-10.html), [resized](www.simpleimageresizer.com/) EAS-4-TbSync-1.12/_locales/000077500000000000000000000000001362346370100151745ustar00rootroot00000000000000EAS-4-TbSync-1.12/_locales/Readme.txt000066400000000000000000000003511362346370100171310ustar00rootroot00000000000000Want to add or fix a localization? To help translating this project, please visit https://crowdin.com/profile/jobisoft where the localizations are managed. If you want to add a new language, just contact me and I will set it up. EAS-4-TbSync-1.12/_locales/bg/000077500000000000000000000000001362346370100155645ustar00rootroot00000000000000EAS-4-TbSync-1.12/_locales/bg/eas.dtd000066400000000000000000000103071362346370100170320ustar00rootroot00000000000000 EAS-4-TbSync-1.12/_locales/bg/eas.properties000066400000000000000000000210621362346370100204530ustar00rootroot00000000000000newaccount.add_custom=Add account newaccount.add_auto=Autodiscover settings and add account autodiscover.Querying=Searching for settings… autodiscover.Ok=Autodiscover completed successfully, you can now check the optional settings and establish the synchronization connection. autodiscover.NeedEmail=Autodiscover needs a valid email address as user name. autodiscover.Failed=Autodiscover for user <##user##> failed. Either the provided credentials were wrong or your ActiveSync provider has a temporary issue, or does not support Autodiscover. deletefolder.notallowed=Please unsubscribe folder “##replace.1##” before trying to purge it from trash. deletefolder.confirm=Do you really want to PERMANENTLY PURGE folder “##replace.1##” from trash? deletefolder.menuentry=Permanently purge folder “##replace.1##” from trash recyclebin = Trash syncstate.prepare.request.setdeviceinfo=Sending device information syncstate.send.request.setdeviceinfo=Sending device information syncstate.eval.response.setdeviceinfo=Sending device information syncstate.syncing=Initialize synchronization syncstate.preparing=Preparing next item for synchronization syncstate.done=Preparing next item for synchronization syncstate.accountdone=Finished account syncstate.prepare.request.autodiscover=Requesting updated server settings syncstate.send.request.autodiscover=Waiting for updated server settings syncstate.eval.response.autodiscover=Processing updated server settings syncstate.prepare.request.options=Requesting server options syncstate.send.request.options=Waiting for server options syncstate.eval.response.options=Processing server options syncstate.prepare.request.folders=Sending folder list update syncstate.send.request.folders=Waiting for folder list update syncstate.eval.response.folders=Processing folder list update syncstate.prepare.request.synckey=Requesting SyncKey syncstate.send.request.synckey=Waiting for SyncKey syncstate.eval.response.synckey=Processing SyncKey syncstate.prepare.request.deletefolder=Preparing to delete folder syncstate.send.request.deletefolder=Waiting for folder to be deleted syncstate.eval.response.deletefolder=Folder deleted syncstate.prepare.request.provision=Requesting provision syncstate.send.request.provision=Waiting for provision syncstate.eval.response.provision=Processing provision syncstate.prepare.request.estimate=Requesting change estimate syncstate.send.request.estimate=Waiting for change estimate syncstate.eval.response.estimate=Processing change estimate syncstate.prepare.request.remotechanges=Requesting remote changes syncstate.send.request.remotechanges=Waiting for remote changes syncstate.eval.response.remotechanges=Processing remote changes syncstate.prepare.request.localchanges=Sending local changes syncstate.send.request.localchanges=Waiting for acknowledgment of local changes syncstate.eval.response.localchanges=Processing acknowledgment of local changes syncstate.prepare.request.revertlocalchanges=Collecting local changes syncstate.send.request.revertlocalchanges=Waiting for most recent versions syncstate.eval.response.revertlocalchanges=Reverting local changes syncstate.prepare.request.localdeletes=Sending local deletes syncstate.send.request.localdeletes=Waiting for acknowledgment of local deletes syncstate.eval.response.localdeletes=Processing acknowledgment of local deletes status.notsyncronized=Account needs to be synchronized, at least one item is out of sync. status.disabled=Disabled status.notargets=Aborting Sync, because sync targets could not be created. status.nouserhost=Missing username and/or server. Please provide those values. status.timeout=Communication timeout. status.networkerror=Could not connect to server. status.404=User not found (HTTP Error 404). status.403=Server rejected connection (forbidden) (HTTP Error 403). status.401=Could not authenticate, check username and password (HTTP Error 401). status.449=Server requests provisioning (HTTP Error 449). status.500=Unknown Server Error (HTTP Error 500). status.503=Service unavailable (HTTP Error 503). status.httperror=Communication error (HTTP status ##replace.1##). status.empty-response=Server sends unexpected empty response. status.wbxml-parse-error=Server sends unreadable response. status.response-contains-no-data=Response from the server contains no data. status.wbxmlerror=Sync failed. Server responded with status <##replace.1##>. status.wbxmlmissingfield=ActiveSync protocol violation: Mandatory field <##replace.1##> is missing from server response. status.OK=OK status.nolightning=Lightning add-on not installed, calendars are not supported. status.malformed-xml=Could not parse XML. Check event log for details. status.invalid=Invalid server response (junk). status.InvalidServerOptions=The server does not provide information about the supported ActiveSync versions. Is EAS blocked for this user or this client (TbSync)? You could try to set the ActiveSync version manually. status.security=Could not establish a secure connection. Are you using a self-signed or otherwise untrusted certificate without importing it into Thunderbird? (##replace.1##) helplink.security=https://github.com/jobisoft/TbSync/wiki/How-to-use-TbSync-with-self-signed-or-otherwise-untrusted-certificates%3F status.network=Could not connect to server (##replace.1##). status.syncing=Synchronizing status.skipped=Not yet supported, skipped status.aborted=Not synchronized status.pending=Waiting to be synchronized status.modified=Local modifications status.resync-loop=There was an error from which we could not recover by resyncing the account. Please disable the account and try again. (Error: resync loop) status.policy.2=There is no policy for this client. Contact your server administrator or disable provisioning for this account. status.policy.3=Unknown PolicyType value. Contact your server administrator or disable provisioning for this account. status.policy.4=The policy data on the server is corrupted (possibly tampered with). Contact your server administrator or disable provisioning for this account. status.policy.5=The client is acknowledging the wrong policy key. Contact your server administrator or disable provisioning for this account. status.provision=Provisioning failed with status <##replace.1##> status.global.101=The request contains WBXML but it could not be decoded into XML (EAS Error 101). status.global.102=The request contains WBXML but it could not be decoded into XML (EAS Error 102). status.global.103=The XML provided in the request does not follow the protocol requirements (EAS Error 103). status.global.110=The server reported an internal error and we should not retry immediately. Automatic periodic sync has been disabled for 30 minutes (EAS Error 110). status.global.clientdenied=The EAS server reports <##replace.2##> (status ##replace.1##) and does not allow TbSync to access your account. helplink.global.clientdenied=https://github.com/jobisoft/EAS-4-TbSync/wiki/What-if-TbSync-is-blocked-by-my-server%3F status.ServerRejectedSomeItems=The EAS server did not accept ##replace.1## elements. status.ServerRejectedRequest=The EAS Server rejected the last request. status.BadItemSkipped=Bad Item Skipped: ##replace.1## helplink.BadItemSkipped=https://github.com/jobisoft/EAS-4-TbSync/wiki/Error:-Bad-item-skipped status.nosupportedeasversion=Server does not support ActiveSync v2.5 or v14.0 (only ##replace.1##). TbSync will not work with this ActiveSync server. status.notsupportedeasversion=Server does not support selected ActiveSync v##replace.1## (only ##replace.2##). status.Sync.3=Invalid synchronization key (status 3), resyncing status.Sync.4=Malformed request (status 4) status.Sync.5=Temporary server issues or invalid item (status 5) status.Sync.6=Invalid item (status 6) status.Sync.8=Object not found (status 8) status.Sync.12=Folder hierarchy changed (status 12), resyncing status.FolderDelete.3=Cannot delete a system folder (status 3) status.FolderDelete.6=Command could not be completed, an error occurred on the server (status 6) status.FolderDelete.4=Folder does not exist (status 4), resyncing status.FolderDelete.9=Invalid synchronization key (status 9), resyncing status.FolderSync.9=Invalid synchronization key (status 9), resyncing status.forbiddenTasksItemInCalendarFolder=Forbidden task item in a calendar folder (please resort) status.forbiddenCalendarItemInTasksFolder=Forbidden calendar item in a task folder (please resort) config.auto=ActiveSync server configuration (Autodiscover) config.custom=ActiveSync server configuration acl.readwrite=Read from and write to server acl.readonly=Read-only server access (revert local changes) autocomplete.serverdirectory = global server directory EAS-4-TbSync-1.12/_locales/bg/messages.json000066400000000000000000000003351362346370100202670ustar00rootroot00000000000000{ "extensionName": { "message": "Provider for Exchange ActiveSync" }, "extensionDescription": { "message": "Add sync support for Exchange ActiveSync accounts to TbSync (contacts, tasks and calendars)." } }EAS-4-TbSync-1.12/_locales/de/000077500000000000000000000000001362346370100155645ustar00rootroot00000000000000EAS-4-TbSync-1.12/_locales/de/eas.dtd000066400000000000000000000105401362346370100170310ustar00rootroot00000000000000 EAS-4-TbSync-1.12/_locales/de/eas.properties000066400000000000000000000237721362346370100204650ustar00rootroot00000000000000newaccount.add_custom=Konto hinzufügen newaccount.add_auto=Einstellungen suchen und Konto hinzufügen autodiscover.Querying=Einstellungen werden gesucht ... autodiscover.Ok=Autodiscover war erfolgreich, Sie können nun die optionalen Einstellungen überprüfen und die Synchronisationsverbindung aktivieren. autodiscover.NeedEmail=Autodiscover benötigt eine gültige E-Mail Adresse als Benutzername. autodiscover.Failed=Autodiscover für den Benutzer <##user##> war nicht erfolgreich. Entweder war das Passwort nicht korrekt, Ihr ActiveSync Server hat ein temporäres Problem oder unterstützt kein Autodiscover. deletefolder.notallowed=Bitte beenden Sie zunächst das Abonement des Ordner "##replace.1##", bevor Sie diesen aus dem Papierkorb entfernen. deletefolder.confirm=Möchten Sie den Ordner "##replace.1##" wirklich ENDGÜLTIG aus dem Papierkorb ENTFERNEN? deletefolder.menuentry=Den Ordner "##replace.1##" endgültig aus dem Papierkorb entfernen recyclebin = Papierkorb syncstate.prepare.request.setdeviceinfo=Sende Geräteinformationen syncstate.send.request.setdeviceinfo=Sende Geräteinformationen syncstate.eval.response.setdeviceinfo=Sende Geräteinformationen syncstate.syncing=Initiiere Synchronisation syncstate.preparing=Bereite das nächste Element für die Synchronisation vor syncstate.done=Bereite das nächste Element für die Synchronisation vor syncstate.accountdone=Kontosynchronisation abgeschlossen syncstate.prepare.request.autodiscover=Sende Anfrage bzgl. aktualisierter Servereinstellungen syncstate.send.request.autodiscover=Warte auf aktualisierte Servereinstellungen syncstate.eval.response.autodiscover=Verarbeite aktualisierte Servereinstellungen syncstate.prepare.request.options=Sende Anfrage bzgl. aktualisierter Serveroptionen syncstate.send.request.options=Warte auf aktualisierte Serveroptionen syncstate.eval.response.options=Verarbeite aktualisierte Serveroptionen syncstate.prepare.request.folders=Sende Anfrage bzgl. aktualisierter Ordnerliste syncstate.send.request.folders=Warte auf aktualisierte Ordnerliste syncstate.eval.response.folders=Verarbeite aktualisierte Ordnerliste syncstate.prepare.request.synckey=Sende Anfrage bzgl. Synchronisationsschlüssel syncstate.send.request.synckey=Warte auf Synchronisationsschlüssel syncstate.eval.response.synckey=Verarbeite Synchronisationsschlüssel syncstate.prepare.request.deletefolder=Löschen des Ordners wird vorbereitet syncstate.send.request.deletefolder=Ordner wird gelöscht syncstate.eval.response.deletefolder=Ordner erfolgreich gelöscht syncstate.prepare.request.provision=Sende Anfrage bzgl. Provisionierung syncstate.send.request.provision=Warte auf Provisionierung syncstate.eval.response.provision=Verarbeite Provisionierung syncstate.prepare.request.estimate=Sende Anfrage bzgl. geschätzter Änderungen syncstate.send.request.estimate=Warte auf geschätzte Änderungen syncstate.eval.response.estimate=Verarbeite geschätzte Änderungen syncstate.prepare.request.remotechanges=Sende Anfrage bzgl. Änderungen syncstate.send.request.remotechanges=Warte auf aktualisierte Elemente syncstate.eval.response.remotechanges=Verarbeite aktualisierte Elemente syncstate.prepare.request.localchanges=Sende lokale Änderungen syncstate.send.request.localchanges=Warte auf Bestätigung der lokalen Änderungen syncstate.eval.response.localchanges=Verarbeite Bestätigung der lokalen Änderungen syncstate.prepare.request.revertlocalchanges=Prüfe lokale Änderungen syncstate.send.request.revertlocalchanges=Warte auf aktuelle Versionen syncstate.eval.response.revertlocalchanges=Lokale Änderungen werden verworfen syncstate.prepare.request.localdeletes=Sende die lokal gelöschten Elemente syncstate.send.request.localdeletes=Warte auf Bestätigung lokal gelöschten Elemente syncstate.eval.response.localdeletes=Verarbeite Bestätigung lokal gelöschten Elemente status.notsyncronized=Konto muss synchronisiert werden. status.disabled=Konto ist deaktiviert, Synchronisation ist ausgeschaltet. status.notargets=Synchronisation abgebrochen da die Elemente zum Synchronisieren nicht erstellt werden konnten. status.nouserhost=Angabe des Benutzernamens und/oder des Servers fehlt. Bitte die korrekten Informationen eintragen. status.timeout=Kommunikations-Timeout. status.networkerror=Verbindung zum Server fehlgeschlagen. status.404=Unbekanner Benutzer (HTTP Fehler 404). status.403=Verbindung vom Server abgelehnt (nicht erlaubt). status.401=Authentifizierung fehlgeschlagen, überprüfen Sie den Benutzernamen und das Passwort. status.449=Server erwartet Provisionierung. status.500=Unbekannter Server Fehler (HTTP Fehler 500). status.503=Service nicht erreichbar. status.httperror=Kommunikationsfehler (HTTP Status ##replace.1##). status.empty-response=Server sendet unerwartete leere Antwort. status.wbxml-parse-error=Server sendet nicht lesbare Antwort. status.response-contains-no-data=Antwort vom Server enthält keine Daten. status.wbxmlerror=Synchronisation fehlgeschlagen. Der Server antwortete mit dem Status <##replace.1##>. status.wbxmlmissingfield=Verletzung des ActiveSync Protokolls: Notwendiges Feld <##replace.1##> ist nicht in der Serverantwort enthalten. status.OK=Ok status.nolightning=Lightning Add-On nicht installiert, Kalender nicht nutzbar. status.malformed-xml=Antwort enthält fehlerhaftes XML, Sync angebrochen. Prüfen Sie bitte das Ereignisprotokoll für weitere Details. status.invalid=Ungültige Serverantwort. status.InvalidServerOptions=Der Server sendet keine Informationen zu den unterstützten ActiveSync Versionen. Ist EAS für diesen Benutzer bzw. für dieses Programm (TbSync) freigeschaltet? Sie können versuchen, die ActiveSync Version manuell festzulegen. status.security=Fehler beim Aufbau einer sicherern Verbindung. Benutzen Sie eventuell ein selbst signiertes Zertifikat oder ein andersartiges nicht vertrauenswürdiges Zertifikat welches nicht in Thunderbird importiert ist? (##replace.1##) helplink.security=https://github.com/jobisoft/TbSync/wiki/How-to-use-TbSync-with-self-signed-or-otherwise-untrusted-certificates%3F status.network=Verbindung zum Server fehlgeschlagen (##replace.1##). status.syncing=Synchronisierung status.skipped=Nicht unterstützt status.aborted=Nicht synchronisiert status.pending=Warten auf Synchronisation status.modified=Lokale Änderungen status.resync-loop=Es ist ein Fehler aufgetreten, der nicht durch wiederholtes Synchronisieren des Kontos behoben werden konnte. Bitte deaktivieren sie das Konto und wiederholen den Vorgang. (Fehler: Synchronisationsschleife) status.policy.2=Es gibt keine Regel (policy) für diesen Klient. Kontaktieren Sie Ihren Server Administrator oder deaktivieren Sie die Provisionierungsoption für dieses Konto in TbSync. status.policy.3=Unbekannter Wert für den Regeltyp (policy type). Kontaktieren Sie Ihren Server Administrator oder deaktivieren Sie die Provisionierungsoption für dieses Konto in TbSync. status.policy.4=Die Daten für die Regeln auf dem Server sind beschädigt (möglicherweise manipuliert). Kontaktieren Sie Ihren Server Administrator oder deaktivieren Sie die Provisionierungsoption für dieses Konto in TbSync. status.policy.5=Der Klient bestätigt einen falschen Richtlinienschlüssel (policy key). Kontaktieren Sie Ihren Server Administrator oder deaktivieren Sie die Provisionierungsoption für dieses Konto in TbSync. status.provision=Provisionierung fehlerhaft mit folgendem Status <##replace.1##> status.global.101=Das WBXML der Anfrage konnte nicht in gültiges XML dekodiert werden (EAS Fehler 101). status.global.102=Das WBXML der Anfrage konnte nicht in gültiges XML dekodiert werden (EAS Fehler 102). status.global.103=Das XML der Anfrage entspricht nicht den Protokollanforderungen (EAS Fehler 103). status.global.110=Der Server meldet einen internen Fehler und es soll nicht unmittelbar ein erneuter Verbindungsaufbau durchgeführt werden. Die automatische periodische Synchronisation wird für 30 Minuten ausgesetzt (EAS Fehler 110). status.global.clientdenied=Der EAS Server antwortet mit <##replace.2##> (status ##replace.1##) und verweigert TbSync den Zugriff auf Ihr Konto. helplink.global.clientdenied=https://github.com/jobisoft/EAS-4-TbSync/wiki/What-if-TbSync-is-blocked-by-my-server%3F status.ServerRejectedSomeItems=Der EAS Server hat ##replace.1## Elemente nicht akzeptiert. status.ServerRejectedRequest=Der EAS Server hat die letzte Anforderung zurückgewiesen. status.BadItemSkipped=Ein Element wurde nicht synchronisiert: ##replace.1## helplink.BadItemSkipped=https://github.com/jobisoft/EAS-4-TbSync/wiki/Error:-Bad-item-skipped status.nosupportedeasversion=Der Server unterstützt kein ActiveSync v2.5 oder v14.0 (nur ##replace.1##). TbSync kann nicht mit diesem Server arbeiten. status.notsupportedeasversion=Der Server unterstützt nicht die ausgewählte ActiveSync Version v##replace.1## (nur ##replace.2##). status.Sync.3=Ungültiger Synchronisationsschlüssel, es wird neu synchronisiert status.Sync.4=Fehlerhafte Anfrage (Status 4) status.Sync.5=Temporäres Serverproblem oder ungültiges Element (Status 5) status.Sync.6=Ungültiges Element (Status 6) status.Sync.8=Objekt nicht gefunden (Status 8) status.Sync.12=Ordnerhierarchie hat sich geändert, es wird neu synchronisiert status.FolderDelete.3=Systemordner können nicht gelöscht werden. status.FolderDelete.6=Das Kommando konnte nicht abgeschlossen werden, auf dem Server ist ein Fehler aufgetreten. status.FolderDelete.4=Ordner existiert nicht, es wird neu synchronisiert status.FolderDelete.9=Ungültiger Synchronisationsschlüssel, es wird neu synchronisiert status.FolderSync.9=Ungültiger Synchronisationsschlüssel, es wird neu synchronisiert status.forbiddenTasksItemInCalendarFolder=Unerlaubtes Aufgaben-Element in einem Kalender-Objekt (bitte umsortieren) status.forbiddenCalendarItemInTasksFolder=Unerlaubtes Kalender-Element in einem Aufgaben-Objekt (bitte umsortieren) config.auto=ActiveSync Server Konfiguration (Autodiscover) config.custom=ActiveSync Server Konfiguration acl.readwrite=Serverzugriff lesend und schreibend acl.readonly=Serverzugriff nur lesend (verwerfe lokale Änderungen) autocomplete.serverdirectory = Globales Serververzeichnis EAS-4-TbSync-1.12/_locales/de/messages.json000066400000000000000000000003641362346370100202710ustar00rootroot00000000000000{ "extensionName": { "message": "Provider für Exchange ActiveSync" }, "extensionDescription": { "message": "Erweitert TbSync und erlaubt die Synchronisation von Exchange ActiveSync Konten (Kontakte, Aufgaben und Kalender)." } }EAS-4-TbSync-1.12/_locales/en-US/000077500000000000000000000000001362346370100161235ustar00rootroot00000000000000EAS-4-TbSync-1.12/_locales/en-US/eas.dtd000066400000000000000000000103071362346370100173710ustar00rootroot00000000000000 EAS-4-TbSync-1.12/_locales/en-US/eas.properties000066400000000000000000000210621362346370100210120ustar00rootroot00000000000000newaccount.add_custom=Add account newaccount.add_auto=Autodiscover settings and add account autodiscover.Querying=Searching for settings… autodiscover.Ok=Autodiscover completed successfully, you can now check the optional settings and establish the synchronization connection. autodiscover.NeedEmail=Autodiscover needs a valid email address as user name. autodiscover.Failed=Autodiscover for user <##user##> failed. Either the provided credentials were wrong or your ActiveSync provider has a temporary issue, or does not support Autodiscover. deletefolder.notallowed=Please unsubscribe folder “##replace.1##” before trying to purge it from trash. deletefolder.confirm=Do you really want to PERMANENTLY PURGE folder “##replace.1##” from trash? deletefolder.menuentry=Permanently purge folder “##replace.1##” from trash recyclebin = Trash syncstate.prepare.request.setdeviceinfo=Sending device information syncstate.send.request.setdeviceinfo=Sending device information syncstate.eval.response.setdeviceinfo=Sending device information syncstate.syncing=Initialize synchronization syncstate.preparing=Preparing next item for synchronization syncstate.done=Preparing next item for synchronization syncstate.accountdone=Finished account syncstate.prepare.request.autodiscover=Requesting updated server settings syncstate.send.request.autodiscover=Waiting for updated server settings syncstate.eval.response.autodiscover=Processing updated server settings syncstate.prepare.request.options=Requesting server options syncstate.send.request.options=Waiting for server options syncstate.eval.response.options=Processing server options syncstate.prepare.request.folders=Sending folder list update syncstate.send.request.folders=Waiting for folder list update syncstate.eval.response.folders=Processing folder list update syncstate.prepare.request.synckey=Requesting SyncKey syncstate.send.request.synckey=Waiting for SyncKey syncstate.eval.response.synckey=Processing SyncKey syncstate.prepare.request.deletefolder=Preparing to delete folder syncstate.send.request.deletefolder=Waiting for folder to be deleted syncstate.eval.response.deletefolder=Folder deleted syncstate.prepare.request.provision=Requesting provision syncstate.send.request.provision=Waiting for provision syncstate.eval.response.provision=Processing provision syncstate.prepare.request.estimate=Requesting change estimate syncstate.send.request.estimate=Waiting for change estimate syncstate.eval.response.estimate=Processing change estimate syncstate.prepare.request.remotechanges=Requesting remote changes syncstate.send.request.remotechanges=Waiting for remote changes syncstate.eval.response.remotechanges=Processing remote changes syncstate.prepare.request.localchanges=Sending local changes syncstate.send.request.localchanges=Waiting for acknowledgment of local changes syncstate.eval.response.localchanges=Processing acknowledgment of local changes syncstate.prepare.request.revertlocalchanges=Collecting local changes syncstate.send.request.revertlocalchanges=Waiting for most recent versions syncstate.eval.response.revertlocalchanges=Reverting local changes syncstate.prepare.request.localdeletes=Sending local deletes syncstate.send.request.localdeletes=Waiting for acknowledgment of local deletes syncstate.eval.response.localdeletes=Processing acknowledgment of local deletes status.notsyncronized=Account needs to be synchronized, at least one item is out of sync. status.disabled=Disabled status.notargets=Aborting Sync, because sync targets could not be created. status.nouserhost=Missing username and/or server. Please provide those values. status.timeout=Communication timeout. status.networkerror=Could not connect to server. status.404=User not found (HTTP Error 404). status.403=Server rejected connection (forbidden) (HTTP Error 403). status.401=Could not authenticate, check username and password (HTTP Error 401). status.449=Server requests provisioning (HTTP Error 449). status.500=Unknown Server Error (HTTP Error 500). status.503=Service unavailable (HTTP Error 503). status.httperror=Communication error (HTTP status ##replace.1##). status.empty-response=Server sends unexpected empty response. status.wbxml-parse-error=Server sends unreadable response. status.response-contains-no-data=Response from the server contains no data. status.wbxmlerror=Sync failed. Server responded with status <##replace.1##>. status.wbxmlmissingfield=ActiveSync protocol violation: Mandatory field <##replace.1##> is missing from server response. status.OK=OK status.nolightning=Lightning add-on not installed, calendars are not supported. status.malformed-xml=Could not parse XML. Check event log for details. status.invalid=Invalid server response (junk). status.InvalidServerOptions=The server does not provide information about the supported ActiveSync versions. Is EAS blocked for this user or this client (TbSync)? You could try to set the ActiveSync version manually. status.security=Could not establish a secure connection. Are you using a self-signed or otherwise untrusted certificate without importing it into Thunderbird? (##replace.1##) helplink.security=https://github.com/jobisoft/TbSync/wiki/How-to-use-TbSync-with-self-signed-or-otherwise-untrusted-certificates%3F status.network=Could not connect to server (##replace.1##). status.syncing=Synchronizing status.skipped=Not yet supported, skipped status.aborted=Not synchronized status.pending=Waiting to be synchronized status.modified=Local modifications status.resync-loop=There was an error from which we could not recover by resyncing the account. Please disable the account and try again. (Error: resync loop) status.policy.2=There is no policy for this client. Contact your server administrator or disable provisioning for this account. status.policy.3=Unknown PolicyType value. Contact your server administrator or disable provisioning for this account. status.policy.4=The policy data on the server is corrupted (possibly tampered with). Contact your server administrator or disable provisioning for this account. status.policy.5=The client is acknowledging the wrong policy key. Contact your server administrator or disable provisioning for this account. status.provision=Provisioning failed with status <##replace.1##> status.global.101=The request contains WBXML but it could not be decoded into XML (EAS Error 101). status.global.102=The request contains WBXML but it could not be decoded into XML (EAS Error 102). status.global.103=The XML provided in the request does not follow the protocol requirements (EAS Error 103). status.global.110=The server reported an internal error and we should not retry immediately. Automatic periodic sync has been disabled for 30 minutes (EAS Error 110). status.global.clientdenied=The EAS server reports <##replace.2##> (status ##replace.1##) and does not allow TbSync to access your account. helplink.global.clientdenied=https://github.com/jobisoft/EAS-4-TbSync/wiki/What-if-TbSync-is-blocked-by-my-server%3F status.ServerRejectedSomeItems=The EAS server did not accept ##replace.1## elements. status.ServerRejectedRequest=The EAS Server rejected the last request. status.BadItemSkipped=Bad Item Skipped: ##replace.1## helplink.BadItemSkipped=https://github.com/jobisoft/EAS-4-TbSync/wiki/Error:-Bad-item-skipped status.nosupportedeasversion=Server does not support ActiveSync v2.5 or v14.0 (only ##replace.1##). TbSync will not work with this ActiveSync server. status.notsupportedeasversion=Server does not support selected ActiveSync v##replace.1## (only ##replace.2##). status.Sync.3=Invalid synchronization key (status 3), resyncing status.Sync.4=Malformed request (status 4) status.Sync.5=Temporary server issues or invalid item (status 5) status.Sync.6=Invalid item (status 6) status.Sync.8=Object not found (status 8) status.Sync.12=Folder hierarchy changed (status 12), resyncing status.FolderDelete.3=Cannot delete a system folder (status 3) status.FolderDelete.6=Command could not be completed, an error occurred on the server (status 6) status.FolderDelete.4=Folder does not exist (status 4), resyncing status.FolderDelete.9=Invalid synchronization key (status 9), resyncing status.FolderSync.9=Invalid synchronization key (status 9), resyncing status.forbiddenTasksItemInCalendarFolder=Forbidden task item in a calendar folder (please resort) status.forbiddenCalendarItemInTasksFolder=Forbidden calendar item in a task folder (please resort) config.auto=ActiveSync server configuration (Autodiscover) config.custom=ActiveSync server configuration acl.readwrite=Read from and write to server acl.readonly=Read-only server access (revert local changes) autocomplete.serverdirectory = global server directory EAS-4-TbSync-1.12/_locales/en-US/messages.json000066400000000000000000000003361362346370100206270ustar00rootroot00000000000000{ "extensionName": { "message": "Provider for Exchange ActiveSync" }, "extensionDescription": { "message": "Add sync support for Exchange ActiveSync accounts to TbSync (contacts, tasks and calendars)." } } EAS-4-TbSync-1.12/_locales/fr/000077500000000000000000000000001362346370100156035ustar00rootroot00000000000000EAS-4-TbSync-1.12/_locales/fr/eas.dtd000066400000000000000000000113221362346370100170470ustar00rootroot00000000000000 EAS-4-TbSync-1.12/_locales/fr/eas.properties000066400000000000000000000210701362346370100204710ustar00rootroot00000000000000newaccount.add_custom=Ajouter un compte newaccount.add_auto=Autodiscover settings and add account autodiscover.Querying=Searching for settings… autodiscover.Ok=Autodiscover completed successfully, you can now check the optional settings and establish the synchronization connection. autodiscover.NeedEmail=Autodiscover needs a valid email address as user name. autodiscover.Failed=Autodiscover for user <##user##> failed. Either the provided credentials were wrong or your ActiveSync provider has a temporary issue, or does not support Autodiscover. deletefolder.notallowed=Please unsubscribe folder “##replace.1##” before trying to purge it from trash. deletefolder.confirm=Do you really want to PERMANENTLY PURGE folder “##replace.1##” from trash? deletefolder.menuentry=Permanently purge folder “##replace.1##” from trash recyclebin = Trash syncstate.prepare.request.setdeviceinfo=Sending device information syncstate.send.request.setdeviceinfo=Sending device information syncstate.eval.response.setdeviceinfo=Sending device information syncstate.syncing=Initialize synchronization syncstate.preparing=Preparing next item for synchronization syncstate.done=Preparing next item for synchronization syncstate.accountdone=Finished account syncstate.prepare.request.autodiscover=Requesting updated server settings syncstate.send.request.autodiscover=Waiting for updated server settings syncstate.eval.response.autodiscover=Processing updated server settings syncstate.prepare.request.options=Requesting server options syncstate.send.request.options=Waiting for server options syncstate.eval.response.options=Processing server options syncstate.prepare.request.folders=Sending folder list update syncstate.send.request.folders=Waiting for folder list update syncstate.eval.response.folders=Processing folder list update syncstate.prepare.request.synckey=Requesting SyncKey syncstate.send.request.synckey=Waiting for SyncKey syncstate.eval.response.synckey=Processing SyncKey syncstate.prepare.request.deletefolder=Preparing to delete folder syncstate.send.request.deletefolder=Waiting for folder to be deleted syncstate.eval.response.deletefolder=Folder deleted syncstate.prepare.request.provision=Requesting provision syncstate.send.request.provision=Waiting for provision syncstate.eval.response.provision=Processing provision syncstate.prepare.request.estimate=Requesting change estimate syncstate.send.request.estimate=Waiting for change estimate syncstate.eval.response.estimate=Processing change estimate syncstate.prepare.request.remotechanges=Requesting remote changes syncstate.send.request.remotechanges=Waiting for remote changes syncstate.eval.response.remotechanges=Processing remote changes syncstate.prepare.request.localchanges=Sending local changes syncstate.send.request.localchanges=Waiting for acknowledgment of local changes syncstate.eval.response.localchanges=Processing acknowledgment of local changes syncstate.prepare.request.revertlocalchanges=Collecting local changes syncstate.send.request.revertlocalchanges=Waiting for most recent versions syncstate.eval.response.revertlocalchanges=Reverting local changes syncstate.prepare.request.localdeletes=Sending local deletes syncstate.send.request.localdeletes=Waiting for acknowledgment of local deletes syncstate.eval.response.localdeletes=Processing acknowledgment of local deletes status.notsyncronized=Account needs to be synchronized, at least one item is out of sync. status.disabled=Disabled status.notargets=Aborting Sync, because sync targets could not be created. status.nouserhost=Missing username and/or server. Please provide those values. status.timeout=Communication timeout. status.networkerror=Could not connect to server. status.404=User not found (HTTP Error 404). status.403=Server rejected connection (forbidden) (HTTP Error 403). status.401=Could not authenticate, check username and password (HTTP Error 401). status.449=Server requests provisioning (HTTP Error 449). status.500=Unknown Server Error (HTTP Error 500). status.503=Service unavailable (HTTP Error 503). status.httperror=Communication error (HTTP status ##replace.1##). status.empty-response=Server sends unexpected empty response. status.wbxml-parse-error=Server sends unreadable response. status.response-contains-no-data=Response from the server contains no data. status.wbxmlerror=Sync failed. Server responded with status <##replace.1##>. status.wbxmlmissingfield=ActiveSync protocol violation: Mandatory field <##replace.1##> is missing from server response. status.OK=OK status.nolightning=Lightning add-on not installed, calendars are not supported. status.malformed-xml=Could not parse XML. Check event log for details. status.invalid=Invalid server response (junk). status.InvalidServerOptions=The server does not provide information about the supported ActiveSync versions. Is EAS blocked for this user or this client (TbSync)? You could try to set the ActiveSync version manually. status.security=Could not establish a secure connection. Are you using a self-signed or otherwise untrusted certificate without importing it into Thunderbird? (##replace.1##) helplink.security=https://github.com/jobisoft/TbSync/wiki/How-to-use-TbSync-with-self-signed-or-otherwise-untrusted-certificates%3F status.network=Could not connect to server (##replace.1##). status.syncing=Synchronizing status.skipped=Not yet supported, skipped status.aborted=Not synchronized status.pending=Waiting to be synchronized status.modified=Local modifications status.resync-loop=There was an error from which we could not recover by resyncing the account. Please disable the account and try again. (Error: resync loop) status.policy.2=There is no policy for this client. Contact your server administrator or disable provisioning for this account. status.policy.3=Unknown PolicyType value. Contact your server administrator or disable provisioning for this account. status.policy.4=The policy data on the server is corrupted (possibly tampered with). Contact your server administrator or disable provisioning for this account. status.policy.5=The client is acknowledging the wrong policy key. Contact your server administrator or disable provisioning for this account. status.provision=Provisioning failed with status <##replace.1##> status.global.101=The request contains WBXML but it could not be decoded into XML (EAS Error 101). status.global.102=The request contains WBXML but it could not be decoded into XML (EAS Error 102). status.global.103=The XML provided in the request does not follow the protocol requirements (EAS Error 103). status.global.110=The server reported an internal error and we should not retry immediately. Automatic periodic sync has been disabled for 30 minutes (EAS Error 110). status.global.clientdenied=The EAS server reports <##replace.2##> (status ##replace.1##) and does not allow TbSync to access your account. helplink.global.clientdenied=https://github.com/jobisoft/EAS-4-TbSync/wiki/What-if-TbSync-is-blocked-by-my-server%3F status.ServerRejectedSomeItems=The EAS server did not accept ##replace.1## elements. status.ServerRejectedRequest=The EAS Server rejected the last request. status.BadItemSkipped=Bad Item Skipped: ##replace.1## helplink.BadItemSkipped=https://github.com/jobisoft/EAS-4-TbSync/wiki/Error:-Bad-item-skipped status.nosupportedeasversion=Server does not support ActiveSync v2.5 or v14.0 (only ##replace.1##). TbSync will not work with this ActiveSync server. status.notsupportedeasversion=Server does not support selected ActiveSync v##replace.1## (only ##replace.2##). status.Sync.3=Invalid synchronization key (status 3), resyncing status.Sync.4=Malformed request (status 4) status.Sync.5=Temporary server issues or invalid item (status 5) status.Sync.6=Invalid item (status 6) status.Sync.8=Object not found (status 8) status.Sync.12=Folder hierarchy changed (status 12), resyncing status.FolderDelete.3=Cannot delete a system folder (status 3) status.FolderDelete.6=Command could not be completed, an error occurred on the server (status 6) status.FolderDelete.4=Folder does not exist (status 4), resyncing status.FolderDelete.9=Invalid synchronization key (status 9), resyncing status.FolderSync.9=Invalid synchronization key (status 9), resyncing status.forbiddenTasksItemInCalendarFolder=Forbidden task item in a calendar folder (please resort) status.forbiddenCalendarItemInTasksFolder=Forbidden calendar item in a task folder (please resort) config.auto=ActiveSync server configuration (Autodiscover) config.custom=ActiveSync server configuration acl.readwrite=Read from and write to server acl.readonly=Read-only server access (revert local changes) autocomplete.serverdirectory = global server directory EAS-4-TbSync-1.12/_locales/fr/messages.json000066400000000000000000000004041362346370100203030ustar00rootroot00000000000000{ "extensionName": { "message": "Fournisseur pour Exchange ActiveSync" }, "extensionDescription": { "message": "Ajouter la prise en charge de la synchronisation des comptes Exchange ActiveSync à TbSync (contacts, tâches et calendriers)." } } EAS-4-TbSync-1.12/_locales/hu/000077500000000000000000000000001362346370100156105ustar00rootroot00000000000000EAS-4-TbSync-1.12/_locales/hu/eas.dtd000066400000000000000000000110611362346370100170540ustar00rootroot00000000000000 EAS-4-TbSync-1.12/_locales/hu/eas.properties000066400000000000000000000240021362346370100204740ustar00rootroot00000000000000newaccount.add_custom=Fiók hozzáadása newaccount.add_auto=Az önműködő felismerés beállításai és fiók hozzáadása autodiscover.Querying=Beállítások keresése… autodiscover.Ok=Az önműködő felderítés sikeresen befejeződött, most ellenőrizheti az választható beállításokat és létrehozhatja az összehangolási kapcsolatot. autodiscover.NeedEmail=Az önműködő felismerésnek érvényes e-mail címnek kell lennie felhasználói névként. autodiscover.Failed=A felhasználó <##user##> önműködő felderítése sikertelen. Vagy a megadott hitelesítő adatok hibásak voltak, vagy az ActiveSync szolgáltató ideiglenes problémát okozott, vagy nem támogatja az önműködő felderítést. deletefolder.notallowed=Kérjük, törölje le a(z) „##replace.1##” mappát, mielőtt megpróbálnánk kuka ürítése. deletefolder.confirm=Tényleg azt szeretné, hogy a(z) „##replace.1##” mappa véglegesen törölje a kukát? deletefolder.menuentry=„##replace.1##” mappa kuka ürítése recyclebin = Kuka syncstate.prepare.request.setdeviceinfo=Eszköz tájékoztatás küldése syncstate.send.request.setdeviceinfo=Eszköz tájékoztatás küldése syncstate.eval.response.setdeviceinfo=Eszköz tájékoztatás küldése syncstate.syncing=Összehangolás kezdése syncstate.preparing=Készítse elő a következő elemet az összehangolásához syncstate.done=Készítse elő a következő elemet az összehangolásához syncstate.accountdone=Fiók kész syncstate.prepare.request.autodiscover=Frissített kiszolgáló beállítások kérése syncstate.send.request.autodiscover=Várakozás a frissített kiszolgáló beállításokra syncstate.eval.response.autodiscover=Frissített kiszolgáló beállítások feldolgozása syncstate.prepare.request.options=A kiszolgáló beállításainak megkeresése syncstate.send.request.options=Várakozás a kiszolgáló beállításaira syncstate.eval.response.options=A kiszolgáló beállításainak feldolgozása syncstate.prepare.request.folders=A mappalisták frissítés elküldése syncstate.send.request.folders=Várakozás a mappa listájának frissítésére syncstate.eval.response.folders=A mappalista frissítés feldolgozása syncstate.prepare.request.synckey=Összehangoláskulcs kérése syncstate.send.request.synckey=Várakozás az összehangoláskulcsra syncstate.eval.response.synckey=Összehangoláskulcs feldolgozása syncstate.prepare.request.deletefolder=Előkészület a mappa törlésére syncstate.send.request.deletefolder=Várakozás a mappa törlésére syncstate.eval.response.deletefolder=A mappa törölve syncstate.prepare.request.provision=A rendelkezés igénylése syncstate.send.request.provision=Várakozás a rendelkezésre syncstate.eval.response.provision=Feldolgozási rendelkezés syncstate.prepare.request.estimate=Módosítási becslés kérése syncstate.send.request.estimate=Várakozás a becslés megváltoztatására syncstate.eval.response.estimate=Feldolgozási becslés változása syncstate.prepare.request.remotechanges=Távoli módosítások kérése syncstate.send.request.remotechanges=Várakozás a távoli módosításokra syncstate.eval.response.remotechanges=A távoli módosítások feldolgozása syncstate.prepare.request.localchanges=Helyi változások küldése syncstate.send.request.localchanges=Várakozás a helyi változások elismerésére syncstate.eval.response.localchanges=A helyi változások elismerésének feldolgozása syncstate.prepare.request.revertlocalchanges=Helyi változások gyűjtése syncstate.send.request.revertlocalchanges=Várja a legfrissebb verziókat syncstate.eval.response.revertlocalchanges=A helyi változások visszaállítása syncstate.prepare.request.localdeletes=Helyi törlések küldése syncstate.send.request.localdeletes=Várakozás a helyi törlések nyugtázására syncstate.eval.response.localdeletes=A helyi törlések feldolgozása status.notsyncronized=A fiókot összehangolni kell, legalább egy elem nem összehangolva van. status.disabled=A fiók nincs engedélyezve, az összehangolás le van tiltva. status.notargets=Az összehangolás megszakítása, mert a szinkronizálási célokat nem lehetett létrehozni. status.nouserhost=Hiányzik a felhasználónév és/vagy a kiszolgáló név. Kérjük, adja meg ezeket az értékeket. status.timeout=Közlés időtúllépés. status.networkerror=Nem tudott csatlakozni a kiszolgálóhoz. status.404=A felhasználó nem található (404-es HTTP-hiba). status.403=Kiszolgáló elutasította a kapcsolatot (tiltott) (403-ás HTTP-hiba). status.401=Nem sikerült hitelesíteni, ellenőrizni a felhasználónevet és a jelszót (401-es HTTP-hiba). status.449=Kiszolgáló kérések készítése (449-es HTTP-hiba). status.500=Ismeretlen kiszolgálóhiba (500-as HTTP-hiba). status.503=A szolgáltatás nem elérhető (503-ás HTTP-hiba). status.httperror=Közlés hiba (HTTP állapot ##replace.1##). status.empty-response=A kiszolgáló váratlan üres választ küld. status.wbxml-parse-error=A kiszolgáló olvashatatlan választ küld. status.response-contains-no-data=A szerverre adott válasz nem tartalmaz adatokat. status.wbxmlerror=A összehangolás sikertelen. A kiszolgáló <##replace.1##> állapotban válaszolt. status.wbxmlmissingfield=ActiveSync egyezmény megsértése: A(z) <##replace.1##> kötelező mező hiányzik a kiszolgáló válaszából. status.OK=Rendben van status.nolightning=A Lightning kiegészítő nincs telepítve, a naptárak nem támogatottak. status.malformed-xml=Nem sikerült elemezni az XML-t. Ellenőrizze az eseménynaplót a részletekért. status.invalid=Érvénytelen kiszolgáló válasz (szemét). status.InvalidServerOptions=A kiszolgáló nem nyújt felvilágosítást a támogatott ActiveSync verziókról. EAS akadályozva van ez a felhasználó vagy az ügyfél (Thunderbird-összehangolás) számára? Megpróbálhatja kézzel beállítani az ActiveSync verziót. status.security=Biztonsági kapcsolat létrehozása nem sikerült. Ön aláírta vagy egyébként nem megbízható tanúsítványt importálta a Thunderbirdbe? (##replace.1##) helplink.security=https://github.com/jobisoft/TbSync/wiki/How-to-use-TbSync-with-self-signed-or-otherwise-untrusted-certificates%3F status.network=Nem tudott csatlakozni a kiszolgálóhoz (##replace.1##). status.syncing=Összehangol status.skipped=Még nem támogatott, kihagyva status.aborted=Nem összehangolva status.pending=Várakozás összehangolásra status.modified=Helyi módosítások status.resync-loop=Hiba történt, amelyből nem sikerült visszaszerezni a fiók újbóli létrehozásával. Kérjük, tiltsd le a fiókot, és próbálkozz újra. (Hiba: újra összehangolja hurok) status.policy.2=Az ügyfél számára nincs irányelv (Policy). Vegye fel a kapcsolatot a kiszolgáló rendszergazdájával, vagy tiltsa le a fiókhoz való hozzáférést. status.policy.3=Ismeretlen irányelvtípus (PolicyType) érték. Vegye fel a kapcsolatot a kiszolgáló rendszergazdájával, vagy tiltsa le a fiókhoz való hozzáférést. status.policy.4=A kiszolgáló házirend-adatai sérültek (esetleg meghamisították). Vegye fel a kapcsolatot a kiszolgáló rendszergazdájával, vagy tiltsa le a fiókhoz való hozzáférést. status.policy.5=Az ügyfél elismeri a rossz irányelv kulcsot (PolicyKey). Vegye fel a kapcsolatot a kiszolgáló rendszergazdájával, vagy tiltsa le a fiókhoz való hozzáférést. status.provision=A hozzáférést nem sikerült, állapota <##replace.1##> status.global.101=A kérelem WBXML-t tartalmaz, de XML-ben nem sikerült dekódolni (101-es HTTP-hiba). status.global.102=A kérelem WBXML-t tartalmaz, de XML-ben nem sikerült dekódolni (102-es HTTP-hiba). status.global.103=A kérésben megadott XML nem követi a egyezménykövetelményeket (103-ás HTTP-hiba). status.global.110=The server reported an internal error and we should not retry immediately. Automatic periodic sync has been disabled for 30 minutes (EAS Error 110). status.global.clientdenied=Az EAS kiszolgáló jelentése <##replace.2##> (állapota ##replace.1##), és nem engedélyezi a Thunderbird-összehangolás számára a fiók elérését. helplink.global.clientdenied=https://github.com/jobisoft/EAS-4-TbSync/wiki/What-if-TbSync-is-blocked-by-my-server%3F status.ServerRejectedSomeItems=Az EAS-kiszolgáló nem fogadta el a ##replace.1## elemeket. status.ServerRejectedRequest=Az EAS szerver megtagadta az utolsó kérésünket. status.BadItemSkipped=Bad Item Skipped: ##replace.1## helplink.BadItemSkipped=https://github.com/jobisoft/EAS-4-TbSync/wiki/Error:-Bad-item-skipped status.nosupportedeasversion=A kiszolgáló nem támogatja az ActiveSync 2.5 vagy 14.0 verziót (csak ##replace.1##). A Thunderbird-összehangolás nem fog működni az ActiveSync kiszolgálóval. status.notsupportedeasversion=A kiszolgáló nem támogatja a kiválasztott ActiveSync ##replace.1## verziót (csak ##replace.2## verzió). status.Sync.3=?? Invalid synchronization key (status 3), resyncing ?? status.Sync.4=?? Malformed request (status 4) ?? status.Sync.5=?? Temporary server issues or invalid item (status 5) ?? status.Sync.6=?? Invalid item (status 6) ?? status.Sync.8=?? Object not found (status 8) ?? status.Sync.12=?? Folder hierarchy changed (status 12), resyncing ?? status.FolderDelete.3=Nem lehet törölni egy rendszermappát. status.FolderDelete.6=A parancs nem sikerült, hiba történt a kiszolgálón. status.FolderDelete.4=?? Folder does not exist (status 4), resyncing ?? status.FolderDelete.9=?? Invalid synchronization key (status 9), resyncing ?? status.FolderSync.9=?? Invalid synchronization key (status 9), resyncing ?? status.forbiddenTasksItemInCalendarFolder=?? Forbidden task item in a calendar folder (please resort) ?? status.forbiddenCalendarItemInTasksFolder=?? Forbidden calendar item in a task folder (please resort) ?? config.auto=Az ActiveSync kiszolgáló beállítások (önműködő észlelés) config.custom=Az ActiveSync kiszolgáló beállítások acl.readwrite=Read from and write to server acl.readonly=Read-only server access (revert local changes) autocomplete.serverdirectory = global server directory EAS-4-TbSync-1.12/_locales/hu/messages.json000066400000000000000000000004441362346370100203140ustar00rootroot00000000000000{ "extensionName": { "message": "Az Exchange ActiveSync szolgáltató" }, "extensionDescription": { "message": "Az Exchange ActiveSync-fiókok összehangolás támogatásának hozzáadása a Thunderbird-összehangoláshoz (TbSync) (névjegyzék, feladatok és naptárak)." } }EAS-4-TbSync-1.12/_locales/it/000077500000000000000000000000001362346370100156105ustar00rootroot00000000000000EAS-4-TbSync-1.12/_locales/it/eas.dtd000066400000000000000000000107331362346370100170610ustar00rootroot00000000000000 EAS-4-TbSync-1.12/_locales/it/eas.properties000066400000000000000000000235031362346370100205010ustar00rootroot00000000000000newaccount.add_custom=Aggiungi account newaccount.add_auto=Rileva automaticamente impostazioni e aggiungi account autodiscover.Querying=Rilevamento impostazioni in corso... autodiscover.Ok=Rilevamento automatico impostazioni completato con successo, è ora possibile verificare le impostazioni facoltative e stabilire la connessione per la sincronizzazione. autodiscover.NeedEmail=Il rilevamento automatico impostazioni richiede che venga specificato un indirizzo di posta elettronica valido come nome utente. autodiscover.Failed=Rilevamento automatico non riuscito per l'utente <##user##>. Le credenziali fornite sono errate o il proprio provider ActiveSync ha un problema temporaneo o non supporta il rilevamento automatico impostazioni. deletefolder.notallowed=Annullare la sottoscrizione alla cartella "##replace.1##" prima di provare a eliminarla dal Cestino. deletefolder.confirm=ELIMINARE PERMANENTEMENTE la cartella "##replace.1##" dal Cestino? deletefolder.menuentry=Elimina permanentemente la cartella "##replace.1##" dal Cestino recyclebin = Cestino syncstate.prepare.request.setdeviceinfo=Invio informazioni dispositivo in corso syncstate.send.request.setdeviceinfo=Invio informazioni dispositivo in corso syncstate.eval.response.setdeviceinfo=Invio informazioni dispositivo in corso syncstate.syncing=Inizializzazione sincronizzazione in corso syncstate.preparing=Preparazione prossimo elemento per la sincronizzazione in corso syncstate.done=Preparazione prossimo elemento per la sincronizzazione in corso syncstate.accountdone=Sincronizzazione account completata syncstate.prepare.request.autodiscover=Richiesta impostazioni server aggiornate in corso syncstate.send.request.autodiscover=In attesa delle impostazioni server aggiornate syncstate.eval.response.autodiscover=Elaborazione impostazioni server aggiornate in corso syncstate.prepare.request.options=Richiesta opzioni server in corso syncstate.send.request.options=In attesa delle opzioni server syncstate.eval.response.options=Elaborazione opzioni server in corso syncstate.prepare.request.folders=Invio aggiornamenti elenco cartelle in corso syncstate.send.request.folders=In attesa degli aggiornamenti elenco cartelle syncstate.eval.response.folders=Elaborazione aggiornamenti elenco cartelle in corso syncstate.prepare.request.synckey=Richiesta chiave di sincronizzazione in corso syncstate.send.request.synckey=In attesa della chiave di sincronizzazione syncstate.eval.response.synckey=Elaborazione chiave di sincronizzazione in corso syncstate.prepare.request.deletefolder=Preparazione eliminazione cartella in corso syncstate.send.request.deletefolder=In attesa dell'eliminazione della cartella syncstate.eval.response.deletefolder=Cartella eliminata syncstate.prepare.request.provision=Richiesta provisioning in corso syncstate.send.request.provision=In attesa del provisioning syncstate.eval.response.provision=Elaborazione provisioning in corso syncstate.prepare.request.estimate=Richiesta stima modifiche in corso syncstate.send.request.estimate=In attesa della stima modifiche syncstate.eval.response.estimate=Elaborazione stima modifiche in corso syncstate.prepare.request.remotechanges=Richiesta modifiche remote in corso syncstate.send.request.remotechanges=In attesa delle modifiche remote syncstate.eval.response.remotechanges=Elaborazione modifiche remote in corso syncstate.prepare.request.localchanges=Invio modifiche locali in corso syncstate.send.request.localchanges=In attesa del riconoscimento delle modifiche locali syncstate.eval.response.localchanges=Elaborazione riconoscimento modifiche locali in corso syncstate.prepare.request.revertlocalchanges=Raccolta modifiche locali in corso syncstate.send.request.revertlocalchanges=In attesa delle versioni più recenti syncstate.eval.response.revertlocalchanges=Annullamento modifiche locali in corso syncstate.prepare.request.localdeletes=Invio eliminazioni locali in corso syncstate.send.request.localdeletes=In attesa del riconoscimento delle eliminazioni locali syncstate.eval.response.localdeletes=Elaborazione riconoscimento eliminazioni locali in corso status.notsyncronized=L'account deve essere sincronizzato, almeno un elemento non è sincronizzato. status.disabled=L'account non è abilitato, la sincronizzazione è disabilitata. status.notargets=Interruzione sincronizzazione in corso: non è possibile creare le destinazioni sincronizzazione. status.nouserhost=Nome utente e/o server mancanti. Fornire tali valori. status.timeout=Timeout durante la comunicazione. status.networkerror=Impossibile connettersi al server. status.404=Utente non trovato (errore HTTP 404). status.403=Il server ha rifiutato la connessione (vietato). status.401=Impossibile autenticarsi, controllare nome utente e password. status.449=Il server richiede il provisioning. status.500=Errore server sconosciuto (errore HTTP 500). status.503=Servizio non disponibile. status.httperror=Errore di comunicazione (stato HTTP ##replace.1##). status.empty-response=Il server invia una risposta vuota inaspettata. status.wbxml-parse-error=Il server invia una risposta illeggibile. status.response-contains-no-data=La risposta dal server non contiene dati. status.wbxmlerror=Sincronizzazione non riuscita. Il server ha risposto con lo stato <##replace.1##>. status.wbxmlmissingfield=Violazione protocollo ActiveSync: il campo obbligatorio <##replace.1##> manca dalla risposta del server. status.OK=OK status.nolightning=Componente aggiuntivo Lightning non installato, i calendari non sono supportati. status.malformed-xml=Impossibile analizzare XML. Controllare il registro eventi per i dettagli. status.invalid=Risposta del server non valida (inutile). status.InvalidServerOptions=Il server non fornisce informazioni sulle versioni di ActiveSync supportate. EAS è bloccato per questo utente o questo client (TbSync)? È possibile provare a impostare manualmente la versione di ActiveSync. status.security=Impossibile stabilire una connessione sicura. Si sta utilizzando un certificato autofirmato o non affidabile senza averlo importato in Thunderbird? (##replace.1##) helplink.security=https://github.com/jobisoft/TbSync/wiki/How-to-use-TbSync-with-self-signed-or-otherwise-untrusted-certificates%3F status.network=Impossibile collegarsi al server (##replace.1##). status.syncing=Sincronizzazione in corso status.skipped=Non ancora supportato, omesso status.aborted=Non sincronizzato status.pending=In attesa di sincronizzazione status.modified=Modifiche locali presenti status.resync-loop=Si è verificato un errore che non è stato possibile correggere eseguendo una nuova sincronizzazione dell'account. Disabilitare l'account e riprovare. (Errore: ciclo di risincronizzazione) status.policy.2=Non esiste alcuna policy per questo client. Contattare l'amministratore del proprio server o disabilitare il provisioning per questo account. status.policy.3=Valore PolicyType sconosciuto. Contattare l'amministratore del proprio server o disabilitare il provisioning per questo account. status.policy.4=I dati di policy sul server sono corrotti (forse alterati). Contattare l'amministratore del proprio server o disabilitare il provisioning per questo account. status.policy.5=Il client sta riconoscendo una policy con nome errato. Contattare l'amministratore del proprio server o disabilitare il provisioning per questo account. status.provision=Provisioning non riuscito: stato <##replace.1##> status.global.101=La richiesta contiene WBXML ma non è possibile decodificarlo in XML (errore 101). status.global.102=La richiesta contiene WBXML ma non è possibile decodificarlo in XML (errore 102). status.global.103=L'XML fornito nella richiesta non rispetta i requisiti del protocollo (errore 103). status.global.110=Il server ha segnalato un errore interno e non dovremmo riprovare immediatamente. La sincronizzazione periodica automatica è stata disattivata per 30 minuti (errore EAS 110). status.global.clientdenied=Il server EAS segnala <##replace.2##> (stato ##replace.1##) e non consente a TbSync l'accesso al proprio account. helplink.global.clientdenied=https://github.com/jobisoft/EAS-4-TbSync/wiki/What-if-TbSync-is-blocked-by-my-server%3F status.ServerRejectedSomeItems=Il server EAS non ha accettato ##replace.1## elementi. status.ServerRejectedRequest=Il server EAS ha rifiutato l'ultima richiesta. status.BadItemSkipped=Oggetto errato saltato: ##replace.1## helplink.BadItemSkipped=https://github.com/jobisoft/EAS-4-TbSync/wiki/Error:-Bad-item-skipped status.nosupportedeasversion=Il server non supporta ActiveSync v2.5 o v14.0 (solo ##replace.1##). TbSync non funzionerà con questo server ActiveSync. status.notsupportedeasversion=Il server non supporta la versione di ActiveSync selezionata ##replace.1## (solo ##replace.2##). status.Sync.3=?? Invalid synchronization key (status 3), resyncing ?? status.Sync.4=?? Malformed request (status 4) ?? status.Sync.5=?? Temporary server issues or invalid item (status 5) ?? status.Sync.6=?? Invalid item (status 6) ?? status.Sync.8=?? Object not found (status 8) ?? status.Sync.12=?? Folder hierarchy changed (status 12), resyncing ?? status.FolderDelete.3=Impossibile eliminare una cartella di sistema. status.FolderDelete.6=Impossibile completare il comando, si è verificato un errore sul server. status.FolderDelete.4=?? Folder does not exist (status 4), resyncing ?? status.FolderDelete.9=?? Invalid synchronization key (status 9), resyncing ?? status.FolderSync.9=?? Invalid synchronization key (status 9), resyncing ?? status.forbiddenTasksItemInCalendarFolder=?? Forbidden task item in a calendar folder (please resort) ?? status.forbiddenCalendarItemInTasksFolder=?? Forbidden calendar item in a task folder (please resort) ?? config.auto=Configurazione server ActiveSync (rilevamento automatico) config.custom=Configurazione server ActiveSync acl.readwrite=Leggi e scrivi sul server acl.readonly=Accesso al server in sola lettura (ripristina le modifiche locali) autocomplete.serverdirectory = directory globale del server EAS-4-TbSync-1.12/_locales/it/messages.json000066400000000000000000000003641362346370100203150ustar00rootroot00000000000000{ "extensionName": { "message": "Provider Exchange ActiveSync" }, "extensionDescription": { "message": "Aggiunge il supporto per la sincronizzazione di account Exchange ActiveSync (contatti, attività e calendari) a TbSync." } }EAS-4-TbSync-1.12/_locales/pl/000077500000000000000000000000001362346370100156075ustar00rootroot00000000000000EAS-4-TbSync-1.12/_locales/pl/eas.dtd000066400000000000000000000105751362346370100170640ustar00rootroot00000000000000 EAS-4-TbSync-1.12/_locales/pl/eas.properties000066400000000000000000000225361362346370100205050ustar00rootroot00000000000000newaccount.add_custom=Dodaj konto newaccount.add_auto=Wykryj automatycznie ustawienia i dodaj konto autodiscover.Querying=Wyszukiwanie ustawień… autodiscover.Ok=Automatyczne wykrywanie zakończyło się pomyślnie, możesz teraz sprawdzić ustawienia opcjonalne i ustanowić połączenie synchronizacji. autodiscover.NeedEmail=Automatyczne wykrywanie potrzebuje prawidłowego adresu e-mail jako nazwy użytkownika. autodiscover.Failed=Auto-wykrywanie dla użytkownika <##user##> nie powiodło się. Podane dane uwierzytelniające były nieprawidłowe lub dostawca ActiveSync ma tymczasowy problem lub nie obsługuje funkcji automatycznego wykrywania. deletefolder.notallowed=Anuluj subskrypcję folderu “##replace.1##” przed próbą usunięcia go z kosza. deletefolder.confirm=Czy naprawdę chcesz TRWALE USUNĄĆ folder “##replace.1##” z kosza? deletefolder.menuentry=Trwale usuń folder “##replace.1##” z kosza recyclebin = Kosz syncstate.prepare.request.setdeviceinfo=Wysyłanie informacji o urządzeniu syncstate.send.request.setdeviceinfo=Wysyłanie informacji o urządzeniu syncstate.eval.response.setdeviceinfo=Wysyłanie informacji o urządzeniu syncstate.syncing=Zainicjuj synchronizację syncstate.preparing=Przygotowuję następny element do synchronizacji syncstate.done=Przygotowuję następny element do synchronizacji syncstate.accountdone=Konto gotowe syncstate.prepare.request.autodiscover=Żądanie zaktualizowanych ustawień serwera syncstate.send.request.autodiscover=Oczekiwanie na zaktualizowane ustawienia serwera syncstate.eval.response.autodiscover=Przetwarzanie zaktualizowanych ustawień serwera syncstate.prepare.request.options=Żądanie opcji serwera syncstate.send.request.options=Oczekiwanie na opcje serwera syncstate.eval.response.options=Przetwarzanie opcji serwera syncstate.prepare.request.folders=Wysyłanie aktualizacji listy folderów syncstate.send.request.folders=Oczekiwanie na aktualizację listy folderów syncstate.eval.response.folders=Przetwarzanie aktualizacji listy folderów syncstate.prepare.request.synckey=Żądanie SyncKey syncstate.send.request.synckey=Oczekiwanie na SyncKey syncstate.eval.response.synckey=Przetwarzanie SyncKey syncstate.prepare.request.deletefolder=Przygotowanie do usunięcia folderu syncstate.send.request.deletefolder=Oczekiwanie na usunięcie folderu syncstate.eval.response.deletefolder=Folder usunięty syncstate.prepare.request.provision=Żądanie provision syncstate.send.request.provision=Oczekiwanie na provision syncstate.eval.response.provision=Przetwarzanie provision syncstate.prepare.request.estimate=Żądanie oszacowanych zmian syncstate.send.request.estimate=Oczekiwanie na oszacowanie zmian syncstate.eval.response.estimate=Przetwarzanie oszacowanych zmian syncstate.prepare.request.remotechanges=Żądanie zdalnych zmian syncstate.send.request.remotechanges=Oczekiwanie na zdalne zmiany syncstate.eval.response.remotechanges=Przetwarzanie zdalnych zmian syncstate.prepare.request.localchanges=Wysyłanie zmian lokalnych syncstate.send.request.localchanges=Oczekiwanie na potwierdzenie lokalnych zmian syncstate.eval.response.localchanges=Przetwarzanie potwierdzenia lokalnych zmian syncstate.prepare.request.revertlocalchanges=Zbieranie zmian lokalnych syncstate.send.request.revertlocalchanges=Czekam na najnowsze wersje syncstate.eval.response.revertlocalchanges=Wycofywanie zmian lokalnych syncstate.prepare.request.localdeletes=Wysyłanie usuniętych lokalnie syncstate.send.request.localdeletes=Oczekiwanie na potwierdzenie usunięcia lokalnego syncstate.eval.response.localdeletes=Przetwarzanie potwierdzenia usunięcia lokalnego status.notsyncronized=Konto musi zostać zsynchronizowane, co najmniej jeden element nie jest zsynchronizowany. status.disabled=Wyłączony status.notargets=Przerywam synchronizację, ponieważ nie można utworzyć celów synchronizacji. status.nouserhost=Brak nazwy użytkownika i/lub serwera. Podaj te wartości. status.timeout=Przekroczony limit czasu połączenia. status.networkerror=Nie można połączyć z serwerem. status.404=Użytkownik nie znaleziony (błąd HTTP 404). status.403=Serwer odrzucił połączenie (zabronione) (błąd HTTP 403). status.401=Nie można uwierzytelnić, sprawdź nazwę użytkownika i hasło (HTTP Error 401). status.449=Server requests provisioning (HTTP Error 449). status.500=Nieznany błąd serwera (błąd HTTP 500). status.503=Usługa niedostępna (błąd HTTP 503). status.httperror=Błąd komunikacji (status HTTP ##replace.1##). status.empty-response=Serwer wysyła nieoczekiwaną pustą odpowiedź. status.wbxml-parse-error=Serwer wysyła nieczytelną odpowiedź. status.response-contains-no-data=Odpowiedź z serwera nie zawiera danych. status.wbxmlerror=Synchronizacja nie powiodła się. Serwer odpowiedział statusem <##replace.1##>. status.wbxmlmissingfield=Naruszenie protokołu ActiveSync: W odpowiedzi serwera brakuje obowiązkowego pola <##replace.1##>. status.OK=OK status.nolightning=Dodatek Lightning nie został zainstalowany, kalendarze nie są obsługiwane. status.malformed-xml=Nie można przeanalizować XML. Sprawdź dziennik zdarzeń, aby uzyskać szczegółowe informacje. status.invalid=Niepoprawna odpowiedź serwera (śmieci). status.InvalidServerOptions=Serwer nie dostarcza informacji o obsługiwanych wersjach ActiveSync. Czy EAS jest zablokowany dla tego użytkownika lub tego klienta (TbSync)? Możesz spróbować ręcznie ustawić wersję ActiveSync. status.security=Nie można nawiązać bezpiecznego połączenia. Czy używasz self-signed lub w inny sposób niezaufanego certyfikatu bez importowania go do Thunderbirda? (##replace.1##) helplink.security=https://github.com/jobisoft/TbSync/wiki/How-to-use-TbSync-with-self-signed-or-otherwise-untrusted-certificates%3F status.network=Nie można połączyć z serwerem (##replace.1##). status.syncing=Synchronizuję status.skipped=Jeszcze nie obsługiwane, pominięto status.aborted=Nie zsynchronizowane status.pending=Oczekiwanie na synchronizację status.modified=Zmiany lokalne status.resync-loop=Wystąpił błąd, którego nie udało się naprawić poprzez ponowne zsynchronizowanie konta. Wyłącz konto i spróbuj ponownie. (Błąd: pętla resynchronizacji) status.policy.2=Brak zasad dla tego klienta. Skontaktuj się z administratorem serwera lub wyłącz provisioning dla tego konta. status.policy.3=Nieznana wartość PolicyType. Skontaktuj się z administratorem serwera lub wyłącz provisioning dla tego konta. status.policy.4=Dane zasad na serwerze są uszkodzone (prawdopodobnie zmienione/sfałszowane). Skontaktuj się z administratorem serwera lub wyłącz provisioning dla tego konta. status.policy.5=Klient potwierdza zły klucz zasad. Skontaktuj się z administratorem serwera lub wyłącz provisioning dla tego konta. status.provision=Niepowodzenie provisioning ze statusem <##replace.1##> status.global.101=Żądanie zawiera WBXML, ale nie można go zdekodować do formatu XML (błąd EAS 101). status.global.102=Żądanie zawiera WBXML, ale nie można go zdekodować do formatu XML (błąd EAS 102). status.global.103=XML podany w żądaniu nie spełnia wymagań protokołu (błąd EAS 103). status.global.110=Serwer zgłosił błąd wewnętrzny i nie powinniśmy natychmiast ponawiać próby. Automatyczna synchronizacja okresowa została wyłączona na 30 minut (błąd EAS 110). status.global.clientdenied=Serwer EAS zgłasza <##replace.2##> (status ##replace.1##) i nie pozwala TbSync na dostęp do Twojego konta. helplink.global.clientdenied=https://github.com/jobisoft/EAS-4-TbSync/wiki/What-if-TbSync-is-blocked-by-my-server%3F status.ServerRejectedSomeItems=Serwer EAS nie zaakceptował elementów #replace.1##. status.ServerRejectedRequest=Serwer EAS odrzucił ostatnie żądanie. status.BadItemSkipped=Błędny element pominięto: ##replace.1## helplink.BadItemSkipped=https://github.com/jobisoft/EAS-4-TbSync/wiki/Error:-Bad-item-skipped status.nosupportedeasversion=Serwer nie obsługuje ActiveSync v2.5 lub v14.0 (tylko ##replace.1##). TbSync nie będzie działać z tym serwerem ActiveSync. status.notsupportedeasversion=Serwer nie wspiera wybranego ActiveSync v##replace.1## (tylko ##replace.2##). status.Sync.3=Nieprawidłowy klucz synchronizacji (status 3), ponowna synchronizacja status.Sync.4=Zniekształcone żądanie (status 4) status.Sync.5=Tymczasowe problemy z serwerem lub nieprawidłowy element (status 5) status.Sync.6=Nieprawidłowy element (status 6) status.Sync.8=Nie znaleziono obiektu (status 8) status.Sync.12=Hierarchia folderów zmieniona (status 12), ponowna synchronizacja status.FolderDelete.3=Nie można usunąć folderu systemowego (status 3) status.FolderDelete.6=Nie można wykonać polecenia, wystąpił błąd na serwerze (status 6) status.FolderDelete.4=Folder nie istnieje (status 4), ponowna synchronizacja status.FolderDelete.9=Nieprawidłowy klucz synchronizacji (status 9), ponowna synchronizacja status.FolderSync.9=Nieprawidłowy klucz synchronizacji (status 9), ponowna synchronizacja status.forbiddenTasksItemInCalendarFolder=Niedozwolony element zadania w folderze kalendarza (please resort) status.forbiddenCalendarItemInTasksFolder=Niedozwolony element kalendarza w folderze zadań (please resort) config.auto=Konfiguracja serwera ActiveSync (Auto-wykrywanie) config.custom=Konfiguracja serwera ActiveSync acl.readwrite=Odczytuj i zapisuj na serwer acl.readonly=Dostęp do serwera tylko do odczytu (cofnij zmiany lokalne) autocomplete.serverdirectory = globalny katalog serwera EAS-4-TbSync-1.12/_locales/pl/messages.json000066400000000000000000000003441362346370100203120ustar00rootroot00000000000000{ "extensionName": { "message": "Dostawca dla Exchange ActiveSync" }, "extensionDescription": { "message": "Dodaj obsługę synchronizacji kont Exchange ActiveSync do TbSync (kontakty, zadania i kalendarze)." } }EAS-4-TbSync-1.12/_locales/pt_BR/000077500000000000000000000000001362346370100162025ustar00rootroot00000000000000EAS-4-TbSync-1.12/_locales/pt_BR/eas.dtd000066400000000000000000000110461362346370100174510ustar00rootroot00000000000000 EAS-4-TbSync-1.12/_locales/pt_BR/eas.properties000066400000000000000000000230601362346370100210710ustar00rootroot00000000000000newaccount.add_custom=Adicionar conta newaccount.add_auto=Configurações de descoberta automática e adicionar conta autodiscover.Querying=Procurando por configurações... autodiscover.Ok=Descoberta automática concluída com êxito, agora você pode verificar as configurações opcionais e estabelecer a conexão de sincronização. autodiscover.NeedEmail=A descoberta automática precisa de um endereço de email válido autodiscover.Failed=Descoberta automática para usuário <##user##> falhou. As credenciais fornecidas estavam erradas ou seu provedor do ActiveSync tem um problema temporário ou não oferece suporte à descoberta automática. deletefolder.notallowed=Por favor, cancele a subscrição da pasta "##replace.1##" antes de tentar limpá-lo do lixo. deletefolder.confirm=Você realmente deseja limpar permanentemente a pasta "##replace.1##" da lixeira? deletefolder.menuentry=Remover permanentemente a pasta "##replace.1##" da lixeira recyclebin = Lixeira syncstate.prepare.request.setdeviceinfo=Enviando informações do dispositivo syncstate.send.request.setdeviceinfo=Enviando informações do dispositivo syncstate.eval.response.setdeviceinfo=Enviando informações do dispositivo syncstate.syncing=Inicializar sincronização syncstate.preparing=Preparando o próximo item para sincronização syncstate.done=Preparando o próximo item para sincronização syncstate.accountdone=Sincronização finalizada syncstate.prepare.request.autodiscover=Solicitando configurações do servidor atualizadas syncstate.send.request.autodiscover=Aguardando as configurações do servidor atualizadas syncstate.eval.response.autodiscover=Aguardando as configurações do servidor atualizadas syncstate.prepare.request.options=Solicitando opções do servidor syncstate.send.request.options=Aguardando por opções do servidor syncstate.eval.response.options=Processando opções do servidor syncstate.prepare.request.folders=Enviando lista de pastas atualizadas syncstate.send.request.folders=Aguardando a atualização da lista de pastas syncstate.eval.response.folders=Processando a atualização da lista de pastas syncstate.prepare.request.synckey=Solicitando SyncKey syncstate.send.request.synckey=Esperando SyncKey syncstate.eval.response.synckey=Processando SyncKey syncstate.prepare.request.deletefolder=Preparando-se para excluir pasta syncstate.send.request.deletefolder=Aguardando a pasta ser excluída syncstate.eval.response.deletefolder=Pasta excluída syncstate.prepare.request.provision=Solicitando provisão syncstate.send.request.provision=Agurdandando provisão syncstate.eval.response.provision=Processando provisão syncstate.prepare.request.estimate=Solicitando estimativa de alterações syncstate.send.request.estimate=Aguardando a estimativa de alterações syncstate.eval.response.estimate=Processando a estimativa de alterações syncstate.prepare.request.remotechanges=Solicitando alterações remotas syncstate.send.request.remotechanges=Aguardando alterações remotas syncstate.eval.response.remotechanges=Processando alterações remotas syncstate.prepare.request.localchanges=Enviando alterações locais syncstate.send.request.localchanges=Aguardando confirmação de alterações locais syncstate.eval.response.localchanges=Processando reconhecimento de alterações locais syncstate.prepare.request.revertlocalchanges=Coletando alterações locais syncstate.send.request.revertlocalchanges=Aguardando as versões mais recentes syncstate.eval.response.revertlocalchanges=Revertendo alterações locais syncstate.prepare.request.localdeletes=Enviando exclusões locais syncstate.send.request.localdeletes=Aguardando o reconhecimento de exclusões locais syncstate.eval.response.localdeletes=Processando reconhecimento de exclusões locais status.notsyncronized=A conta precisa ser sincronizada, pelo menos, um item está fora de sincronia. status.disabled=Desativado status.notargets=Anulando a sincronização, porque os destinos de sincronização não puderam ser criados. status.nouserhost=Nome de usuário e/ou servidor ausente. Por favor, forneça esses valores. status.timeout=Tempo limite de comunicação. status.networkerror=Não foi possível conectar ao servidor. status.404=Usuário não encontrado (HTTP Erro 404). status.403=Conexão rejeitada pelo servidor (proibido) (HTTP Erro 403). status.401=Não foi possível autenticar, verifique o nome de usuário e senha (HTTP Erro 401). status.449=O servidor solicita o provisionamento (HTTP Erro 449). status.500=Erro de servidor desconhecido (HTTP Erro 500). status.503=Serviço indisponível (HTTP Erro 503). status.httperror=Erro de comunicação (HTTP status ##replace.1##). status.empty-response=O servidor enviou uma resposta vazia inesperada. status.wbxml-parse-error=O servidor enviou uma resposta ilegível. status.response-contains-no-data=A resposta do servidor não contém dados. status.wbxmlerror=A sincronização falhou. O servidor respondeu com status <##replace.1##>. status.wbxmlmissingfield=Violação do protocolo ActiveSync: o campo obrigatório <##replace.1##> está faltando na resposta do servidor. status.OK=OK status.nolightning=O complemento Lightning não está instalado, os calendários não são suportados. status.malformed-xml=Não foi possível analisar XML. Verifique o log de eventos para obter detalhes. status.invalid=Resposta inválida do servidor (lixo). status.InvalidServerOptions=O servidor não fornece informações sobre as versões do ActiveSync com suporte. O EAS está bloqueado para este usuário ou esse cliente (TbSync)? Você pode tentar definir a versão do ActiveSync manualmente. status.security=Não foi possível estabelecer uma conexão segura. Você está usando um certificado autoassinado ou não confiável sem importá-lo para o Thunderbird? (##replace.1##) helplink.security=https://github.com/jobisoft/TbSync/wiki/How-to-use-TbSync-with-self-signed-or-otherwise-untrusted-certificates%3F status.network=Não foi possível conectar ao servidor (##replace.1##). status.syncing=Sincronizando status.skipped=Ainda não suportado, ignorado status.aborted=Não sincronizado status.pending=Esperando para ser sincronizado status.modified=Modificações locais status.resync-loop=Ocorreu um erro do qual não conseguimos recuperar ao reativar a conta. Por favor, desative a conta e tente novamente. (Erro: loop de ressincronização) status.policy.2=Não há política para este cliente. Entre em contato com o administrador do servidor ou desabilite o provisionamento para essa conta. status.policy.3=Valor de PolicyType desconhecido. Entre em contato com o administrador do servidor ou desabilite o provisionamento para essa conta. status.policy.4=Os dados da política no servidor estão corrompidos (possivelmente adulterados). Entre em contato com o administrador do servidor ou desabilite o provisionamento para essa conta. status.policy.5=O cliente está reconhecendo a chave de política incorreta. Entre em contato com o administrador do servidor ou desabilite o provisionamento para essa conta. status.provision=O provisionamento falhou com o status <##replace.1##> status.global.101=A solicitação contém WBXML, mas não pôde ser decodificada em XML (Erro EAS 101). status.global.102=A solicitação contém WBXML, mas não pôde ser decodificada em XML (Erro EAS 102). status.global.103=O XML fornecido na solicitação não segue os requisitos do protocolo (Erro EAS 103). status.global.110=O servidor relatou um erro interno e não devemos tentar novamente imediatamente. A sincronização periódica automática foi desativada por 30 minutos (Erro 110 do EAS). status.global.clientdenied=O servidor EAS reporta <##replace.2##> (status ##replace.1##) e não permite que o TbSync acesse sua conta. helplink.global.clientdenied=https://github.com/jobisoft/EAS-4-TbSync/wiki/What-if-TbSync-is-blocked-by-my-server%3F status.ServerRejectedSomeItems=O servidor EAS não aceitou ##replace.1## elementos. status.ServerRejectedRequest=O servidor EAS rejeitou a última solicitação. status.BadItemSkipped=Item inválido ignorado: ##replace.1## helplink.BadItemSkipped=https://github.com/jobisoft/EAS-4-TbSync/wiki/Error:-Bad-item-skipped status.nosupportedeasversion=O servidor não suporta o ActiveSync v2.5 ou v14.0 (somente ##replace.1##). TbSync não funcionará com este servidor ActiveSync. status.notsupportedeasversion=O servidor não suporta o ActiveSync selecionado v##replace.1## (somente ##replace.2##). status.Sync.3=?? Invalid synchronization key (status 3), resyncing ?? status.Sync.4=?? Malformed request (status 4) ?? status.Sync.5=?? Temporary server issues or invalid item (status 5) ?? status.Sync.6=?? Invalid item (status 6) ?? status.Sync.8=?? Object not found (status 8) ?? status.Sync.12=?? Folder hierarchy changed (status 12), resyncing ?? status.FolderDelete.3=Não é possível excluir uma pasta do sistema. status.FolderDelete.6=O comando não pôde ser concluído, ocorreu um erro no servidor. status.FolderDelete.4=?? Folder does not exist (status 4), resyncing ?? status.FolderDelete.9=?? Invalid synchronization key (status 9), resyncing ?? status.FolderSync.9=?? Invalid synchronization key (status 9), resyncing ?? status.forbiddenTasksItemInCalendarFolder=?? Forbidden task item in a calendar folder (please resort) ?? status.forbiddenCalendarItemInTasksFolder=?? Forbidden calendar item in a task folder (please resort) ?? config.auto=Configuração do servidor ActiveSync (Descoberta Automática) config.custom=Configuração do servidor ActiveSync acl.readwrite=Ler e escrever no servidor acl.readonly=Acesso ao servidor somente leitura (reverter alterações locais) autocomplete.serverdirectory = diretório global do servidor EAS-4-TbSync-1.12/_locales/pt_BR/messages.json000066400000000000000000000003561362346370100207100ustar00rootroot00000000000000{ "extensionName": { "message": "Provedor Exchange ActiveSync" }, "extensionDescription": { "message": "Adiciona suporte para sincronizar contas do Exchange ActiveSync (contatos, tarefas e calendários) para o TbSync." } }EAS-4-TbSync-1.12/_locales/ru/000077500000000000000000000000001362346370100156225ustar00rootroot00000000000000EAS-4-TbSync-1.12/_locales/ru/eas.dtd000066400000000000000000000141361362346370100170740ustar00rootroot00000000000000 EAS-4-TbSync-1.12/_locales/ru/eas.properties000066400000000000000000000333061362346370100205150ustar00rootroot00000000000000newaccount.add_custom=Добавить аккаунт newaccount.add_auto=Настройки автообнаружения и добавление аккаунта autodiscover.Querying=Поиск настроек… autodiscover.Ok=Автообнаружение успешно завершено, теперь вы можете проверить дополнительные настройки и установить соединение синхронизации. autodiscover.NeedEmail=Автообнаружению нужен действительный адрес электронной почты в качестве имени пользователя. autodiscover.Failed=Автообнаружение для пользователя <##user##> не удалось. Либо предоставленные учетные данные были неправильными, либо у вашего провайдера ActiveSync была временная проблема, или не поддерживается автообнаружение. deletefolder.notallowed=Откажитесь от подписки папки “##replace.1##” прежде чем пытаться очистить ее от мусора. deletefolder.confirm=Вы действительно хотите ПЕРМАНЕНТНО ВЫЧИСТИТЬ папку “##replace.1##” из корзины? deletefolder.menuentry=Перманентно вычистить папку “##replace.1##” из корзины recyclebin = Корзина syncstate.prepare.request.setdeviceinfo=Отправка информации об устройстве syncstate.send.request.setdeviceinfo=Ожидание информации об устройстве syncstate.eval.response.setdeviceinfo=Обработка информации об устройстве syncstate.syncing=Инициализация синхронизации syncstate.preparing=Подготовка следующего элемента для синхронизации syncstate.done=Подготовка следующего элемента для синхронизации syncstate.accountdone=Завершенный аккаунт syncstate.prepare.request.autodiscover=Запрос обновленных настроек сервера syncstate.send.request.autodiscover=Ожидание обновленных настроек сервера syncstate.eval.response.autodiscover=Обработка обновленных настроек сервера syncstate.prepare.request.options=Запрос настроек сервера syncstate.send.request.options=Ожидание настроек сервера syncstate.eval.response.options=Обработка настроек сервера syncstate.prepare.request.folders=Отправка обновления листа папок syncstate.send.request.folders=Ожидание обновления листа папок syncstate.eval.response.folders=Обработка обновления листа папок syncstate.prepare.request.synckey=Запрос элементов синхронизации syncstate.send.request.synckey=Ожидание элементов синхронизации syncstate.eval.response.synckey=Обработка элементов синхронизации syncstate.prepare.request.deletefolder=Подготовка к удалению папки syncstate.send.request.deletefolder=Ожидание удаления папки syncstate.eval.response.deletefolder=Папка удалена syncstate.prepare.request.provision=Запрос для резервирования syncstate.send.request.provision=Ожидание резервирования syncstate.eval.response.provision=Обработка резервирования syncstate.prepare.request.estimate=Запрос оценки изменения syncstate.send.request.estimate=Ожидание оценки изменений syncstate.eval.response.estimate=Обработка оценки изменений syncstate.prepare.request.remotechanges=Запрос удаленных изменений syncstate.send.request.remotechanges=Ожидание удаленных изменений syncstate.eval.response.remotechanges=Обработка удаленных изменений syncstate.prepare.request.localchanges=Отправка локальных изменений syncstate.send.request.localchanges=Ожидание подтверждения локальных изменений syncstate.eval.response.localchanges=Обработка подтверждения локальных изменений syncstate.prepare.request.revertlocalchanges=Сбор локальных изменений syncstate.send.request.revertlocalchanges=Ожидание самых последних версий syncstate.eval.response.revertlocalchanges=Откат локальных изменений syncstate.prepare.request.localdeletes=Отправка локальных удалений syncstate.send.request.localdeletes=Ожидание подтверждения локальных удалений syncstate.eval.response.localdeletes=Обработка подтверждения локальных удалений status.notsyncronized=Аккаунт должен быть синхронизирован, по крайней мере один элемент не синхронизирован. status.disabled=Аккаунт не включен, синхронизация отключена. status.notargets=Отмена синхронизации, поскольку цели синхронизации не могут быть созданы. status.nouserhost=Отсутствует имя пользователя и/или сервера. Укажите эти значения. status.timeout=Тайм-аут связи. status.networkerror=Не удалось подключиться к серверу. status.404=Пользователь не найден (HTTP Ошибка 404). status.403=Сервер отклонил соединение (запрещено) (HTTP Ошибка 403). status.401=Не удалось аутентифицировать, проверить имя пользователя и пароль. (HTTP Ошибка 401). status.449=Серверные запросы дублируются (HTTP Ошибка 449). status.500=Неизвестная ошибка сервера (HTTP Ошибка 500). status.503=Сервис недоступен (HTTP Ошибка 503). status.httperror=Ошибка связи (HTTP статус ##replace.1##). status.empty-response=Сервер отправляет неожиданный пустой ответ. status.wbxml-parse-error=Сервер отправляет нечитаемый ответ. status.response-contains-no-data=Ответ от сервера не содержит данных. status.wbxmlerror=Ошибка синхронизации. Сервер ответил статусом <##replace.1##>. status.wbxmlmissingfield=ActiveSync протокол нарушен: Обязательное поле <##replace.1##> отсутствует в ответе сервера. status.OK=Готово status.nolightning=Lightning Add-On не установлен, календари не поддерживаются. status.malformed-xml=Не удалось разобрать XML. Проверьте журнал событий для деталей. status.invalid=Недопустимый ответ сервера (мусор в обмене). status.InvalidServerOptions=Сервер не предоставляет информацию о поддерживаемых версиях ActiveSync. EAS блокирован для этого пользователя или этого клиента (TbSync)? Вы можете попробовать установить версию ActiveSync вручную. status.security=Не удалось установить безопасное соединение. Вы используете самоподписанный или ненадежный сертификат без импорта его в Thunderbird? (##replace.1##) helplink.security=https://github.com/jobisoft/TbSync/wiki/How-to-use-TbSync-with-self-signed-or-otherwise-untrusted-certificates%3F status.network=Не удалось подключиться к серверу (##replace.1##). status.syncing=Синхронизация status.skipped=Пока не поддерживается, пропущено status.aborted=Не синхронизировано status.pending=Ожидание пока синхронизируется status.modified=Локальные изменения status.resync-loop=Произошла ошибка, из-за которой не удалось пересинхронизировать аккаунт. Отключите аккаунт и повторите попытку. (Ошибка: повторная синхронизация) status.policy.2=Для этого клиента нет политики. Обратитесь к администратору вашего сервера или отключите использование этого аккаунта. status.policy.3=Неизвестное значение политики для этого клиента. Обратитесь к администратору вашего сервера или отключите использование этого аккаунта. status.policy.4=Данные политики на сервере повреждены (возможно, подделаны). Обратитесь к администратору вашего сервера или отключите использование этого аккаунта. status.policy.5=Клиент обнаружил неправильный ключ политики. Обратитесь к администратору вашего сервера или отключите использование этого аккаунта. status.provision=Резервирование не удалось со статусом <##replace.1##> status.global.101=Запрос содержит WBXML но он не может быть декодирован в XML (EAS Ошибка 101). status.global.102=Запрос содержит WBXML но он не может быть декодирован в XML (EAS Ошибка 102). status.global.103=XML, указанный в запросе, не соответствует требованиям протокола (EAS Ошибка 103). status.global.110=Сервер сообщил о внутренней ошибке, и мы не должны немедленно повторять попытку. Автоматическая периодическая синхронизация была отключена на 30 минут (Ошибка EAS 110). status.global.clientdenied=Отчеты сервера EAS <##replace.2##> (статус ##replace.1##) и не позволяют TbSync доступ к вашему аккаунту. helplink.global.clientdenied=https://github.com/jobisoft/EAS-4-TbSync/wiki/What-if-TbSync-is-blocked-by-my-server%3F status.ServerRejectedSomeItems=Сервер EAS не принял элементы ##replace.1##. status.ServerRejectedRequest=Сервер EAS отклонил наш последний запрос. status.BadItemSkipped=Плохой элемент пропущен: ##replace.1## helplink.BadItemSkipped=https://github.com/jobisoft/EAS-4-TbSync/wiki/Error:-Bad-item-skipped status.nosupportedeasversion=Сервер не поддерживает ActiveSync v2.5 или v14.0 (только ##replace.1##). TbSync не будет работать с этим ActiveSync сервером. status.notsupportedeasversion=Сервер не поддерживает выбранный ActiveSync v##replace.1## (только ##replace.2##). status.Sync.3=Неверный ключ синхронизации (статус 3), повторная синхронизация status.Sync.4=Неверный запрос (статус 4) status.Sync.5=Временные проблемы с сервером или недопустимый элемент (статус 5) status.Sync.6=Недопустимый элемент (статус 6) status.Sync.8=Объект не найден (статус 8) status.Sync.12=Изменена иерархия папок (статус 12), повторная синхронизация status.FolderDelete.3=Не удается удалить системную папку. status.FolderDelete.6=Команда не может быть выполнена, на сервере возникла ошибка. status.FolderDelete.4=Папка не существует (статус 4), повторная синхронизация status.FolderDelete.9=Неверный ключ синхронизации (статус 9), повторная синхронизация status.FolderSync.9=Неверный ключ синхронизации (статус 9), повторная синхронизация status.forbiddenTasksItemInCalendarFolder=Запрещенный элемент задачи в папке календаря (пожалуйста, исправьте) status.forbiddenCalendarItemInTasksFolder=Запрещенный элемент календаря в папке задач (пожалуйста, исправьте) config.auto=ActiveSync конфигурация сервера (Автообнаружение) config.custom=ActiveSync конфигурация сервера acl.readwrite=Читать и писать на сервер acl.readonly=Доступ к серверу только для чтения (отменить локальные изменения) autocomplete.serverdirectory = global server directory EAS-4-TbSync-1.12/_locales/ru/messages.json000066400000000000000000000006011362346370100203210ustar00rootroot00000000000000{ "extensionName": { "message": "Provider for Exchange ActiveSync" }, "extensionDescription": { "message": "Добавляет в TbSync поддержку синхронизации для учетных записей Exchange через базирующийся на http/https протокол ActiveSync (контакты, задачи и календари)." } }EAS-4-TbSync-1.12/beta-release-channel-update.json000066400000000000000000000005031362346370100215230ustar00rootroot00000000000000{ "addons": { "eas4tbsync@jobisoft.de": { "updates": [ { "version": "%VERSION%", "update_info_url": "https://github.com/jobisoft/EAS-4-TbSync/releases", "update_link": "%LINK%", "applications": { "gecko": { "strict_min_version": "68.0" } } } ] } } }EAS-4-TbSync-1.12/bootstrap.js000066400000000000000000000047051362346370100157740ustar00rootroot00000000000000/* * This file is part of EAS-4-TbSync. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ // no need to create namespace, we are in a sandbox var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); let thisID = ""; let onInitDoneObserver = { observe: async function (aSubject, aTopic, aData) { let valid = false; try { var { TbSync } = ChromeUtils.import("chrome://tbsync/content/tbsync.jsm"); valid = TbSync.enabled; } catch (e) { // If this fails, TbSync is not loaded yet and we will get the notification later again. } //load this provider add-on into TbSync if (valid) { await TbSync.providers.loadProvider(thisID, "eas", "chrome://eas4tbsync/content/provider.js"); } } } function install(data, reason) { } function uninstall(data, reason) { } function startup(data, reason) { // Possible reasons: APP_STARTUP, ADDON_ENABLE, ADDON_INSTALL, ADDON_UPGRADE, or ADDON_DOWNGRADE. thisID = data.id; Services.obs.addObserver(onInitDoneObserver, "tbsync.observer.initialized", false); // The startup of TbSync is delayed until all add-ons have called their startup(), // so all providers have registered the "tbsync.observer.initialized" observer. // Once TbSync has finished its startup, all providers will be notified (also if // TbSync itself is restarted) to load themself. // If this is not startup, we need load manually. if (reason != APP_STARTUP) { onInitDoneObserver.observe(); } } function shutdown(data, reason) { // Possible reasons: APP_SHUTDOWN, ADDON_DISABLE, ADDON_UNINSTALL, ADDON_UPGRADE, or ADDON_DOWNGRADE. // When the application is shutting down we normally don't have to clean up. if (reason == APP_SHUTDOWN) { return; } Services.obs.removeObserver(onInitDoneObserver, "tbsync.observer.initialized"); //unload this provider add-on from TbSync try { var { TbSync } = ChromeUtils.import("chrome://tbsync/content/tbsync.jsm"); TbSync.providers.unloadProvider("eas"); } catch (e) { //if this fails, TbSync has been unloaded already and has unloaded this addon as well } Services.obs.notifyObservers(null, "chrome-flush-caches", null); } EAS-4-TbSync-1.12/chrome.manifest000066400000000000000000000006751362346370100164300ustar00rootroot00000000000000content eas4tbsync content/ skin eas4tbsync classic/1.0 skin/ locale eas4tbsync bg _locales/bg/ locale eas4tbsync de _locales/de/ locale eas4tbsync en-US _locales/en-US/ locale eas4tbsync fr _locales/fr/ locale eas4tbsync hu _locales/hu/ locale eas4tbsync it _locales/it/ locale eas4tbsync pl _locales/pl/ locale eas4tbsync pt-BR _locales/pt_BR/ locale eas4tbsync ru _locales/ru/ EAS-4-TbSync-1.12/content/000077500000000000000000000000001362346370100150655ustar00rootroot00000000000000EAS-4-TbSync-1.12/content/OAuth2_1.jsm000066400000000000000000000165061362346370100171320ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ /** * Provides OAuth 2.0 authentication. * @see RFC 6749 */ var EXPORTED_SYMBOLS = ["OAuth2_1"]; const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); const { Log4Moz } = ChromeUtils.import("resource:///modules/gloda/log4moz.js"); Cu.importGlobalProperties(["fetch"]); // Only allow one connecting window per endpoint. var gConnecting = {}; function OAuth2_1(aBaseURI, aScope, aAppKey, aAppSecret) { this.authURI = aBaseURI + "oauth2/auth"; this.tokenURI = aBaseURI + "oauth2/token"; this.consumerKey = aAppKey; this.consumerSecret = aAppSecret; this.scope = aScope; this.extraAuthParams = []; this.log = Log4Moz.getConfiguredLogger("TBOAuth"); } OAuth2_1.prototype = { consumerKey: null, consumerSecret: null, completionURI: "http://localhost", requestWindowURI: "chrome://messenger/content/browserRequest.xul", requestWindowFeatures: "chrome,private,centerscreen,width=980,height=750", requestWindowTitle: "", scope: null, accessToken: null, refreshToken: null, tokenExpires: 0, connect(aSuccess, aFailure, aWithUI, aRefresh) { this.connectSuccessCallback = aSuccess; this.connectFailureCallback = aFailure; if (!aRefresh && this.accessToken) { aSuccess(); } else if (this.refreshToken) { this.requestAccessToken(this.refreshToken, true); } else { if (!aWithUI) { aFailure('{ "error": "auth_noui" }'); return; } if (gConnecting[this.authURI]) { aFailure("Window already open"); return; } this.requestAuthorization(); } }, requestAuthorization() { let params = new URLSearchParams({ response_type: "code", client_id: this.consumerKey, redirect_uri: this.completionURI, }); // The scope is optional. if (this.scope) { params.append("scope", this.scope); } for (let [name, value] of this.extraAuthParams) { params.append(name, value); } let authEndpointURI = this.authURI + "?" + params.toString(); this.log.info( "Interacting with the resource owner to obtain an authorization grant " + "from the authorization endpoint: " + authEndpointURI ); this._browserRequest = { account: this, url: authEndpointURI, _active: true, iconURI: "", cancelled() { if (!this._active) { return; } this.account.finishAuthorizationRequest(); this.account.onAuthorizationFailed( Cr.NS_ERROR_ABORT, '{ "error": "cancelled"}' ); }, loaded(aWindow, aWebProgress) { if (!this._active) { return; } this._listener = { window: aWindow, webProgress: aWebProgress, _parent: this.account, QueryInterface: ChromeUtils.generateQI([ Ci.nsIWebProgressListener, Ci.nsISupportsWeakReference, ]), _cleanUp() { this.webProgress.removeProgressListener(this); this.window.close(); delete this.window; }, _checkForRedirect(aURL) { if (aURL.indexOf(this._parent.completionURI) != 0) { return; } this._parent.finishAuthorizationRequest(); this._parent.onAuthorizationReceived(aURL); }, onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) { const wpl = Ci.nsIWebProgressListener; if (aStateFlags & (wpl.STATE_START | wpl.STATE_IS_NETWORK)) { this._checkForRedirect(aRequest.name); } }, onLocationChange(aWebProgress, aRequest, aLocation) { this._checkForRedirect(aLocation.spec); }, onProgressChange() {}, onStatusChange() {}, onSecurityChange() {}, }; aWebProgress.addProgressListener( this._listener, Ci.nsIWebProgress.NOTIFY_ALL ); aWindow.document.title = this.account.requestWindowTitle; }, }; this.wrappedJSObject = this._browserRequest; gConnecting[this.authURI] = true; Services.ww.openWindow( null, this.requestWindowURI, null, this.requestWindowFeatures, this ); }, finishAuthorizationRequest() { gConnecting[this.authURI] = false; if (!("_browserRequest" in this)) { return; } this._browserRequest._active = false; if ("_listener" in this._browserRequest) { this._browserRequest._listener._cleanUp(); } delete this._browserRequest; }, // @see RFC 6749 section 4.1.2: Authorization Response onAuthorizationReceived(aURL) { this.log.info("OAuth2_1 authorization received: url=" + aURL); let params = new URLSearchParams(aURL.split("?", 2)[1]); if (params.has("code")) { this.requestAccessToken(params.get("code"), false); } else { this.onAuthorizationFailed(null, aURL); } }, onAuthorizationFailed(aError, aData) { this.connectFailureCallback(aData); }, /** * Request a new access token, or refresh an existing one. * @param {string} aCode - The token issued to the client. * @param {boolean} aRefresh - Whether it's a refresh of a token or not. */ requestAccessToken(aCode, aRefresh) { // @see RFC 6749 section 4.1.3. Access Token Request // @see RFC 6749 section 6. Refreshing an Access Token let data = new URLSearchParams(); data.append("client_id", this.consumerKey); if (this.consumerSecret) data.append("client_secret", this.consumerSecret); if (aRefresh) { this.log.info( `Making a refresh request to the token endpoint: ${this.tokenURI}` ); data.append("grant_type", "refresh_token"); data.append("refresh_token", aCode); } else { this.log.info( `Making access token request to the token endpoint: ${this.tokenURI}` ); data.append("grant_type", "authorization_code"); data.append("code", aCode); data.append("redirect_uri", this.completionURI); } fetch(this.tokenURI, { method: "POST", cache: "no-cache", body: data, }) .then(response => response.json()) .then(result => { if ("error" in result) { // RFC 6749 section 5.2. Error Response this.log.info( `The authorization server returned an error response: ${JSON.stringify( result )}` ); this.connectFailureCallback(result); return; } // RFC 6749 section 5.1. Successful Response this.log.info("The authorization server issued an access token."); this.accessToken = result.access_token; if ("refresh_token" in result) { this.refreshToken = result.refresh_token; } if ("expires_in" in result) { this.tokenExpires = new Date().getTime() + result.expires_in * 1000; } else { this.tokenExpires = Number.MAX_VALUE; } this.connectSuccessCallback(); }) .catch(err => { this.log.info(`Connection to authorization server failed: ${err}`); this.connectFailureCallback(err); }); }, }; EAS-4-TbSync-1.12/content/includes/000077500000000000000000000000001362346370100166735ustar00rootroot00000000000000EAS-4-TbSync-1.12/content/includes/calendarsync.js000066400000000000000000000525561362346370100217140ustar00rootroot00000000000000/* * This file is part of EAS-4-TbSync. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; const cal = TbSync.lightning.cal; const ICAL = TbSync.lightning.ICAL; var Calendar = { // --------------------------------------------------------------------------- // // Read WBXML and set Thunderbird item // --------------------------------------------------------------------------- // setThunderbirdItemFromWbxml: function (tbItem, data, id, syncdata) { let item = tbItem instanceof TbSync.lightning.TbItem ? tbItem.nativeItem : tbItem; let asversion = syncdata.accountData.getAccountProperty("asversion"); item.id = id; let easTZ = new eas.tools.TimeZoneDataStructure(); eas.sync.setItemSubject(item, syncdata, data); eas.sync.setItemLocation(item, syncdata, data); eas.sync.setItemCategories(item, syncdata, data); eas.sync.setItemBody(item, syncdata, data); //timezone let stdOffset = eas.defaultTimezoneInfo.std.offset; let dstOffset = eas.defaultTimezoneInfo.dst.offset; if (data.TimeZone) { if (data.TimeZone == "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==") { TbSync.dump("Recieve TZ", "No timezone data received, using local default timezone."); } else { //load timezone struct into EAS TimeZone object easTZ.easTimeZone64 = data.TimeZone; if (TbSync.prefs.getIntPref("log.userdatalevel") > 2) TbSync.dump("Recieve TZ", item.title + easTZ.toString()); stdOffset = easTZ.utcOffset; dstOffset = easTZ.daylightBias + easTZ.utcOffset; } } if (data.StartTime) { let utc = cal.createDateTime(data.StartTime); //format "19800101T000000Z" - UTC item.startDate = utc.getInTimezone(eas.tools.guessTimezoneByStdDstOffset(stdOffset, dstOffset, easTZ.standardName)); if (data.AllDayEvent && data.AllDayEvent == "1") { item.startDate.timezone = (cal.dtz && cal.dtz.floating) ? cal.dtz.floating : cal.floating(); item.startDate.isDate = true; } } if (data.EndTime) { let utc = cal.createDateTime(data.EndTime); item.endDate = utc.getInTimezone(eas.tools.guessTimezoneByStdDstOffset(stdOffset, dstOffset, easTZ.standardName)); if (data.AllDayEvent && data.AllDayEvent == "1") { item.endDate.timezone = (cal.dtz && cal.dtz.floating) ? cal.dtz.floating : cal.floating(); item.endDate.isDate = true; } } //stamp time cannot be set and it is not needed, an updated version is only send to the server, if there was a change, so stamp will be updated //EAS Reminder item.clearAlarms(); if (data.Reminder && data.StartTime) { let alarm = cal.createAlarm(); alarm.related = Components.interfaces.calIAlarm.ALARM_RELATED_START; alarm.offset = cal.createDuration(); alarm.offset.inSeconds = (0-parseInt(data.Reminder)*60); alarm.action ="DISPLAY"; item.addAlarm(alarm); let alarmData = cal.alarms.calculateAlarmDate(item, alarm); let startDate = cal.createDateTime(data.StartTime); let nowDate = eas.tools.getNowUTC(); if (startDate.compare(nowDate) < 0) { // Mark alarm as ACK if in the past. item.alarmLastAck = nowDate; } } eas.sync.mapEasPropertyToThunderbird ("BusyStatus", "TRANSP", data, item); eas.sync.mapEasPropertyToThunderbird ("Sensitivity", "CLASS", data, item); if (data.ResponseType) { //store original EAS value item.setProperty("X-EAS-ResponseType", eas.xmltools.checkString(data.ResponseType, "0")); //some server send empty ResponseType ??? } //Attendees - remove all Attendees and re-add the ones from XML item.removeAllAttendees(); if (data.Attendees && data.Attendees.Attendee) { let att = []; if (Array.isArray(data.Attendees.Attendee)) att = data.Attendees.Attendee; else att.push(data.Attendees.Attendee); for (let i = 0; i < att.length; i++) { if (att[i].Email && eas.tools.isString(att[i].Email) && att[i].Name) { //req. let attendee = cal.createAttendee(); //is this attendee the local EAS user? let isSelf = (att[i].Email == syncdata.accountData.getAccountProperty("user")); attendee["id"] = cal.email.prependMailTo(att[i].Email); attendee["commonName"] = att[i].Name; //default is "FALSE", only if THIS attendee isSelf, use ResponseRequested (we cannot respond for other attendee) - ResponseType is not send back to the server, it is just a local information attendee["rsvp"] = (isSelf && data.ResponseRequested) ? "TRUE" : "FALSE"; //not supported in 2.5 switch (att[i].AttendeeType) { case "1": //required attendee["role"] = "REQ-PARTICIPANT"; attendee["userType"] = "INDIVIDUAL"; break; case "2": //optional attendee["role"] = "OPT-PARTICIPANT"; attendee["userType"] = "INDIVIDUAL"; break; default : //resource or unknown attendee["role"] = "NON-PARTICIPANT"; attendee["userType"] = "RESOURCE"; break; } //not supported in 2.5 - if attendeeStatus is missing, check if this isSelf and there is a ResponseType if (att[i].AttendeeStatus) attendee["participationStatus"] = eas.sync.MAP_EAS2TB.ATTENDEESTATUS[att[i].AttendeeStatus]; else if (isSelf && data.ResponseType) attendee["participationStatus"] = eas.sync.MAP_EAS2TB.ATTENDEESTATUS[data.ResponseType]; else attendee["participationStatus"] = "NEEDS-ACTION"; // status : [NEEDS-ACTION, ACCEPTED, DECLINED, TENTATIVE, DELEGATED, COMPLETED, IN-PROCESS] // rolemap : [REQ-PARTICIPANT, OPT-PARTICIPANT, NON-PARTICIPANT, CHAIR] // typemap : [INDIVIDUAL, GROUP, RESOURCE, ROOM] // Add attendee to event item.addAttendee(attendee); } else { TbSync.eventlog.add("info", syncdata, "Attendee without required name and/or email found. Skipped."); } } } if (data.OrganizerName && data.OrganizerEmail && eas.tools.isString(data.OrganizerEmail)) { //Organizer let organizer = cal.createAttendee(); organizer.id = cal.email.prependMailTo(data.OrganizerEmail); organizer.commonName = data.OrganizerName; organizer.rsvp = "FALSE"; organizer.role = "CHAIR"; organizer.userType = null; organizer.participationStatus = "ACCEPTED"; organizer.isOrganizer = true; item.organizer = organizer; } eas.sync.setItemRecurrence(item, syncdata, data, eas.tools.guessTimezoneByStdDstOffset(stdOffset, dstOffset, easTZ.standardName)); // BusyStatus is always representing the status of the current user in terms of availability. // It has nothing to do with the status of a meeting. The user could be just the organizer, but does not need to attend, so he would be free. // The correct map is between BusyStatus and TRANSP (show time as avail, busy, unset) // A new event always sets TRANSP to busy, so unset is indeed a good way to store Tentiative // However: // - EAS Meetingstatus only knows ACTIVE or CANCELLED, but not CONFIRMED or TENTATIVE // - TB STATUS has UNSET, CONFIRMED, TENTATIVE, CANCELLED // -> Special case: User sets BusyStatus to TENTIATIVE -> TRANSP is unset and also set STATUS to TENTATIVE // The TB STATUS is the correct map for EAS Meetingstatus and should be unset, if it is not a meeting EXCEPT if set to TENTATIVE let tbStatus = (data.BusyStatus && data.BusyStatus == "1" ? "TENTATIVE" : null); if (data.MeetingStatus) { //store original EAS value item.setProperty("X-EAS-MeetingStatus", data.MeetingStatus); //bitwise representation for Meeting, Received, Cancelled: let M = data.MeetingStatus & 0x1; let R = data.MeetingStatus & 0x2; let C = data.MeetingStatus & 0x4; // We can map M+C to TB STATUS (TENTATIVE, CONFIRMED, CANCELLED, unset). if (M) { if (C) tbStatus = "CANCELLED"; else if (!tbStatus) tbStatus = "CONFIRMED"; // do not override "TENTIATIVE" } //we can also use the R information, to update our fallbackOrganizerName if (!R && data.OrganizerName) syncdata.target.calendar.setProperty("fallbackOrganizerName", data.OrganizerName); } if (tbStatus) item.setProperty("STATUS", tbStatus) else item.deleteProperty("STATUS"); //TODO: attachements (needs EAS 16.0!) }, // --------------------------------------------------------------------------- // //read TB event and return its data as WBXML // --------------------------------------------------------------------------- // getWbxmlFromThunderbirdItem: function (tbItem, syncdata, isException = false) { let item = tbItem instanceof TbSync.lightning.TbItem ? tbItem.nativeItem : tbItem; let asversion = syncdata.accountData.getAccountProperty("asversion"); let wbxml = eas.wbxmltools.createWBXML("", syncdata.type); //init wbxml with "" and not with precodes, and set initial codepage let nowDate = new Date(); /* * We do not use ghosting, that means, if we do not include a value in CHANGE, it is removed from the server. * However, this does not seem to work on all fields. Furthermore, we need to include any (empty) container to blank its childs. */ //Order of tags taken from https://msdn.microsoft.com/en-us/library/dn338917(v=exchg.80).aspx //timezone if (!isException) { let easTZ = new eas.tools.TimeZoneDataStructure(); //if there is no end and no start (or both are floating) use default timezone info let tzInfo = null; if (item.startDate && item.startDate.timezone.tzid != "floating") tzInfo = eas.tools.getTimezoneInfo(item.startDate.timezone); else if (item.endDate && item.endDate.timezone.tzid != "floating") tzInfo = eas.tools.getTimezoneInfo(item.endDate.timezone); if (!tzInfo) tzInfo = eas.defaultTimezoneInfo; easTZ.utcOffset = tzInfo.std.offset; easTZ.standardBias = 0; easTZ.daylightBias = tzInfo.dst.offset - tzInfo.std.offset; easTZ.standardName = tzInfo.std.displayname; easTZ.daylightName = tzInfo.dst.displayname; if (tzInfo.std.switchdate && tzInfo.dst.switchdate) { easTZ.standardDate.wMonth = tzInfo.std.switchdate.month; easTZ.standardDate.wDay = tzInfo.std.switchdate.weekOfMonth; easTZ.standardDate.wDayOfWeek = tzInfo.std.switchdate.dayOfWeek; easTZ.standardDate.wHour = tzInfo.std.switchdate.hour; easTZ.standardDate.wMinute = tzInfo.std.switchdate.minute; easTZ.standardDate.wSecond = tzInfo.std.switchdate.second; easTZ.daylightDate.wMonth = tzInfo.dst.switchdate.month; easTZ.daylightDate.wDay = tzInfo.dst.switchdate.weekOfMonth; easTZ.daylightDate.wDayOfWeek = tzInfo.dst.switchdate.dayOfWeek; easTZ.daylightDate.wHour = tzInfo.dst.switchdate.hour; easTZ.daylightDate.wMinute = tzInfo.dst.switchdate.minute; easTZ.daylightDate.wSecond = tzInfo.dst.switchdate.second; } wbxml.atag("TimeZone", easTZ.easTimeZone64); if (TbSync.prefs.getIntPref("log.userdatalevel") > 2) TbSync.dump("Send TZ", item.title + easTZ.toString()); } //AllDayEvent (for simplicity, we always send a value) wbxml.atag("AllDayEvent", (item.startDate && item.startDate.isDate && item.endDate && item.endDate.isDate) ? "1" : "0"); //Body wbxml.append(eas.sync.getItemBody(item, syncdata)); //BusyStatus (Free, Tentative, Busy) is taken from TRANSP (busy, free, unset=tentative) //However if STATUS is set to TENTATIVE, overide TRANSP and set BusyStatus to TENTATIVE if (item.hasProperty("STATUS") && item.getProperty("STATUS") == "TENTATIVE") { wbxml.atag("BusyStatus","1"); } else { wbxml.atag("BusyStatus", eas.sync.mapThunderbirdPropertyToEas("TRANSP", "BusyStatus", item)); } //Organizer if (!isException) { if (item.organizer && item.organizer.commonName) wbxml.atag("OrganizerName", item.organizer.commonName); if (item.organizer && item.organizer.id) wbxml.atag("OrganizerEmail", cal.email.removeMailTo(item.organizer.id)); } //DtStamp in UTC wbxml.atag("DtStamp", item.stampTime ? eas.tools.getIsoUtcString(item.stampTime) : eas.tools.dateToBasicISOString(nowDate)); //EndTime in UTC wbxml.atag("EndTime", item.endDate ? eas.tools.getIsoUtcString(item.endDate) : eas.tools.dateToBasicISOString(nowDate)); //Location wbxml.atag("Location", (item.hasProperty("location")) ? item.getProperty("location") : ""); //EAS Reminder (TB getAlarms) - at least with zpush blanking by omitting works, horde does not work let alarms = item.getAlarms({}); if (alarms.length>0) { let reminder = -1; if (alarms[0].offset !== null) { reminder = 0 - alarms[0].offset.inSeconds/60; } else if (item.startDate) { let timeDiff =item.startDate.getInTimezone(eas.utcTimezone).subtractDate(alarms[0].alarmDate.getInTimezone(eas.utcTimezone)); reminder = timeDiff.inSeconds/60; TbSync.eventlog.add("info", syncdata, "Converting absolute alarm to relative alarm (not supported).", item.icalString); } if (reminder >= 0) wbxml.atag("Reminder", reminder.toString()); else TbSync.eventlog.add("info", syncdata, "Droping alarm after start date (not supported).", item.icalString); } //Sensitivity (CLASS) wbxml.atag("Sensitivity", eas.sync.mapThunderbirdPropertyToEas("CLASS", "Sensitivity", item)); //Subject (obmitting these, should remove them from the server - that does not work reliably, so we send blanks) wbxml.atag("Subject", (item.title) ? item.title : ""); //StartTime in UTC wbxml.atag("StartTime", item.startDate ? eas.tools.getIsoUtcString(item.startDate) : eas.tools.dateToBasicISOString(nowDate)); //UID (limit to 300) //each TB event has an ID, which is used as EAS serverId - however there is a second UID in the ApplicationData //since we do not have two different IDs to use, we use the same ID if (!isException) { //docs say it would be allowed in exception in 2.5, but it does not work, if present wbxml.atag("UID", item.id); } //IMPORTANT in EAS v16 it is no longer allowed to send a UID //Only allowed in exceptions in v2.5 //EAS MeetingStatus // 0 (000) The event is an appointment, which has no attendees. // 1 (001) The event is a meeting and the user is the meeting organizer. // 3 (011) This event is a meeting, and the user is not the meeting organizer; the meeting was received from someone else. // 5 (101) The meeting has been canceled and the user was the meeting organizer. // 7 (111) The meeting has been canceled. The user was not the meeting organizer; the meeting was received from someone else //there are 3 fields; Meeting, Owner, Cancelled //M can be reconstructed from #of attendees (looking at the old value is not wise, since it could have been changed) //C can be reconstucted from TB STATUS //O can be reconstructed by looking at the original value, or (if not present) by comparing EAS ownerID with TB ownerID let countAttendees = {}; let attendees = item.getAttendees(countAttendees); //if (!(isException && asversion == "2.5")) { //MeetingStatus is not supported in exceptions in EAS 2.5 if (!isException) { //Exchange 2010 does not seem to support MeetingStatus at all in exceptions if (countAttendees == 0) wbxml.atag("MeetingStatus", "0"); else { //get owner information let isReceived = false; if (item.hasProperty("X-EAS-MEETINGSTATUS")) isReceived = item.getProperty("X-EAS-MEETINGSTATUS") & 0x2; else isReceived = (item.organizer && item.organizer.id && cal.email.removeMailTo(item.organizer.id) != syncdata.accountData.getAccountProperty("user")); //either 1,3,5 or 7 if (item.hasProperty("STATUS") && item.getProperty("STATUS") == "CANCELLED") { //either 5 or 7 wbxml.atag("MeetingStatus", (isReceived ? "7" : "5")); } else { //either 1 or 3 wbxml.atag("MeetingStatus", (isReceived ? "3" : "1")); } } } //Attendees let TB_responseType = null; if (!(isException && asversion == "2.5")) { //attendees are not supported in exceptions in EAS 2.5 if (countAttendees.value > 0) { wbxml.otag("Attendees"); for (let attendee of attendees) { wbxml.otag("Attendee"); wbxml.atag("Email", cal.email.removeMailTo(attendee.id)); wbxml.atag("Name", (attendee.commonName ? attendee.commonName : cal.email.removeMailTo(attendee.id).split("@")[0])); if (asversion != "2.5") { //it's pointless to send AttendeeStatus, // - if we are the owner of a meeting, TB does not have an option to actually set the attendee status (on behalf of an attendee) in the UI // - if we are an attendee (of an invite) we cannot and should not set status of other attendees and or own status must be send through a MeetingResponse // -> all changes of attendee status are send from the server to us, either via ResponseType or via AttendeeStatus //wbxml.atag("AttendeeStatus", eas.sync.MAP_TB2EAS.ATTENDEESTATUS[attendee.participationStatus]); if (attendee.userType == "RESOURCE" || attendee.userType == "ROOM" || attendee.role == "NON-PARTICIPANT") wbxml.atag("AttendeeType","3"); else if (attendee.role == "REQ-PARTICIPANT" || attendee.role == "CHAIR") wbxml.atag("AttendeeType","1"); else wbxml.atag("AttendeeType","2"); //leftovers are optional } wbxml.ctag(); } wbxml.ctag(); } else { wbxml.atag("Attendees"); } } //Categories (see https://github.com/jobisoft/TbSync/pull/35#issuecomment-359286374) if (!isException) { wbxml.append(eas.sync.getItemCategories(item, syncdata)); } //recurrent events (implemented by Chris Allan) if (!isException) { wbxml.append(eas.sync.getItemRecurrence(item, syncdata)); } //--------------------------- //TP PRIORITY (9=LOW, 5=NORMAL, 1=HIGH) not mapable to EAS Event //TODO: attachements (needs EAS 16.0!) //https://dxr.mozilla.org/comm-central/source/calendar/base/public/calIAlarm.idl //TbSync.dump("ALARM ("+i+")", [, alarms[i].related, alarms[i].repeat, alarms[i].repeatOffset, alarms[i].repeatDate, alarms[i].action].join("|")); return wbxml.getBytes(); } /* //loop over all properties let propEnum = item.propertyEnumerator; while (propEnum.hasMoreElements()) { let prop = propEnum.getNext().QueryInterface(Components.interfaces.nsIProperty); let pname = prop.name; TbSync.dump("PROP", pname + " = " + prop.value); } */ } EAS-4-TbSync-1.12/content/includes/contactsync.js000066400000000000000000000346151362346370100215720ustar00rootroot00000000000000/* * This file is part of EAS-4-TbSync. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; const eas = TbSync.providers.eas; var Contacts = { //these functions handle categories compatible to the Category Manager add-on, which is compatible to lots of other sync tools (sogo, carddav-sync, roundcube) categoriesFromString: function (catString) { let catsArray = []; if (catString.trim().length>0) catsArray = catString.trim().split("\u001A").filter(String); return catsArray; }, categoriesToString: function (catsArray) { return catsArray.join("\u001A"); }, /* The following TB properties are not yet synced anywhere: - , FamilyName - _AimScreenName - WebPage2 (home) */ //includes all properties, which can be mapped 1-to-1 map_TB_properties_to_EAS_properties : { DisplayName: 'FileAs', FirstName: 'FirstName', LastName: 'LastName', PrimaryEmail: 'Email1Address', SecondEmail: 'Email2Address', Email3Address: 'Email3Address', WebPage1: 'WebPage', SpouseName: 'Spouse', CellularNumber: 'MobilePhoneNumber', PagerNumber: 'PagerNumber', HomeCity: 'HomeAddressCity', HomeCountry: 'HomeAddressCountry', HomeZipCode: 'HomeAddressPostalCode', HomeState: 'HomeAddressState', HomePhone: 'HomePhoneNumber', Company: 'CompanyName', Department: 'Department', JobTitle: 'JobTitle', WorkCity: 'BusinessAddressCity', WorkCountry: 'BusinessAddressCountry', WorkZipCode: 'BusinessAddressPostalCode', WorkState: 'BusinessAddressState', WorkPhone: 'BusinessPhoneNumber', //Missusing so that "Custom1" is saved to the server Custom1: 'OfficeLocation', //As in TZPUSH FaxNumber: 'HomeFaxNumber', //Custom fields added to UI AssistantName: 'AssistantName', AssistantPhoneNumber: 'AssistantPhoneNumber', BusinessFaxNumber: 'BusinessFaxNumber', Business2PhoneNumber: 'Business2PhoneNumber', Home2PhoneNumber: 'Home2PhoneNumber', CarPhoneNumber: 'CarPhoneNumber', MiddleName: 'MiddleName', RadioPhoneNumber: 'RadioPhoneNumber', OtherAddressCity: 'OtherAddressCity', OtherAddressCountry: 'OtherAddressCountry', OtherAddressPostalCode: 'OtherAddressPostalCode', OtherAddressState: 'OtherAddressState' }, //there are currently no TB fields for these values, TbSync will store (and resend) them, but will not allow to view/edit unused_EAS_properties: [ 'Suffix', 'Title', 'Alias', //pseudo field 'WeightedRank', //pseudo field 'YomiCompanyName', //japanese phonetic equivalent 'YomiFirstName', //japanese phonetic equivalent 'YomiLastName', //japanese phonetic equivalent 'CompressedRTF' ], map_TB_properties_to_EAS_properties2 : { NickName: 'NickName', //Missusing so that "Custom2,3,4" is saved to the server Custom2: 'CustomerId', Custom3: 'GovernmentId', Custom4: 'AccountName', //custom fields added to UI IMAddress: 'IMAddress', IMAddress2: 'IMAddress2', IMAddress3: 'IMAddress3', ManagerName: 'ManagerName', CompanyMainPhone: 'CompanyMainPhone', MMS: 'MMS' }, // --------------------------------------------------------------------------- // // Read WBXML and set Thunderbird item // --------------------------------------------------------------------------- // setThunderbirdItemFromWbxml: function (abItem, data, id, syncdata) { let asversion = syncdata.accountData.getAccountProperty("asversion"); abItem.primaryKey = id; //loop over all known TB properties which map 1-to-1 (two EAS sets Contacts and Contacts2) for (let set=0; set < 2; set++) { let properties = (set == 0) ? this.TB_properties : this.TB_properties2; for (let p=0; p < properties.length; p++) { let TB_property = properties[p]; let EAS_property = (set == 0) ? this.map_TB_properties_to_EAS_properties[TB_property] : this.map_TB_properties_to_EAS_properties2[TB_property]; let value = eas.xmltools.checkString(data[EAS_property]); //is this property part of the send data? if (value) { //do we need to manipulate the value? switch (EAS_property) { case "Email1Address": case "Email2Address": case "Email3Address": let parsedInput = MailServices.headerParser.makeFromDisplayAddress(value); let fixedValue = (parsedInput && parsedInput[0] && parsedInput[0].email) ? parsedInput[0].email : value; if (fixedValue != value) { if (TbSync.prefs.getIntPref("log.userdatalevel") > 2) TbSync.dump("Parsing email display string via RFC 2231 and RFC 2047 ("+EAS_property+")", value + " -> " + fixedValue); value = fixedValue; } break; } abItem.setProperty(TB_property, value); } else { //clear abItem.deleteProperty(TB_property); } } } //take care of birthday and anniversary let dates = []; dates.push(["Birthday", "BirthDay", "BirthMonth", "BirthYear"]); //EAS, TB1, TB2, TB3 dates.push(["Anniversary", "AnniversaryDay", "AnniversaryMonth", "AnniversaryYear"]); for (let p=0; p < dates.length; p++) { let value = eas.xmltools.checkString(data[dates[p][0]]); if (value == "") { //clear abItem.deleteProperty(dates[p][1]); abItem.deleteProperty(dates[p][2]); abItem.deleteProperty(dates[p][3]); } else { //set let dateObj = new Date(value); abItem.setProperty(dates[p][3], dateObj.getFullYear().toString()); abItem.setProperty(dates[p][2], (dateObj.getMonth()+1).toString()); abItem.setProperty(dates[p][1], dateObj.getDate().toString()); } } //take care of multiline address fields let streets = []; let seperator = String.fromCharCode(syncdata.accountData.getAccountProperty("seperator")); // options are 44 (,) or 10 (\n) streets.push(["HomeAddressStreet", "HomeAddress", "HomeAddress2"]); //EAS, TB1, TB2 streets.push(["BusinessAddressStreet", "WorkAddress", "WorkAddress2"]); streets.push(["OtherAddressStreet", "OtherAddress", "OtherAddress2"]); for (let p=0; p < streets.length; p++) { let value = eas.xmltools.checkString(data[streets[p][0]]); if (value == "") { //clear abItem.deleteProperty(streets[p][1]); abItem.deleteProperty(streets[p][2]); } else { //set let lines = value.split(seperator); abItem.setProperty(streets[p][1], lines.shift()); abItem.setProperty(streets[p][2], lines.join(seperator)); } } //take care of photo if (data.Picture) { abItem.addPhoto(id, eas.xmltools.nodeAsArray(data.Picture)[0], "jpg"); //Kerio sends Picture as container } //take care of notes if (asversion == "2.5") { abItem.setProperty("Notes", eas.xmltools.checkString(data.Body)); } else { if (data.Body && data.Body.Data) abItem.setProperty("Notes", eas.xmltools.checkString(data.Body.Data)); else abItem.setProperty("Notes", ""); } //take care of categories and children let containers = []; containers.push(["Categories", "Category"]); containers.push(["Children", "Child"]); for (let c=0; c < containers.length; c++) { if (data[containers[c][0]] && data[containers[c][0]][containers[c][1]]) { let cats = []; if (Array.isArray(data[containers[c][0]][containers[c][1]])) cats = data[containers[c][0]][containers[c][1]]; else cats.push(data[containers[c][0]][containers[c][1]]); abItem.setProperty(containers[c][0], this.categoriesToString(cats)); } } //take care of unmapable EAS option (Contact) for (let i=0; i < this.unused_EAS_properties.length; i++) { if (data[this.unused_EAS_properties[i]]) abItem.setProperty("EAS-" + this.unused_EAS_properties[i], data[this.unused_EAS_properties[i]]); } //further manipulations if (syncdata.accountData.getAccountProperty("displayoverride")) { abItem.setProperty("DisplayName", abItem.getProperty("FirstName", "") + " " + abItem.getProperty("LastName", "")); if (abItem.getProperty("DisplayName", "" ) == " " ) abItem.setProperty("DisplayName", abItem.getProperty("Company", abItem.getProperty("PrimaryEmail", ""))); } }, // --------------------------------------------------------------------------- // //read TB event and return its data as WBXML // --------------------------------------------------------------------------- // getWbxmlFromThunderbirdItem: function (abItem, syncdata, isException = false) { let asversion = syncdata.accountData.getAccountProperty("asversion"); let wbxml = eas.wbxmltools.createWBXML("", syncdata.type); //init wbxml with "" and not with precodes, and set initial codepage let nowDate = new Date(); //loop over all known TB properties which map 1-to-1 (send empty value if not set) for (let p=0; p < this.TB_properties.length; p++) { let TB_property = this.TB_properties[p]; let EAS_property = this.map_TB_properties_to_EAS_properties[TB_property]; let value = abItem.getProperty(TB_property,""); if (value) wbxml.atag(EAS_property, value); } //take care of birthday and anniversary let dates = []; dates.push(["Birthday", "BirthDay", "BirthMonth", "BirthYear"]); dates.push(["Anniversary", "AnniversaryDay", "AnniversaryMonth", "AnniversaryYear"]); for (let p=0; p < dates.length; p++) { let year = abItem.getProperty(dates[p][3], ""); let month = abItem.getProperty(dates[p][2], ""); let day = abItem.getProperty(dates[p][1], ""); if (year && month && day) { //set if (month.length<2) month="0"+month; if (day.length<2) day="0"+day; wbxml.atag(dates[p][0], year + "-" + month + "-" + day + "T00:00:00.000Z"); } } //take care of multiline address fields let streets = []; let seperator = String.fromCharCode(syncdata.accountData.getAccountProperty("seperator")); // options are 44 (,) or 10 (\n) streets.push(["HomeAddressStreet", "HomeAddress", "HomeAddress2"]); //EAS, TB1, TB2 streets.push(["BusinessAddressStreet", "WorkAddress", "WorkAddress2"]); streets.push(["OtherAddressStreet", "OtherAddress", "OtherAddress2"]); for (let p=0; p < streets.length; p++) { let values = []; let s1 = abItem.getProperty(streets[p][1], ""); let s2 = abItem.getProperty(streets[p][2], ""); if (s1) values.push(s1); if (s2) values.push(s2); if (values.length>0) wbxml.atag(streets[p][0], values.join(seperator)); } //take care of photo if (abItem.getProperty("PhotoType", "") == "file") { wbxml.atag("Picture", abItem.getPhoto()); } //take care of unmapable EAS option for (let i=0; i < this.unused_EAS_properties.length; i++) { let value = abItem.getProperty("EAS-" + this.unused_EAS_properties[i], ""); if (value) wbxml.atag(this.unused_EAS_properties[i], value); } //take care of categories and children let containers = []; containers.push(["Categories", "Category"]); containers.push(["Children", "Child"]); for (let c=0; c < containers.length; c++) { let cats = abItem.getProperty(containers[c][0], ""); if (cats) { let catsArray = this.categoriesFromString(cats); wbxml.otag(containers[c][0]); for (let ca=0; ca < catsArray.length; ca++) wbxml.atag(containers[c][1], catsArray[ca]); wbxml.ctag(); } } //take care of notes - SWITCHING TO AirSyncBase (if 2.5, we still need Contact group here!) let description = abItem.getProperty("Notes", ""); if (asversion == "2.5") { wbxml.atag("Body", description); } else { wbxml.switchpage("AirSyncBase"); wbxml.otag("Body"); wbxml.atag("Type", "1"); wbxml.atag("EstimatedDataSize", "" + description.length); wbxml.atag("Data", description); wbxml.ctag(); } //take care of Contacts2 group - SWITCHING TO CONTACTS2 wbxml.switchpage("Contacts2"); //loop over all known TB properties of EAS group Contacts2 (send empty value if not set) for (let p=0; p < this.TB_properties2.length; p++) { let TB_property = this.TB_properties2[p]; let EAS_property = this.map_TB_properties_to_EAS_properties2[TB_property]; let value = abItem.getProperty(TB_property,""); if (value) wbxml.atag(EAS_property, value); } return wbxml.getBytes(); } } Contacts.TB_properties = Object.keys(Contacts.map_TB_properties_to_EAS_properties); Contacts.TB_properties2 = Object.keys(Contacts.map_TB_properties_to_EAS_properties2); EAS-4-TbSync-1.12/content/includes/network.js000066400000000000000000002032201362346370100207210ustar00rootroot00000000000000/* * This file is part of EAS-4-TbSync. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; var { OAuth2_1 } = ChromeUtils.import("chrome://eas4tbsync/content/OAuth2_1.jsm"); var network = { getEasURL: function(accountData) { let protocol = (accountData.getAccountProperty("https")) ? "https://" : "http://"; let h = protocol + accountData.getAccountProperty("host"); while (h.endsWith("/")) { h = h.slice(0,-1); } if (h.endsWith("Microsoft-Server-ActiveSync")) return h; return h + "/Microsoft-Server-ActiveSync"; }, getAuthData: function(accountData) { let authData = { // This is the host for the password manager, which could be different from // the actual host property of the account. For EAS we want to couple the password // with the ACCOUNT and not any sort of url, which could change via autodiscover // at any time. get host() { return "TbSync#" + accountData.accountID; }, get user() { return accountData.getAccountProperty("user"); }, get password() { return TbSync.passwordManager.getLoginInfo(this.host, "TbSync/EAS", this.user); }, updateLoginData: function(newUsername, newPassword) { let oldUsername = this.user; TbSync.passwordManager.updateLoginInfo(this.host, "TbSync/EAS", oldUsername, newUsername, newPassword); // Also update the username of this account. Add dedicated username setter? accountData.setAccountProperty("user", newUsername); }, removeLoginData: function() { TbSync.passwordManager.removeLoginInfos(this.host, "TbSync/EAS"); } }; return authData; }, // prepare and patch OAuth2 object getOAuthObj: function(configObject = null) { let accountname, user, host, accountID, servertype; let accountData = (configObject && configObject.hasOwnProperty("accountData")) ? configObject.accountData : null; if (accountData) { accountname = accountData.getAccountProperty("accountname"); user = accountData.getAccountProperty("user"); host = accountData.getAccountProperty("host"); servertype = accountData.getAccountProperty("servertype"); accountID = accountData.accountID; } else { accountname = (configObject && configObject.hasOwnProperty("accountname")) ? configObject.accountname : ""; user = (configObject && configObject.hasOwnProperty("user")) ? configObject.user : ""; host = (configObject && configObject.hasOwnProperty("host")) ? configObject.host : ""; servertype = (configObject && configObject.hasOwnProperty("servertype")) ? configObject.servertype : ""; accountID = ""; } if (!["office365"].includes(servertype)) return null; let config = {}; switch (host) { case "outlook.office365.com": config = { auth_uri : "https://login.microsoftonline.com/common/oauth2/v2.0/authorize", token_uri : "https://login.microsoftonline.com/common/oauth2/v2.0/token", redirect_uri : "https://login.microsoftonline.com/common/oauth2/nativeclient", scope : "offline_access https://outlook.office.com/EAS.AccessAsUser.All", client_id : "2980deeb-7460-4723-864a-f9b0f10cd992", } break; default: return null; } let oauth = new OAuth2_1("", config.scope, config.client_id, config.client_secret); oauth.requestWindowFeatures = "chrome,private,centerscreen,width=500,height=750"; // The v2 endpoints are different and need manual override oauth.authURI = config.auth_uri ; oauth.tokenURI = config.token_uri; oauth.completionURI = config.redirect_uri; oauth.extraAuthParams = [ ["prompt", "consent"], ["login_hint", user], ]; if (accountname) { oauth.requestWindowTitle = "TbSync account <" + accountname + "> requests authorization."; } else { oauth.requestWindowTitle = "A TbSync account requests authorization."; } /* Adding custom methods to the oauth object */ oauth.asyncConnect = async function(rv) { let self = this; rv.error = ""; rv.tokens = ""; // If multiple resources need to authenticate they will all end here, even though they // might share the same token. Due to the async nature, each process will refresh // "its own" token again, which is not needed. We force clear the token here and each // final connect process will actually check the acccessToken and abort the refresh, // if it is already there, generated by some other process. if (self.accessToken) self.accessToken = ""; try { await new Promise(function(resolve, reject) { // refresh = false will do nothing and resolve immediately, if an accessToken // exists already, which must have been generated by another process, as // we cleared it beforehand. self.connect(resolve, reject, /* with UI */ true, /* refresh */ false); }); rv.tokens = self.tokens; return true; } catch (e) { rv.error = eas.tools.isString(e) ? e : JSON.stringify(e); } try { switch (JSON.parse(rv.error).error) { case "invalid_grant": self.accessToken = ""; self.refreshToken = ""; return true; case "cancelled": rv.error = "OAuthAbortError"; break; default: rv.error = "OAuthServerError::"+rv.error; break; } } catch (e) { rv.error = "OAuthServerError::"+rv.error; Components.utils.reportError(e); } return false; }; oauth.isExpired = function() { const OAUTH_GRACE_TIME = 30 * 1000; return (this.tokenExpires - OAUTH_GRACE_TIME < new Date().getTime()); }; const OAUTHVALUES = [ ["access", "", "accessToken"], ["refresh", "", "refreshToken"], ["expires", Number.MAX_VALUE, "tokenExpires"], ]; // returns a JSON string containing all the oauth values Object.defineProperty(oauth, "tokens", { get: function() { let tokensObj = {}; for (let oauthValue of OAUTHVALUES) { // use the system value or if not defined the default tokensObj[oauthValue[0]] = this[oauthValue[2]] || oauthValue[1]; } return JSON.stringify(tokensObj); }, enumerable: true, }); if (accountData) { // authData allows us to access the password manager values belonging to this account/calendar // simply by authdata.username and authdata.password oauth.authData = TbSync.providers.eas.network.getAuthData(accountData); oauth.parseAndSanitizeTokenString = function(tokenString) { let _tokensObj = {}; try { _tokensObj = JSON.parse(tokenString); } catch (e) {} let tokensObj = {}; for (let oauthValue of OAUTHVALUES) { // use the provided value or if not defined the default tokensObj[oauthValue[0]] = (_tokensObj && _tokensObj.hasOwnProperty(oauthValue[0])) ? _tokensObj[oauthValue[0]] : oauthValue[1]; } return tokensObj; }; // Define getter/setter to act on the password manager password value belonging to this account/calendar for (let oauthValue of OAUTHVALUES) { Object.defineProperty(oauth, oauthValue[2], { get: function() { return this.parseAndSanitizeTokenString(this.authData.password)[oauthValue[0]]; }, set: function(val) { let tokens = this.parseAndSanitizeTokenString(this.authData.password); let valueChanged = (val != tokens[oauthValue[0]]) if (valueChanged) { tokens[oauthValue[0]] = val; this.authData.updateLoginData(this.authData.user, JSON.stringify(tokens)); } }, enumerable: true, }); } } return oauth; }, getOAuthValue: function(currentTokenString, type = "access") { try { let tokens = JSON.parse(currentTokenString); if (tokens.hasOwnProperty(type)) return tokens[type]; } catch (e) { //NOOP } return ""; }, sendRequest: async function (wbxml, command, syncData, allowSoftFail = false) { let ALLOWED_RETRIES = { PasswordPrompt : 3, NetworkError : 1, } let rv = {}; let oauthData = eas.network.getOAuthObj({ accountData: syncData.accountData }); let syncState = syncData.getSyncState().state; for (;;) { if (rv.errorType) { let retry = false; if (ALLOWED_RETRIES[rv.errorType] > 0) { ALLOWED_RETRIES[rv.errorType]--; switch (rv.errorType) { case "PasswordPrompt": { if (oauthData) { oauthData.accessToken = ""; retry = true; } else { let authData = eas.network.getAuthData(syncData.accountData); syncData.setSyncState("passwordprompt"); let promptData = { windowID: "auth:" + syncData.accountData.accountID, accountname: syncData.accountData.getAccountProperty("accountname"), usernameLocked: syncData.accountData.isConnected(), username: authData.user } let credentials = await TbSync.passwordManager.asyncPasswordPrompt(promptData, eas.openWindows); if (credentials) { retry = true; authData.updateLoginData(credentials.username, credentials.password); } } } break; case "NetworkError": { // Could not connect to server. Can we rerun autodiscover? // Note: Autodiscover is currently not supported by OAuth if (syncData.accountData.getAccountProperty( "servertype") == "auto" && !oauthData) { let errorcode = await eas.network.updateServerConnectionViaAutodiscover(syncData); console.log("ERR: " + errorcode); if (errorcode == 200) { // autodiscover succeeded, retry with new data retry = true; } else if (errorcode == 401) { // manipulate rv to run password prompt ALLOWED_RETRIES[rv.errorType]++; rv.errorType = "PasswordPrompt"; rv.errorObj = eas.sync.finish("error", "401"); continue; // with the next loop, skip connection to the server } } } break; } } if (!retry) throw rv.errorObj; } // check OAuth situation before connecting if (oauthData && (!oauthData.accessToken || oauthData.isExpired())) { syncData.setSyncState("oauthprompt"); let _rv = {} if (!(await oauthData.asyncConnect(_rv))) { throw eas.sync.finish("error", _rv.error); } } // Return to original syncstate if (syncState != syncData.getSyncState().state) { syncData.setSyncState(syncState); } rv = await this.sendRequestPromise(wbxml, command, syncData, allowSoftFail); if (rv.errorType) { // make sure, there is a valid ALLOWED_RETRIES setting for the returned error if (rv.errorType && !ALLOWED_RETRIES.hasOwnProperty(rv.errorType)) { ALLOWED_RETRIES[rv.errorType] = 1; } } else { return rv; } } }, sendRequestPromise: function (wbxml, command, syncData, allowSoftFail = false) { let msg = "Sending data <" + syncData.getSyncState().state + "> for " + syncData.accountData.getAccountProperty("accountname"); if (syncData.currentFolderData) msg += " (" + syncData.currentFolderData.getFolderProperty("foldername") + ")"; syncData.request = eas.network.logXML(wbxml, msg); syncData.response = ""; let connection = eas.network.getAuthData(syncData.accountData); let userAgent = syncData.accountData.getAccountProperty("useragent"); //plus calendar.useragent.extra = Lightning/5.4.5.2 let deviceType = syncData.accountData.getAccountProperty("devicetype"); let deviceId = syncData.accountData.getAccountProperty("deviceId"); TbSync.dump("Sending (EAS v"+syncData.accountData.getAccountProperty("asversion") +")", "POST " + eas.network.getEasURL(syncData.accountData) + '?Cmd=' + command + '&User=' + encodeURIComponent(connection.user) + '&DeviceType=' +deviceType + '&DeviceId=' + deviceId, true); const textEncoder = new TextEncoder(); let encoded = textEncoder.encode(wbxml); // console.log("wbxml: " + wbxml); // console.log("byte array: " + encoded); // console.log("length :" + wbxml.length + " vs " + encoded.byteLength + " vs " + encoded.length); return new Promise(function(resolve,reject) { // Create request handler - API changed with TB60 to new XMKHttpRequest() syncData.req = new XMLHttpRequest(); syncData.req.mozBackgroundRequest = true; syncData.req.open("POST", eas.network.getEasURL(syncData.accountData) + '?Cmd=' + command + '&User=' + encodeURIComponent(connection.user) + '&DeviceType=' +encodeURIComponent(deviceType) + '&DeviceId=' + deviceId, true); syncData.req.overrideMimeType("text/plain"); syncData.req.setRequestHeader("User-Agent", userAgent); syncData.req.setRequestHeader("Content-Type", "application/vnd.ms-sync.wbxml"); if (connection.password) { if (eas.network.getOAuthObj({ accountData: syncData.accountData })) { syncData.req.setRequestHeader("Authorization", 'Bearer ' + eas.network.getOAuthValue(connection.password, "access")); } else { syncData.req.setRequestHeader("Authorization", 'Basic ' + TbSync.tools.b64encode(connection.user + ':' + connection.password)); } } if (syncData.accountData.getAccountProperty("asversion") == "2.5") { syncData.req.setRequestHeader("MS-ASProtocolVersion", "2.5"); } else { syncData.req.setRequestHeader("MS-ASProtocolVersion", "14.0"); } syncData.req.setRequestHeader("Content-Length", encoded.length); if (syncData.accountData.getAccountProperty("provision")) { syncData.req.setRequestHeader("X-MS-PolicyKey", syncData.accountData.getAccountProperty("policykey")); TbSync.dump("PolicyKey used", syncData.accountData.getAccountProperty("policykey")); } syncData.req.timeout = eas.Base.getConnectionTimeout(); syncData.req.ontimeout = function () { if (allowSoftFail) { resolve(""); } else { reject(eas.sync.finish("error", "timeout")); } }; syncData.req.onerror = function () { if (allowSoftFail) { resolve(""); } else { let error = TbSync.network.createTCPErrorFromFailedXHR(syncData.req) || "networkerror"; let rv = {}; rv.errorObj = eas.sync.finish("error", error); rv.errorType = "NetworkError"; resolve(rv); } }; syncData.req.onload = function() { let response = syncData.req.responseText; switch(syncData.req.status) { case 200: //OK let msg = "Receiving data <" + syncData.getSyncState().state + "> for " + syncData.accountData.getAccountProperty("accountname"); if (syncData.currentFolderData) msg += " (" + syncData.currentFolderData.getFolderProperty("foldername") + ")"; syncData.response = eas.network.logXML(response, msg); //What to do on error? IS this an error? Yes! if (!allowSoftFail && response.length !== 0 && response.substr(0, 4) !== String.fromCharCode(0x03, 0x01, 0x6A, 0x00)) { TbSync.dump("Recieved Data", "Expecting WBXML but got junk (request status = " + syncData.req.status + ", ready state = " + syncData.req.readyState + "\n>>>>>>>>>>\n" + response + "\n<<<<<<<<<<\n"); reject(eas.sync.finish("warning", "invalid")); } else { resolve(response); } break; case 401: // AuthError case 403: // Forbiddden (some servers send forbidden on AuthError, like Freenet) let rv = {}; rv.errorObj = eas.sync.finish("error", "401"); rv.errorType = "PasswordPrompt"; resolve(rv); break; case 449: // Request for new provision (enable it if needed) //enable provision syncData.accountData.setAccountProperty("provision", true); syncData.accountData.resetAccountProperty("policykey"); reject(eas.sync.finish("resyncAccount", syncData.req.status)); break; case 451: // Redirect - update host and login manager let header = syncData.req.getResponseHeader("X-MS-Location"); let newHost = header.slice(header.indexOf("://") + 3, header.indexOf("/M")); TbSync.dump("redirect (451)", "header: " + header + ", oldHost: " +syncData.accountData.getAccountProperty("host") + ", newHost: " + newHost); syncData.accountData.setAccountProperty("host", newHost); reject(eas.sync.finish("resyncAccount", syncData.req.status)); break; default: if (allowSoftFail) { resolve(""); } else { reject(eas.sync.finish("error", "httperror::" + syncData.req.status)); } } }; syncData.req.send(encoded); }); }, // RESPONSE EVALUATION logXML : function (wbxml, what) { let rawxml = eas.wbxmltools.convert2xml(wbxml); let xml = null; if (rawxml) { xml = rawxml.split('><').join('>\n<'); } //include xml in log, if userdatalevel 2 or greater if (TbSync.prefs.getIntPref("log.userdatalevel") > 1) { //log raw wbxml if userdatalevel is 3 or greater if (TbSync.prefs.getIntPref("log.userdatalevel") > 2) { let charcodes = []; for (let i=0; i< wbxml.length; i++) charcodes.push(wbxml.charCodeAt(i).toString(16)); let bytestring = charcodes.join(" "); TbSync.dump("WBXML: " + what, "\n" + bytestring); } if (xml) { //raw xml is save xml with all special chars in user data encoded by encodeURIComponent - KEEP that in order to be able to analyze logged XML //let xml = decodeURIComponent(rawxml.split('><').join('>\n<')); TbSync.dump("XML: " + what, "\n" + xml); } else { TbSync.dump("XML: " + what, "\nFailed to convert WBXML to XML!\n"); } } return xml; }, //returns false on parse error and null on empty response (if allowed) getDataFromResponse: function (wbxml, allowEmptyResponse = !eas.flags.allowEmptyResponse) { //check for empty wbxml if (wbxml.length === 0) { if (allowEmptyResponse) return null; else throw eas.sync.finish("warning", "empty-response"); } //convert to save xml (all special chars in user data encoded by encodeURIComponent) and check for parse errors let xml = eas.wbxmltools.convert2xml(wbxml); if (xml === false) { throw eas.sync.finish("warning", "wbxml-parse-error"); } //retrieve data and check for empty data (all returned data fields are already decoded by decodeURIComponent) let wbxmlData = eas.xmltools.getDataFromXMLString(xml); if (wbxmlData === null) { if (allowEmptyResponse) return null; else throw eas.sync.finish("warning", "response-contains-no-data"); } //debug eas.xmltools.printXmlData(wbxmlData, false); //do not include ApplicationData in log return wbxmlData; }, updateSynckey: function (syncData, wbxmlData) { let synckey = eas.xmltools.getWbxmlDataField(wbxmlData,"Sync.Collections.Collection.SyncKey"); if (synckey) { // This COULD be a cause of problems... syncData.synckey = synckey; syncData.currentFolderData.setFolderProperty("synckey", synckey); } else { throw eas.sync.finish("error", "wbxmlmissingfield::Sync.Collections.Collection.SyncKey"); } }, checkStatus : function (syncData, wbxmlData, path, rootpath="", allowSoftFail = false) { //path is relative to wbxmlData //rootpath is the absolute path and must be specified, if wbxml is not the root node and thus path is not the rootpath let status = eas.xmltools.getWbxmlDataField(wbxmlData,path); let fullpath = (rootpath=="") ? path : rootpath; let elements = fullpath.split("."); let type = elements[0]; //check if fallback to main class status: the answer could just be a "Sync.Status" instead of a "Sync.Collections.Collections.Status" if (status === false) { let mainStatus = eas.xmltools.getWbxmlDataField(wbxmlData, type + "." + elements[elements.length-1]); if (mainStatus === false) { //both possible status fields are missing, abort throw eas.sync.finish("warning", "wbxmlmissingfield::" + fullpath, "Request:\n" + syncData.request + "\n\nResponse:\n" + syncData.response); } else { //the alternative status could be extracted status = mainStatus; fullpath = type + "." + elements[elements.length-1]; } } //check if all is fine (not bad) if (status == "1") { return ""; } TbSync.dump("wbxml status check", type + ": " + fullpath + " = " + status); //handle errrors based on type let statusType = type+"."+status; switch (statusType) { case "Sync.3": /* MUST return to SyncKey element value of 0 for the collection. The client SHOULD either delete any items that were added since the last successful Sync or the client MUST add those items back to the server after completing the full resynchronization */ TbSync.eventlog.add("warning", syncData.eventLogInfo, "Forced Folder Resync", "Request:\n" + syncData.request + "\n\nResponse:\n" + syncData.response); syncData.currentFolderData.remove(); throw eas.sync.finish("resyncFolder", statusType); case "Sync.4": //Malformed request case "Sync.5": //Temporary server issues or invalid item case "Sync.6": //Invalid item case "Sync.8": //Object not found if (allowSoftFail) return statusType; throw eas.sync.finish("warning", statusType, "Request:\n" + syncData.request + "\n\nResponse:\n" + syncData.response); case "Sync.7": //The client has changed an item for which the conflict policy indicates that the server's changes take precedence. case "Sync.9": //User account could be out of disk space, also send if no write permission (TODO) return ""; case "FolderDelete.3": // special system folder - fatal error case "FolderDelete.6": // error on server throw eas.sync.finish("warning", statusType, "Request:\n" + syncData.request + "\n\nResponse:\n" + syncData.response); case "FolderDelete.4": // folder does not exist - resync ( we allow delete only if folder is not subscribed ) case "FolderDelete.9": // invalid synchronization key - resync case "FolderSync.9": // invalid synchronization key - resync case "Sync.12": // folder hierarchy changed { let folders = syncData.accountData.getAllFoldersIncludingCache(); for (let folder of folders) { folder.remove(); } // reset account eas.Base.onEnableAccount(syncData.accountData); throw eas.sync.finish("resyncAccount", statusType, "Request:\n" + syncData.request + "\n\nResponse:\n" + syncData.response); } } //handle global error (https://msdn.microsoft.com/en-us/library/ee218647(v=exchg.80).aspx) let descriptions = {}; switch(status) { case "101": //invalid content case "102": //invalid wbxml case "103": //invalid xml throw eas.sync.finish("error", "global." + status, "Request:\n" + syncData.request + "\n\nResponse:\n" + syncData.response); case "109": descriptions["109"]="DeviceTypeMissingOrInvalid"; case "112": descriptions["112"]="ActiveDirectoryAccessDenied"; case "126": descriptions["126"]="UserDisabledForSync"; case "127": descriptions["127"]="UserOnNewMailboxCannotSync"; case "128": descriptions["128"]="UserOnLegacyMailboxCannotSync"; case "129": descriptions["129"]="DeviceIsBlockedForThisUser"; case "130": descriptions["120"]="AccessDenied"; case "131": descriptions["131"]="AccountDisabled"; throw eas.sync.finish("error", "global.clientdenied"+ "::" + status + "::" + descriptions[status]); case "110": //server error - abort and disable autoSync for 30 minutes { let noAutosyncUntil = 30 * 60000 + Date.now(); let humanDate = new Date(noAutosyncUntil).toUTCString(); syncData.accountData.setAccountProperty("noAutosyncUntil", noAutosyncUntil); throw eas.sync.finish("error", "global." + status, "AutoSync disabled until: " + humanDate + " \n\nRequest:\n" + syncData.request + "\n\nResponse:\n" + syncData.response); /* // reset account * let folders = syncData.accountData.getAllFoldersIncludingCache(); * for (let folder of folders) { * folder.remove(); * } * // reset account * eas.Base.onEnableAccount(syncData.accountData); * throw eas.sync.finish("resyncAccount", statusType, "Request:\n" + syncData.request + "\n\nResponse:\n" + syncData.response); */ } case "141": // The device is not provisionable case "142": // DeviceNotProvisioned case "143": // PolicyRefresh case "144": // InvalidPolicyKey //enable provision syncData.accountData.setAccountProperty("provision", true); syncData.accountData.resetAccountProperty("policykey"); throw eas.sync.finish("resyncAccount", statusType); default: if (allowSoftFail) return statusType; throw eas.sync.finish("error", statusType, "Request:\n" + syncData.request + "\n\nResponse:\n" + syncData.response); } }, // WBXML COMM STUFF setDeviceInformation: async function (syncData) { if (syncData.accountData.getAccountProperty("asversion") == "2.5" || !syncData.accountData.getAccountProperty("allowedEasCommands").split(",").includes("Settings")) { return; } syncData.setSyncState("prepare.request.setdeviceinfo"); let wbxml = wbxmltools.createWBXML(); wbxml.switchpage("Settings"); wbxml.otag("Settings"); wbxml.otag("DeviceInformation"); wbxml.otag("Set"); wbxml.atag("Model", "Computer"); wbxml.atag("FriendlyName", "TbSync on Device " + syncData.accountData.getAccountProperty("deviceId").substring(4)); wbxml.atag("OS", OS.Constants.Sys.Name); wbxml.atag("UserAgent", syncData.accountData.getAccountProperty("useragent")); wbxml.ctag(); wbxml.ctag(); wbxml.ctag(); syncData.setSyncState("send.request.setdeviceinfo"); let response = await eas.network.sendRequest(wbxml.getBytes(), "Settings", syncData); syncData.setSyncState("eval.response.setdeviceinfo"); let wbxmlData = eas.network.getDataFromResponse(response); eas.network.checkStatus(syncData, wbxmlData,"Settings.Status"); }, getPolicykey: async function (syncData) { //build WBXML to request provision syncData.setSyncState("prepare.request.provision"); let wbxml = eas.wbxmltools.createWBXML(); wbxml.switchpage("Provision"); wbxml.otag("Provision"); wbxml.otag("Policies"); wbxml.otag("Policy"); wbxml.atag("PolicyType", (syncData.accountData.getAccountProperty("asversion") == "2.5") ? "MS-WAP-Provisioning-XML" : "MS-EAS-Provisioning-WBXML" ); wbxml.ctag(); wbxml.ctag(); wbxml.ctag(); for (let loop=0; loop < 2; loop++) { syncData.setSyncState("send.request.provision"); let response = await eas.network.sendRequest(wbxml.getBytes(), "Provision", syncData); syncData.setSyncState("eval.response.provision"); let wbxmlData = eas.network.getDataFromResponse(response); let policyStatus = eas.xmltools.getWbxmlDataField(wbxmlData, "Provision.Policies.Policy.Status"); let provisionStatus = eas.xmltools.getWbxmlDataField(wbxmlData, "Provision.Status"); if (provisionStatus === false) { throw eas.sync.finish("error", "wbxmlmissingfield::Provision.Status"); } else if (provisionStatus != "1") { //dump policy status as well if (policyStatus) TbSync.dump("PolicyKey","Received policy status: " + policyStatus); throw eas.sync.finish("error", "provision::" + provisionStatus); } //reaching this point: provision status was ok let policykey = eas.xmltools.getWbxmlDataField(wbxmlData,"Provision.Policies.Policy.PolicyKey"); switch (policyStatus) { case false: throw eas.sync.finish("error", "wbxmlmissingfield::Provision.Policies.Policy.Status"); case "2": //server does not have a policy for this device: disable provisioning syncData.accountData.setAccountProperty("provision", false) syncData.accountData.resetAccountProperty("policykey"); throw eas.sync.finish("resyncAccount", "NoPolicyForThisDevice"); case "1": if (policykey === false) { throw eas.sync.finish("error", "wbxmlmissingfield::Provision.Policies.Policy.PolicyKey"); } TbSync.dump("PolicyKey","Received policykey (" + loop + "): " + policykey); syncData.accountData.setAccountProperty("policykey", policykey); break; default: throw eas.sync.finish("error", "policy." + policyStatus); } //build WBXML to acknowledge provision syncData.setSyncState("prepare.request.provision"); wbxml = eas.wbxmltools.createWBXML(); wbxml.switchpage("Provision"); wbxml.otag("Provision"); wbxml.otag("Policies"); wbxml.otag("Policy"); wbxml.atag("PolicyType",(syncData.accountData.getAccountProperty("asversion") == "2.5") ? "MS-WAP-Provisioning-XML" : "MS-EAS-Provisioning-WBXML" ); wbxml.atag("PolicyKey", policykey); wbxml.atag("Status", "1"); wbxml.ctag(); wbxml.ctag(); wbxml.ctag(); //this wbxml will be used by Send at the top of this loop } }, getSynckey: async function (syncData) { syncData.setSyncState("prepare.request.synckey"); //build WBXML to request a new syncKey let wbxml = eas.wbxmltools.createWBXML(); wbxml.otag("Sync"); wbxml.otag("Collections"); wbxml.otag("Collection"); if (syncData.accountData.getAccountProperty("asversion") == "2.5") wbxml.atag("Class", syncData.type); wbxml.atag("SyncKey","0"); wbxml.atag("CollectionId", syncData.currentFolderData.getFolderProperty("serverID")); wbxml.ctag(); wbxml.ctag(); wbxml.ctag(); syncData.setSyncState("send.request.synckey"); let response = await eas.network.sendRequest(wbxml.getBytes(), "Sync", syncData); syncData.setSyncState("eval.response.synckey"); // get data from wbxml response let wbxmlData = eas.network.getDataFromResponse(response); //check status eas.network.checkStatus(syncData, wbxmlData,"Sync.Collections.Collection.Status"); //update synckey eas.network.updateSynckey(syncData, wbxmlData); }, getItemEstimate: async function (syncData) { syncData.progressData.reset(); if (!syncData.accountData.getAccountProperty("allowedEasCommands").split(",").includes("GetItemEstimate")) { return; //do not throw, this is optional } syncData.setSyncState("prepare.request.estimate"); // BUILD WBXML let wbxml = eas.wbxmltools.createWBXML(); wbxml.switchpage("GetItemEstimate"); wbxml.otag("GetItemEstimate"); wbxml.otag("Collections"); wbxml.otag("Collection"); if (syncData.accountData.getAccountProperty("asversion") == "2.5") { //got this order for 2.5 directly from Microsoft support wbxml.atag("Class", syncData.type); //only 2.5 wbxml.atag("CollectionId", syncData.currentFolderData.getFolderProperty("serverID")); wbxml.switchpage("AirSync"); // required ! // https://docs.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-ascmd/ffbefa62-e315-40b9-9cc6-f8d74b5f65d4 if (syncData.type == "Calendar") wbxml.atag("FilterType", syncData.currentFolderData.accountData.getAccountProperty("synclimit")); else wbxml.atag("FilterType", "0"); // we may filter incomplete tasks wbxml.atag("SyncKey", syncData.synckey); wbxml.switchpage("GetItemEstimate"); } else { //14.0 wbxml.switchpage("AirSync"); wbxml.atag("SyncKey", syncData.synckey); wbxml.switchpage("GetItemEstimate"); wbxml.atag("CollectionId", syncData.currentFolderData.getFolderProperty("serverID")); wbxml.switchpage("AirSync"); wbxml.otag("Options"); // optional if (syncData.type == "Calendar") wbxml.atag("FilterType", syncData.currentFolderData.accountData.getAccountProperty("synclimit")); wbxml.atag("Class", syncData.type); wbxml.ctag(); wbxml.switchpage("GetItemEstimate"); } wbxml.ctag(); wbxml.ctag(); wbxml.ctag(); //SEND REQUEST syncData.setSyncState("send.request.estimate"); let response = await eas.network.sendRequest(wbxml.getBytes(), "GetItemEstimate", syncData, /* allowSoftFail */ true); //VALIDATE RESPONSE syncData.setSyncState("eval.response.estimate"); // get data from wbxml response, some servers send empty response if there are no changes, which is not an error let wbxmlData = eas.network.getDataFromResponse(response, eas.flags.allowEmptyResponse); if (wbxmlData === null) return; let status = eas.xmltools.getWbxmlDataField(wbxmlData, "GetItemEstimate.Response.Status"); let estimate = eas.xmltools.getWbxmlDataField(wbxmlData, "GetItemEstimate.Response.Collection.Estimate"); if (status && status == "1") { //do not throw on error, with EAS v2.5 I get error 2 for tasks and calendars ??? syncData.progressData.reset(0, estimate); } }, getUserInfo: async function (syncData) { if (!syncData.accountData.getAccountProperty("allowedEasCommands").split(",").includes("Settings")) { return; } syncData.setSyncState("prepare.request.getuserinfo"); let wbxml = eas.wbxmltools.createWBXML(); wbxml.switchpage("Settings"); wbxml.otag("Settings"); wbxml.otag("UserInformation"); wbxml.atag("Get"); wbxml.ctag(); wbxml.ctag(); syncData.setSyncState("send.request.getuserinfo"); let response = await eas.network.sendRequest(wbxml.getBytes(), "Settings", syncData); syncData.setSyncState("eval.response.getuserinfo"); let wbxmlData = eas.network.getDataFromResponse(response); eas.network.checkStatus(syncData, wbxmlData,"Settings.Status"); }, // SEARCH getSearchResults: async function (accountData, currentQuery) { let _wbxml = eas.wbxmltools.createWBXML(); _wbxml.switchpage("Search"); _wbxml.otag("Search"); _wbxml.otag("Store"); _wbxml.atag("Name", "GAL"); _wbxml.atag("Query", currentQuery); _wbxml.otag("Options"); _wbxml.atag("Range", "0-99"); //Z-Push needs a Range //Not valid for GAL: https://msdn.microsoft.com/en-us/library/gg675461(v=exchg.80).aspx //_wbxml.atag("DeepTraversal"); //_wbxml.atag("RebuildResults"); _wbxml.ctag(); _wbxml.ctag(); _wbxml.ctag(); let wbxml = _wbxml.getBytes(); eas.network.logXML(wbxml, "Send (GAL Search)"); let command = "Search"; let authData = eas.network.getAuthData(accountData); let oauthData = eas.network.getOAuthObj({ accountData }); let userAgent = accountData.getAccountProperty("useragent"); //plus calendar.useragent.extra = Lightning/5.4.5.2 let deviceType = accountData.getAccountProperty("devicetype"); let deviceId = accountData.getAccountProperty("deviceId"); TbSync.dump("Sending (EAS v" + accountData.getAccountProperty("asversion") +")", "POST " + eas.network.getEasURL(accountData) + '?Cmd=' + command + '&User=' + encodeURIComponent(authData.user) + '&DeviceType=' +deviceType + '&DeviceId=' + deviceId, true); for (let i=0; i < 2; i++) { // check OAuth situation before connecting if (oauthData && (!oauthData.accessToken || oauthData.isExpired())) { let _rv = {} if (!(await oauthData.asyncConnect(_rv))) { throw eas.sync.finish("error", _rv.error); } } try { let response = await new Promise(function(resolve, reject) { // Create request handler - API changed with TB60 to new XMKHttpRequest() let req = new XMLHttpRequest(); req.mozBackgroundRequest = true; req.open("POST", eas.network.getEasURL(accountData) + '?Cmd=' + command + '&User=' + encodeURIComponent(authData.user) + '&DeviceType=' +encodeURIComponent(deviceType) + '&DeviceId=' + deviceId, true); req.overrideMimeType("text/plain"); req.setRequestHeader("User-Agent", userAgent); req.setRequestHeader("Content-Type", "application/vnd.ms-sync.wbxml"); if (authData.password) { if (eas.network.getOAuthObj({ accountData })) { req.setRequestHeader("Authorization", 'Bearer ' + eas.network.getOAuthValue(authData.password, "access")); } else { req.setRequestHeader("Authorization", 'Basic ' + TbSync.tools.b64encode(authData.user + ':' + authData.password)); } } if (accountData.getAccountProperty("asversion") == "2.5") { req.setRequestHeader("MS-ASProtocolVersion", "2.5"); } else { req.setRequestHeader("MS-ASProtocolVersion", "14.0"); } req.setRequestHeader("Content-Length", wbxml.length); if (accountData.getAccountProperty("provision")) { req.setRequestHeader("X-MS-PolicyKey", accountData.getAccountProperty("policykey")); TbSync.dump("PolicyKey used", accountData.getAccountProperty("policykey")); } req.timeout = eas.Base.getConnectionTimeout(); req.ontimeout = function () { reject("GAL Search timeout"); }; req.onerror = function () { reject("GAL Search Error"); }; req.onload = function() { let response = req.responseText; switch(req.status) { case 200: //OK eas.network.logXML(response, "Received (GAL Search"); //What to do on error? IS this an error? Yes! if (response.length !== 0 && response.substr(0, 4) !== String.fromCharCode(0x03, 0x01, 0x6A, 0x00)) { TbSync.dump("Recieved Data", "Expecting WBXML but got junk (request status = " + req.status + ", ready state = " + req.readyState + "\n>>>>>>>>>>\n" + response + "\n<<<<<<<<<<\n"); reject("GAL Search Response Invalid"); } else { resolve(response); } break; case 401: // bad auth resolve("401"); break; default: reject("GAL Search Failed: " + req.status); } }; req.send(wbxml); }); if (response === "401") { // try to recover from bad auth via token refresh if (oauthData) { oauthData.accessToken = ""; continue; } } return response; } catch (e) { Components.utils.reportError(e); return; } } }, // OPTIONS getServerOptions: async function (syncData) { syncData.setSyncState("prepare.request.options"); let authData = eas.network.getAuthData(syncData.accountData); let userAgent = syncData.accountData.getAccountProperty("useragent"); //plus calendar.useragent.extra = Lightning/5.4.5.2 TbSync.dump("Sending", "OPTIONS " + eas.network.getEasURL(syncData.accountData)); let allowedRetries = 5; let retry; let oauthData = eas.network.getOAuthObj({ accountData: syncData.accountData }); do { retry = false; // Check OAuth situation before connecting if (oauthData && (!oauthData.accessToken || oauthData.isExpired())) { let _rv = {}; syncData.setSyncState("oauthprompt"); if (!(await oauthData.asyncConnect(_rv))) { throw eas.sync.finish("error", _rv.error); } } let result = await new Promise(function(resolve,reject) { syncData.req = new XMLHttpRequest(); syncData.req.mozBackgroundRequest = true; syncData.req.open("OPTIONS", eas.network.getEasURL(syncData.accountData), true); syncData.req.overrideMimeType("text/plain"); syncData.req.setRequestHeader("User-Agent", userAgent); if (authData.password) { if (eas.network.getOAuthObj({ accountData: syncData.accountData })) { syncData.req.setRequestHeader("Authorization", 'Bearer ' + eas.network.getOAuthValue(authData.password, "access")); } else { syncData.req.setRequestHeader("Authorization", 'Basic ' + TbSync.tools.b64encode(authData.user + ':' + authData.password)); } } syncData.req.timeout = eas.Base.getConnectionTimeout(); syncData.req.ontimeout = function () { resolve(); }; syncData.req.onerror = function () { let responseData = {}; responseData["MS-ASProtocolVersions"] = syncData.req.getResponseHeader("MS-ASProtocolVersions"); responseData["MS-ASProtocolCommands"] = syncData.req.getResponseHeader("MS-ASProtocolCommands"); TbSync.dump("EAS OPTIONS with response (status: "+syncData.req.status+")", "\n" + "responseText: " + syncData.req.responseText + "\n" + "responseHeader(MS-ASProtocolVersions): " + responseData["MS-ASProtocolVersions"]+"\n" + "responseHeader(MS-ASProtocolCommands): " + responseData["MS-ASProtocolCommands"]); resolve(); }; syncData.req.onload = function() { syncData.setSyncState("eval.request.options"); let responseData = {}; switch(syncData.req.status) { case 401: // AuthError let rv = {}; rv.errorObj = eas.sync.finish("error", "401"); rv.errorType = "PasswordPrompt"; resolve(rv); break; case 200: responseData["MS-ASProtocolVersions"] = syncData.req.getResponseHeader("MS-ASProtocolVersions"); responseData["MS-ASProtocolCommands"] = syncData.req.getResponseHeader("MS-ASProtocolCommands"); TbSync.dump("EAS OPTIONS with response (status: 200)", "\n" + "responseText: " + syncData.req.responseText + "\n" + "responseHeader(MS-ASProtocolVersions): " + responseData["MS-ASProtocolVersions"]+"\n" + "responseHeader(MS-ASProtocolCommands): " + responseData["MS-ASProtocolCommands"]); if (responseData && responseData["MS-ASProtocolCommands"] && responseData["MS-ASProtocolVersions"]) { syncData.accountData.setAccountProperty("allowedEasCommands", responseData["MS-ASProtocolCommands"]); syncData.accountData.setAccountProperty("allowedEasVersions", responseData["MS-ASProtocolVersions"]); syncData.accountData.setAccountProperty("lastEasOptionsUpdate", Date.now()); } resolve(); break; default: resolve(); break; } }; syncData.setSyncState("send.request.options"); syncData.req.send(); }); if (result && result.hasOwnProperty("errorType") && result.errorType == "PasswordPrompt") { if (allowedRetries > 0) { if (oauthData) { oauthData.accessToken = ""; retry = true; } else { syncData.setSyncState("passwordprompt"); let authData = eas.network.getAuthData(syncData.accountData); let promptData = { windowID: "auth:" + syncData.accountData.accountID, accountname: syncData.accountData.getAccountProperty("accountname"), usernameLocked: syncData.accountData.isConnected(), username: authData.user } let credentials = await TbSync.passwordManager.asyncPasswordPrompt(promptData, eas.openWindows); if (credentials) { authData.updateLoginData(credentials.username, credentials.password); retry = true; } } } if (!retry) { throw result.errorObj; } } allowedRetries--; } while (retry); }, // AUTODISCOVER updateServerConnectionViaAutodiscover: async function (syncData) { syncData.setSyncState("prepare.request.autodiscover"); let authData = eas.network.getAuthData(syncData.accountData); syncData.setSyncState("send.request.autodiscover"); let result = await eas.network.getServerConnectionViaAutodiscover(authData.user, authData.password, 30*1000); syncData.setSyncState("eval.response.autodiscover"); if (result.errorcode == 200) { //update account syncData.accountData.setAccountProperty("host", eas.network.stripAutodiscoverUrl(result.server)); syncData.accountData.setAccountProperty("user", result.user); syncData.accountData.setAccountProperty("https", (result.server.substring(0,5) == "https")); } return result.errorcode; }, stripAutodiscoverUrl: function(url) { let u = url; while (u.endsWith("/")) { u = u.slice(0,-1); } if (u.endsWith("/Microsoft-Server-ActiveSync")) u=u.slice(0, -28); else TbSync.dump("Received non-standard EAS url via autodiscover:", url); return u.split("//")[1]; //cut off protocol }, getServerConnectionViaAutodiscover : async function (user, password, maxtimeout) { let urls = []; let parts = user.split("@"); urls.push({"url":"http://autodiscover."+parts[1]+"/autodiscover/autodiscover.xml", "user":user}); urls.push({"url":"http://"+parts[1]+"/autodiscover/autodiscover.xml", "user":user}); urls.push({"url":"http://autodiscover."+parts[1]+"/Autodiscover/Autodiscover.xml", "user":user}); urls.push({"url":"http://"+parts[1]+"/Autodiscover/Autodiscover.xml", "user":user}); urls.push({"url":"https://autodiscover."+parts[1]+"/autodiscover/autodiscover.xml", "user":user}); urls.push({"url":"https://"+parts[1]+"/autodiscover/autodiscover.xml", "user":user}); urls.push({"url":"https://autodiscover."+parts[1]+"/Autodiscover/Autodiscover.xml", "user":user}); urls.push({"url":"https://"+parts[1]+"/Autodiscover/Autodiscover.xml", "user":user}); let requests = []; let responses = []; //array of objects {url, error, server} for (let i=0; i< urls.length; i++) { await TbSync.tools.sleep(200); requests.push( eas.network.getServerConnectionViaAutodiscoverRedirectWrapper(urls[i].url, urls[i].user, password, maxtimeout) ); } try { responses = await Promise.all(requests); } catch (e) { responses.push(e.result); //this is actually a success, see return value of getServerConnectionViaAutodiscoverRedirectWrapper() } let result; let log = []; for (let r=0; r < responses.length; r++) { log.push("* "+responses[r].url+" @ " + responses[r].user +" : " + (responses[r].server ? responses[r].server : responses[r].error)); if (responses[r].server) { result = {"server": responses[r].server, "user": responses[r].user, "error": "", "errorcode": 200}; break; } if (responses[r].error == 403 || responses[r].error == 401) { //we could still find a valid server, so just store this state result = {"server": "", "user": responses[r].user, "errorcode": responses[r].error, "error": TbSync.getString("status." + responses[r].error, "eas")}; } } //this is only reached on fail, if no result defined yet, use general error if (!result) { result = {"server": "", "user": user, "error": TbSync.getString("autodiscover.Failed","eas").replace("##user##", user), "errorcode": 503}; } TbSync.eventlog.add("error", new TbSync.EventLogInfo("eas"), result.error, log.join("\n")); return result; }, getServerConnectionViaAutodiscoverRedirectWrapper : async function (url, user, password, maxtimeout) { //using HEAD to find URL redirects until response URL no longer changes // * XHR should follow redirects transparently, but that does not always work, POST data could get lost, so we // * need to find the actual POST candidates (example: outlook.de accounts) let result = {}; let method = "HEAD"; let connection = { url, user }; do { await TbSync.tools.sleep(200); result = await eas.network.getServerConnectionViaAutodiscoverRequest(method, connection, password, maxtimeout); method = ""; if (result.error == "redirect found") { TbSync.dump("EAS autodiscover URL redirect", "\n" + connection.url + " @ " + connection.user + " => \n" + result.url + " @ " + result.user); connection.url = result.url; connection.user = result.user; method = "HEAD"; } else if (result.error == "POST candidate found") { method = "POST"; } } while (method); //invert reject and resolve, so we exit the promise group on success right away if (result.server) { let e = new Error("Not an error (early exit from promise group)"); e.result = result; throw e; } else { return result; } }, getServerConnectionViaAutodiscoverRequest: function (method, connection, password, maxtimeout) { TbSync.dump("Querry EAS autodiscover URL", connection.url + " @ " + connection.user); return new Promise(function(resolve,reject) { let xml = '\r\n'; xml += '\r\n'; xml += '\r\n'; xml += '' + connection.user + '\r\n'; xml += 'http://schemas.microsoft.com/exchange/autodiscover/mobilesync/responseschema/2006\r\n'; xml += '\r\n'; xml += '\r\n'; let userAgent = eas.prefs.getCharPref("clientID.useragent"); //plus calendar.useragent.extra = Lightning/5.4.5.2 // Create request handler - API changed with TB60 to new XMKHttpRequest() let req = new XMLHttpRequest(); req.mozBackgroundRequest = true; req.open(method, connection.url, true); req.timeout = maxtimeout; req.setRequestHeader("User-Agent", userAgent); let secure = (connection.url.substring(0,8).toLowerCase() == "https://"); if (method == "POST") { req.setRequestHeader("Content-Length", xml.length); req.setRequestHeader("Content-Type", "text/xml"); if (secure && password) { // OAUTH accounts cannot authenticate against the standard discovery services // updateServerConnectionViaAutodiscover() is not passing them on req.setRequestHeader("Authorization", "Basic " + TbSync.tools.b64encode(connection.user + ":" + password)); } } req.ontimeout = function () { TbSync.dump("EAS autodiscover with timeout", "\n" + connection.url + " => \n" + req.responseURL); resolve({"url":req.responseURL, "error":"timeout", "server":"", "user":connection.user}); }; req.onerror = function () { let error = TbSync.network.createTCPErrorFromFailedXHR(req); if (!error) error = req.responseText; TbSync.dump("EAS autodiscover with error ("+error+")", "\n" + connection.url + " => \n" + req.responseURL); resolve({"url":req.responseURL, "error":error, "server":"", "user":connection.user}); }; req.onload = function() { //initiate rerun on redirects if (req.responseURL != connection.url) { resolve({"url":req.responseURL, "error":"redirect found", "server":"", "user":connection.user}); return; } //initiate rerun on HEAD request without redirect (rerun and do a POST on this) if (method == "HEAD") { resolve({"url":req.responseURL, "error":"POST candidate found", "server":"", "user":connection.user}); return; } //ignore POST without autherization (we just do them to get redirect information) if (!secure) { resolve({"url":req.responseURL, "error":"unsecure POST", "server":"", "user":connection.user}); return; } //evaluate secure POST requests which have not been redirected TbSync.dump("EAS autodiscover POST with status (" + req.status + ")", "\n" + connection.url + " => \n" + req.responseURL + "\n[" + req.responseText + "]"); if (req.status === 200) { let data = null; // getDataFromXMLString may throw an error which cannot be catched outside onload, // because we are in an async callback of the surrounding Promise // Alternatively we could just return the responseText and do any data analysis outside of the Promise try { data = eas.xmltools.getDataFromXMLString(req.responseText); } catch (e) { resolve({"url":req.responseURL, "error":"bad response", "server":"", "user":connection.user}); return; } if (!(data === null) && data.Autodiscover && data.Autodiscover.Response && data.Autodiscover.Response.Action) { // "Redirect" or "Settings" are possible if (data.Autodiscover.Response.Action.Redirect) { // redirect, start again with new user let newuser = action.Redirect; resolve({"url":req.responseURL, "error":"redirect found", "server":"", "user":newuser}); } else if (data.Autodiscover.Response.Action.Settings) { // get server settings let server = eas.xmltools.nodeAsArray(data.Autodiscover.Response.Action.Settings.Server); for (let count = 0; count < server.length; count++) { if (server[count].Type == "MobileSync" && server[count].Url) { resolve({"url":req.responseURL, "error":"", "server":server[count].Url, "user":connection.user}); return; } } } } else { resolve({"url":req.responseURL, "error":"invalid", "server":"", "user":connection.user}); } } else { resolve({"url":req.responseURL, "error":req.status, "server":"", "user":connection.user}); } }; if (method == "HEAD") req.send(); else req.send(xml); }); }, getServerConnectionViaAutodiscoverV2JsonRequest: function (url, maxtimeout) { TbSync.dump("Querry EAS autodiscover V2 URL", url); return new Promise(function(resolve,reject) { let userAgent = eas.prefs.getCharPref("clientID.useragent"); //plus calendar.useragent.extra = Lightning/5.4.5.2 // Create request handler - API changed with TB60 to new XMKHttpRequest() let req = new XMLHttpRequest(); req.mozBackgroundRequest = true; req.open("GET", url, true); req.timeout = maxtimeout; req.setRequestHeader("User-Agent", userAgent); req.ontimeout = function () { TbSync.dump("EAS autodiscover V2 with timeout", "\n" + url + " => \n" + req.responseURL); resolve({"url":req.responseURL, "error":"timeout", "server":""}); }; req.onerror = function () { let error = TbSync.network.createTCPErrorFromFailedXHR(req); if (!error) error = req.responseText; TbSync.dump("EAS autodiscover V2 with error ("+error+")", "\n" + url + " => \n" + req.responseURL); resolve({"url":req.responseURL, "error":error, "server":""}); }; req.onload = function() { if (req.status === 200) { let data = JSON.parse(req.responseText); if (data && data.Url) { resolve({"url":req.responseURL, "error":"", "server": eas.network.stripAutodiscoverUrl(data.Url)}); } else { resolve({"url":req.responseURL, "error":"invalid", "server":""}); } return; } resolve({"url":req.responseURL, "error":req.status, "server":""}); }; req.send(); }); } } EAS-4-TbSync-1.12/content/includes/sync.js000066400000000000000000002125071362346370100202140ustar00rootroot00000000000000/* * This file is part of EAS-4-TbSync. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; // - https://dxr.mozilla.org/comm-central/source/calendar/base/public/calIEvent.idl // - https://dxr.mozilla.org/comm-central/source/calendar/base/public/calIItemBase.idl // - https://dxr.mozilla.org/comm-central/source/calendar/base/public/calICalendar.idl // - https://dxr.mozilla.org/comm-central/source/calendar/base/modules/calAsyncUtils.jsm // https://msdn.microsoft.com/en-us/library/dd299454(v=exchg.80).aspx var sync = { finish: function (aStatus = "", msg = "", details = "") { let status = TbSync.StatusData.SUCCESS switch (aStatus) { case "": case "ok": status = TbSync.StatusData.SUCCESS; break; case "info": status = TbSync.StatusData.INFO; break; case "resyncAccount": status = TbSync.StatusData.ACCOUNT_RERUN; break; case "resyncFolder": status = TbSync.StatusData.FOLDER_RERUN; break; case "warning": status = TbSync.StatusData.WARNING; break; case "error": status = TbSync.StatusData.ERROR; break; default: console.log("TbSync/EAS: Unknown status <"+aStatus+">"); status = TbSync.StatusData.ERROR; break; } let e = new Error(); e.name = "eas4tbsync"; e.message = status.toUpperCase() + ": " + msg.toString() + " (" + details.toString() + ")"; e.statusData = new TbSync.StatusData(status, msg.toString(), details.toString()); return e; }, resetFolderSyncInfo: function (folderData) { folderData.resetFolderProperty("synckey"); folderData.resetFolderProperty("lastsynctime"); }, // update folders avail on server and handle added, removed and renamed // folders folderList: async function(syncData) { //should we recheck options/commands? Always check, if we have no info about asversion! if (syncData.accountData.getAccountProperty("asversion", "") == "" || (Date.now() - syncData.accountData.getAccountProperty("lastEasOptionsUpdate")) > 86400000 ) { await eas.network.getServerOptions(syncData); } //only update the actual used asversion, if we are currently not connected or it has not yet been set if (syncData.accountData.getAccountProperty("asversion", "") == "" || !syncData.accountData.isConnected()) { //eval the currently in the UI selected EAS version let asversionselected = syncData.accountData.getAccountProperty("asversionselected"); let allowedVersionsString = syncData.accountData.getAccountProperty("allowedEasVersions").trim(); let allowedVersionsArray = allowedVersionsString.split(","); if (asversionselected == "auto") { if (allowedVersionsArray.includes("14.0")) syncData.accountData.setAccountProperty("asversion", "14.0"); else if (allowedVersionsArray.includes("2.5")) syncData.accountData.setAccountProperty("asversion", "2.5"); else if (allowedVersionsString == "") { throw eas.sync.finish("error", "InvalidServerOptions"); } else { throw eas.sync.finish("error", "nosupportedeasversion::"+allowedVersionsArray.join(", ")); } } else if (allowedVersionsString != "" && !allowedVersionsArray.includes(asversionselected)) { throw eas.sync.finish("error", "notsupportedeasversion::"+asversionselected+"::"+allowedVersionsArray.join(", ")); } else { //just use the value set by the user syncData.accountData.setAccountProperty("asversion", asversionselected); } } //do we need to get a new policy key? if (syncData.accountData.getAccountProperty("provision") && syncData.accountData.getAccountProperty("policykey") == "0") { await eas.network.getPolicykey(syncData); } //set device info await eas.network.setDeviceInformation(syncData); syncData.setSyncState("prepare.request.folders"); let foldersynckey = syncData.accountData.getAccountProperty("foldersynckey"); //build WBXML to request foldersync let wbxml = eas.wbxmltools.createWBXML(); wbxml.switchpage("FolderHierarchy"); wbxml.otag("FolderSync"); wbxml.atag("SyncKey", foldersynckey); wbxml.ctag(); syncData.setSyncState("send.request.folders"); let response = await eas.network.sendRequest(wbxml.getBytes(), "FolderSync", syncData); syncData.setSyncState("eval.response.folders"); let wbxmlData = eas.network.getDataFromResponse(response); eas.network.checkStatus(syncData, wbxmlData,"FolderSync.Status"); let synckey = eas.xmltools.getWbxmlDataField(wbxmlData,"FolderSync.SyncKey"); if (synckey) { syncData.accountData.setAccountProperty("foldersynckey", synckey); } else { throw eas.sync.finish("error", "wbxmlmissingfield::FolderSync.SyncKey"); } // If we reach this point, wbxmlData contains FolderSync node, // so the next "if" will not fail with an javascript error, no need // to use save getWbxmlDataField function. // Are there any changes in folder hierarchy? if (wbxmlData.FolderSync.Changes) { // Looking for additions. let add = eas.xmltools.nodeAsArray(wbxmlData.FolderSync.Changes.Add); for (let count = 0; count < add.length; count++) { // Only add allowed folder types to DB (include trash(4), so we can find trashed folders. if (!["9","14","8","13","7","15", "4"].includes(add[count].Type)) continue; let existingFolder = syncData.accountData.getFolder("serverID", add[count].ServerId); if (existingFolder) { // Server has send us an ADD for a folder we alreay have, treat as update. existingFolder.setFolderProperty("foldername", add[count].DisplayName); existingFolder.setFolderProperty("type", add[count].Type); existingFolder.setFolderProperty("parentID", add[count].ParentId); } else { // Create folder obj for new folder settings. let newFolder = syncData.accountData.createNewFolder(); switch (add[count].Type) { case "9": // contact case "14": newFolder.setFolderProperty("targetType", "addressbook"); break; case "8": // event case "13": newFolder.setFolderProperty("targetType", "calendar"); break; case "7": // todo case "15": newFolder.setFolderProperty("targetType", "calendar"); break; default: newFolder.setFolderProperty("targetType", "unknown type ("+add[count].Type+")"); break; } newFolder.setFolderProperty("serverID", add[count].ServerId); newFolder.setFolderProperty("foldername", add[count].DisplayName); newFolder.setFolderProperty("type", add[count].Type); newFolder.setFolderProperty("parentID", add[count].ParentId); // Do we have a cached folder? let cachedFolderData = syncData.accountData.getFolderFromCache("serverID", add[count].ServerId); if (cachedFolderData) { // Copy fields from cache which we want to re-use. newFolder.setFolderProperty("targetColor", cachedFolderData.getFolderProperty("targetColor")); newFolder.setFolderProperty("targetName", cachedFolderData.getFolderProperty("targetName")); newFolder.setFolderProperty("downloadonly", cachedFolderData.getFolderProperty("downloadonly")); } } } // Looking for updates. let update = eas.xmltools.nodeAsArray(wbxmlData.FolderSync.Changes.Update); for (let count = 0; count < update.length; count++) { let existingFolder = syncData.accountData.getFolder("serverID", update[count].ServerId); if (existingFolder) { // Update folder. existingFolder.setFolderProperty("foldername", update[count].DisplayName); existingFolder.setFolderProperty("type", update[count].Type); existingFolder.setFolderProperty("parentID", update[count].ParentId); } } // Looking for deletes. Do not delete the targets, // but keep them as stale/unconnected elements. let del = eas.xmltools.nodeAsArray(wbxmlData.FolderSync.Changes.Delete); for (let count = 0; count < del.length; count++) { let existingFolder = syncData.accountData.getFolder("serverID", del[count].ServerId); if (existingFolder) { existingFolder.remove("[deleted on server]"); } } } }, deleteFolder: async function (syncData) { if (!syncData.currentFolderData) { return; } if (!syncData.accountData.getAccountProperty("allowedEasCommands").split(",").includes("FolderDelete")) { throw eas.sync.finish("error", "notsupported::FolderDelete"); } syncData.setSyncState("prepare.request.deletefolder"); let foldersynckey = syncData.accountData.getAccountProperty("foldersynckey"); //request foldersync let wbxml = eas.wbxmltools.createWBXML(); wbxml.switchpage("FolderHierarchy"); wbxml.otag("FolderDelete"); wbxml.atag("SyncKey", foldersynckey); wbxml.atag("ServerId", syncData.currentFolderData.getFolderProperty("serverID")); wbxml.ctag(); syncData.setSyncState("send.request.deletefolder"); let response = await eas.network.sendRequest(wbxml.getBytes(), "FolderDelete", syncData); syncData.setSyncState("eval.response.deletefolder"); let wbxmlData = eas.network.getDataFromResponse(response); eas.network.checkStatus(syncData, wbxmlData,"FolderDelete.Status"); let synckey = eas.xmltools.getWbxmlDataField(wbxmlData,"FolderDelete.SyncKey"); if (synckey) { syncData.accountData.setAccountProperty("foldersynckey", synckey); syncData.currentFolderData.remove(); } else { throw eas.sync.finish("error", "wbxmlmissingfield::FolderDelete.SyncKey"); } }, singleFolder: async function (syncData) { // add target to syncData (getTarget() will throw "nolightning" if lightning missing) try { // accessing the target for the first time will check if it is avail and if not will create it (if possible) syncData.target = await syncData.currentFolderData.targetData.getTarget(); } catch (e) { Components.utils.reportError(e); throw eas.sync.finish("warning", e.message); } //get syncData type, which is also used in WBXML for the CLASS element syncData.type = null; switch (syncData.currentFolderData.getFolderProperty("type")) { case "9": //contact case "14": syncData.type = "Contacts"; break; case "8": //event case "13": syncData.type = "Calendar"; break; case "7": //todo case "15": syncData.type = "Tasks"; break; default: throw eas.sync.finish("info", "skipped"); break; } syncData.setSyncState("preparing"); //get synckey if needed syncData.synckey = syncData.currentFolderData.getFolderProperty("synckey"); if (syncData.synckey == "") { await eas.network.getSynckey(syncData); } //sync folder syncData.timeOfLastSync = syncData.currentFolderData.getFolderProperty( "lastsynctime") / 1000; syncData.timeOfThisSync = (Date.now() / 1000) - 1; let lightningBatch = false; let lightningReadOnly = ""; let error = null; // We ned to intercept any throw error, because lightning needs a few operations after sync finished try { switch (syncData.type) { case "Contacts": await eas.sync.easFolder(syncData); break; case "Calendar": case "Tasks": //save current value of readOnly (or take it from the setting) lightningReadOnly = syncData.target.calendar.getProperty("readOnly") || syncData.currentFolderData.getFolderProperty( "downloadonly"); syncData.target.calendar.setProperty("readOnly", false); lightningBatch = true; syncData.target.calendar.startBatch(); await eas.sync.easFolder(syncData); break; } } catch (report) { error = report; } if (lightningBatch) { syncData.target.calendar.endBatch(); syncData.target.calendar.setProperty("readOnly", lightningReadOnly); } if (error) throw error; }, // --------------------------------------------------------------------------- // MAIN FUNCTIONS TO SYNC AN EAS FOLDER // --------------------------------------------------------------------------- easFolder: async function (syncData) { syncData.progressData.reset(); if (syncData.currentFolderData.getFolderProperty("downloadonly")) { await eas.sync.revertLocalChanges(syncData); } await eas.network.getItemEstimate (syncData); await eas.sync.requestRemoteChanges (syncData); if (!syncData.currentFolderData.getFolderProperty("downloadonly")) { await eas.sync.sendLocalChanges(syncData); } }, requestRemoteChanges: async function (syncData) { do { syncData.setSyncState("prepare.request.remotechanges"); syncData.request = ""; syncData.response = ""; // BUILD WBXML let wbxml = eas.wbxmltools.createWBXML(); wbxml.otag("Sync"); wbxml.otag("Collections"); wbxml.otag("Collection"); if (syncData.accountData.getAccountProperty("asversion") == "2.5") wbxml.atag("Class", syncData.type); wbxml.atag("SyncKey", syncData.synckey); wbxml.atag("CollectionId", syncData.currentFolderData.getFolderProperty("serverID")); wbxml.atag("DeletesAsMoves"); wbxml.atag("GetChanges"); wbxml.atag("WindowSize", eas.prefs.getIntPref("maxitems").toString()); if (syncData.accountData.getAccountProperty("asversion") != "2.5") { wbxml.otag("Options"); if (syncData.type == "Calendar") wbxml.atag("FilterType", syncData.currentFolderData.accountData.getAccountProperty("synclimit")); wbxml.atag("Class", syncData.type); wbxml.switchpage("AirSyncBase"); wbxml.otag("BodyPreference"); wbxml.atag("Type", "1"); wbxml.ctag(); wbxml.switchpage("AirSync"); wbxml.ctag(); } else if (syncData.type == "Calendar") { //in 2.5 we only send it to filter Calendar wbxml.otag("Options"); wbxml.atag("FilterType", syncData.currentFolderData.accountData.getAccountProperty("synclimit")); wbxml.ctag(); } wbxml.ctag(); wbxml.ctag(); wbxml.ctag(); //SEND REQUEST syncData.setSyncState("send.request.remotechanges"); let response = await eas.network.sendRequest(wbxml.getBytes(), "Sync", syncData); //VALIDATE RESPONSE // get data from wbxml response, some servers send empty response if there are no changes, which is not an error let wbxmlData = eas.network.getDataFromResponse(response, eas.flags.allowEmptyResponse); if (wbxmlData === null) return; //check status, throw on error eas.network.checkStatus(syncData, wbxmlData,"Sync.Collections.Collection.Status"); //PROCESS COMMANDS await eas.sync.processCommands(wbxmlData, syncData); //Update count in UI syncData.setSyncState("eval.response.remotechanges"); //update synckey eas.network.updateSynckey(syncData, wbxmlData); if (!eas.xmltools.hasWbxmlDataField(wbxmlData,"Sync.Collections.Collection.MoreAvailable")) { //Feedback from users: They want to see the final count await TbSync.tools.sleep(100); return; } } while (true); }, sendLocalChanges: async function (syncData) { let maxnumbertosend = eas.prefs.getIntPref("maxitems"); syncData.progressData.reset(0, syncData.target.getItemsFromChangeLog().length); //keep track of failed items syncData.failedItems = []; let done = false; let numberOfItemsToSend = maxnumbertosend; do { syncData.setSyncState("prepare.request.localchanges"); syncData.request = ""; syncData.response = ""; //get changed items from ChangeLog let changes = syncData.target.getItemsFromChangeLog(numberOfItemsToSend); let c=0; let e=0; //keep track of send items during this request let changedItems = []; let addedItems = {}; let sendItems = []; // BUILD WBXML let wbxml = eas.wbxmltools.createWBXML(); wbxml.otag("Sync"); wbxml.otag("Collections"); wbxml.otag("Collection"); if (syncData.accountData.getAccountProperty("asversion") == "2.5") wbxml.atag("Class", syncData.type); wbxml.atag("SyncKey", syncData.synckey); wbxml.atag("CollectionId", syncData.currentFolderData.getFolderProperty("serverID")); wbxml.otag("Commands"); for (let i=0; i 0) { //if there was at least one actual local change, send request //SEND REQUEST & VALIDATE RESPONSE syncData.setSyncState("send.request.localchanges"); let response = await eas.network.sendRequest(wbxml.getBytes(), "Sync", syncData); syncData.setSyncState("eval.response.localchanges"); //get data from wbxml response let wbxmlData = eas.network.getDataFromResponse(response); //check status and manually handle error states which support softfails let errorcause = eas.network.checkStatus(syncData, wbxmlData, "Sync.Collections.Collection.Status", "", true); switch (errorcause) { case "": break; case "Sync.4": //Malformed request case "Sync.6": //Invalid item //some servers send a global error - to catch this, we reduce the number of items we send to the server if (sendItems.length == 1) { //the request contained only one item, so we know which one failed if (sendItems[0].type == "deleted_by_user") { //we failed to delete an item, discard and place message in log syncData.target.removeItemFromChangeLog(sendItems[0].id); TbSync.eventlog.add("warning", syncData.eventLogInfo, "ErrorOnDelete::"+sendItems[0].id); } else { let foundItem = await syncData.target.getItem(sendItems[0].id); if (foundItem) { eas.sync.updateFailedItems(syncData, errorcause, foundItem.primaryKey, foundItem.toString()); } else { //should not happen syncData.target.removeItemFromChangeLog(sendItems[0].id); } } syncData.progressData.inc(); //restore numberOfItemsToSend numberOfItemsToSend = maxnumbertosend; } else if (sendItems.length > 1) { //reduce further numberOfItemsToSend = Math.min(1, Math.round(sendItems.length / 5)); } else { //sendItems.length == 0 ??? recheck but this time let it handle all cases eas.network.checkStatus(syncData, wbxmlData, "Sync.Collections.Collection.Status"); } break; default: //recheck but this time let it handle all cases eas.network.checkStatus(syncData, wbxmlData, "Sync.Collections.Collection.Status"); } await TbSync.tools.sleep(10, true); if (errorcause == "") { //PROCESS RESPONSE await eas.sync.processResponses(wbxmlData, syncData, addedItems, changedItems); //PROCESS COMMANDS await eas.sync.processCommands(wbxmlData, syncData); //remove all items from changelog that did not fail for (let a=0; a < changedItems.length; a++) { syncData.target.removeItemFromChangeLog(changedItems[a]); syncData.progressData.inc(); } //update synckey eas.network.updateSynckey(syncData, wbxmlData); } } else if (e==0) { //if there was no local change and also no error (which will not happen twice) finish done = true; } } while (!done); //was there an error? if (syncData.failedItems.length > 0) { throw eas.sync.finish("warning", "ServerRejectedSomeItems::" + syncData.failedItems.length); } }, revertLocalChanges: async function (syncData) { let maxnumbertosend = eas.prefs.getIntPref("maxitems"); syncData.progressData.reset(0, syncData.target.getItemsFromChangeLog().length); if (syncData.progressData.todo == 0) { return; } let viaItemOperations = (syncData.accountData.getAccountProperty("allowedEasCommands").split(",").includes("ItemOperations")); //get changed items from ChangeLog do { syncData.setSyncState("prepare.request.revertlocalchanges"); let changes = syncData.target.getItemsFromChangeLog(maxnumbertosend); let c=0; syncData.request = ""; syncData.response = ""; // BUILD WBXML let wbxml = eas.wbxmltools.createWBXML(); if (viaItemOperations) { wbxml.switchpage("ItemOperations"); wbxml.otag("ItemOperations"); } else { wbxml.otag("Sync"); wbxml.otag("Collections"); wbxml.otag("Collection"); if (syncData.accountData.getAccountProperty("asversion") == "2.5") wbxml.atag("Class", syncData.type); wbxml.atag("SyncKey", syncData.synckey); wbxml.atag("CollectionId", syncData.currentFolderData.getFolderProperty("serverID")); wbxml.otag("Commands"); } for (let i=0; i 0) { //if there was at least one actual local change, send request let error = false; let wbxmlData = ""; //SEND REQUEST & VALIDATE RESPONSE try { syncData.setSyncState("send.request.revertlocalchanges"); let response = await eas.network.sendRequest(wbxml.getBytes(), (viaItemOperations) ? "ItemOperations" : "Sync", syncData); syncData.setSyncState("eval.response.revertlocalchanges"); //get data from wbxml response wbxmlData = eas.network.getDataFromResponse(response); } catch (e) { //we do not handle errors, IF there was an error, wbxmlData is empty and will trigger the fallback } let fetchPath = (viaItemOperations) ? "ItemOperations.Response.Fetch" : "Sync.Collections.Collection.Responses.Fetch"; if (eas.xmltools.hasWbxmlDataField(wbxmlData, fetchPath)) { //looking for additions let add = eas.xmltools.nodeAsArray(eas.xmltools.getWbxmlDataField(wbxmlData, fetchPath)); for (let count = 0; count < add.length; count++) { await TbSync.tools.sleep(10, true); let ServerId = add[count].ServerId; let data = (viaItemOperations) ? add[count].Properties : add[count].ApplicationData; if (data && ServerId) { let foundItem = await syncData.target.getItem(ServerId); if (!foundItem) { //do NOT add, if an item with that ServerId was found let newItem = eas.sync.createItem(syncData); try { eas.sync[syncData.type].setThunderbirdItemFromWbxml(newItem, data, ServerId, syncData); await syncData.target.addItem(newItem); } catch (e) { eas.xmltools.printXmlData(add[count], true); //include application data in log TbSync.eventlog.add("warning", syncData.eventLogInfo, "BadItemSkipped::JavaScriptError", newItem.toString()); throw e; // unable to add item to Thunderbird - fatal error } } else { //should not happen, since we deleted that item beforehand syncData.target.removeItemFromChangeLog(ServerId); } syncData.progressData.inc(); } else { error = true; break; } } } else { error = true; } if (error) { //if ItemOperations.Fetch fails, fall back to Sync.Fetch, if that fails, fall back to resync if (viaItemOperations) { viaItemOperations = false; TbSync.eventlog.add("info", syncData.eventLogInfo, "Server returned error during ItemOperations.Fetch, falling back to Sync.Fetch."); } else { await eas.sync.revertLocalChangesViaResync(syncData); return; } } } else { //if there was no more local change we need to revert, return return; } } while (true); }, revertLocalChangesViaResync: async function (syncData) { TbSync.eventlog.add("info", syncData.eventLogInfo, "Server does not support ItemOperations.Fetch and/or Sync.Fetch, must revert via resync."); let changes = syncData.target.getItemsFromChangeLog(); syncData.progressData.reset(0, changes.length); syncData.setSyncState("prepare.request.revertlocalchanges"); //remove all changes, so we can get them fresh from the server for (let i=0; i-1) changedItems.splice(p,1); } } } //looking for deletions let del = eas.xmltools.nodeAsArray(wbxmlData.Sync.Collections.Collection.Responses.Delete); for (let count = 0; count < del.length; count++) { //What can we do about failed deletes? SyncLog eas.network.checkStatus(syncData, del[count],"Status","Sync.Collections.Collection.Responses.Delete["+count+"].Status", true); } } }, // --------------------------------------------------------------------------- // HELPER FUNCTIONS AND DEFINITIONS // --------------------------------------------------------------------------- MAP_EAS2TB : { //EAS Importance: 0 = LOW | 1 = NORMAL | 2 = HIGH Importance : { "0":"9", "1":"5", "2":"1"}, //to PRIORITY //EAS Sensitivity : 0 = Normal | 1 = Personal | 2 = Private | 3 = Confidential Sensitivity : { "0":"PUBLIC", "1":"unset", "2":"PRIVATE", "3":"CONFIDENTIAL"}, //to CLASS //EAS BusyStatus: 0 = Free | 1 = Tentative | 2 = Busy | 3 = Work | 4 = Elsewhere BusyStatus : {"0":"TRANSPARENT", "1":"unset", "2":"OPAQUE", "3":"OPAQUE", "4":"OPAQUE"}, //to TRANSP //EAS AttendeeStatus: 0 =Response unknown (but needed) | 2 = Tentative | 3 = Accept | 4 = Decline | 5 = Not responded (and not needed) || 1 = Organizer in ResponseType ATTENDEESTATUS : {"0": "NEEDS-ACTION", "1":"Orga", "2":"TENTATIVE", "3":"ACCEPTED", "4":"DECLINED", "5":"ACCEPTED"}, }, MAP_TB2EAS : { //TB PRIORITY: 9 = LOW | 5 = NORMAL | 1 = HIGH PRIORITY : { "9":"0", "5":"1", "1":"2","unset":"1"}, //to Importance //TB CLASS: PUBLIC, PRIVATE, CONFIDENTIAL) CLASS : { "PUBLIC":"0", "PRIVATE":"2", "CONFIDENTIAL":"3", "unset":"1"}, //to Sensitivity //TB TRANSP : free = TRANSPARENT, busy = OPAQUE) TRANSP : {"TRANSPARENT":"0", "unset":"1", "OPAQUE":"2"}, // to BusyStatus //TB STATUS: NEEDS-ACTION, ACCEPTED, DECLINED, TENTATIVE, (DELEGATED, COMPLETED, IN-PROCESS - for todo) ATTENDEESTATUS : {"NEEDS-ACTION":"0", "ACCEPTED":"3", "DECLINED":"4", "TENTATIVE":"2", "DELEGATED":"5","COMPLETED":"5", "IN-PROCESS":"5"}, }, mapEasPropertyToThunderbird : function (easProp, tbProp, data, item) { if (data[easProp]) { //store original EAS value let easPropValue = eas.xmltools.checkString(data[easProp]); item.setProperty("X-EAS-" + easProp, easPropValue); //map EAS value to TB value (use setCalItemProperty if there is one option which can unset/delete the property) eas.tools.setCalItemProperty(item, tbProp, eas.sync.MAP_EAS2TB[easProp][easPropValue]); } }, mapThunderbirdPropertyToEas: function (tbProp, easProp, item) { if (item.hasProperty("X-EAS-" + easProp) && eas.tools.getCalItemProperty(item, tbProp) == eas.sync.MAP_EAS2TB[easProp][item.getProperty("X-EAS-" + easProp)]) { //we can use our stored EAS value, because it still maps to the current TB value return item.getProperty("X-EAS-" + easProp); } else { return eas.sync.MAP_TB2EAS[tbProp][eas.tools.getCalItemProperty(item, tbProp)]; } }, getEasItemType(aItem) { if (aItem instanceof TbSync.addressbook.AbItem) { return "Contacts"; } else if (aItem instanceof TbSync.lightning.TbItem) { return aItem.isTodo ? "Tasks" : "Calendar"; } else { throw "Unknown aItem."; } }, createItem(syncData) { switch (syncData.type) { case "Contacts": return syncData.target.createNewCard(); break; case "Tasks": return syncData.target.createNewTodo(); break; case "Calendar": return syncData.target.createNewEvent(); break; default: throw "Unknown item type <" + syncData.type + ">"; } }, getWbxmlFromThunderbirdItem(item, syncData, isException = false) { try { let wbxml = eas.sync[syncData.type].getWbxmlFromThunderbirdItem(item, syncData, isException); return wbxml; } catch (e) { TbSync.eventlog.add("warning", syncData.eventLogInfo, "BadItemSkipped::JavaScriptError", item.toString()); throw e; // unable to read item from Thunderbird - fatal error } }, // --------------------------------------------------------------------------- // LIGHTNING HELPER FUNCTIONS AND DEFINITIONS // These functions are needed only by tasks and events, so they // are placed here, even though they are not type independent, // but I did not want to add another "lightning" sub layer. // // The item in these functions is a native lightning item. // --------------------------------------------------------------------------- setItemSubject: function (item, syncData, data) { if (data.Subject) item.title = eas.xmltools.checkString(data.Subject); }, setItemLocation: function (item, syncData, data) { if (data.Location) item.setProperty("location", eas.xmltools.checkString(data.Location)); }, setItemCategories: function (item, syncData, data) { if (data.Categories && data.Categories.Category) { let cats = []; if (Array.isArray(data.Categories.Category)) cats = data.Categories.Category; else cats.push(data.Categories.Category); item.setCategories(cats.length, cats); } }, getItemCategories: function (item, syncData) { let asversion = syncData.accountData.getAccountProperty("asversion"); let wbxml = eas.wbxmltools.createWBXML("", syncData.type); //init wbxml with "" and not with precodes, also activate type codePage (Calendar, Tasks, Contacts etc) //to properly "blank" categories, we need to always include the container let categories = item.getCategories({}); if (categories.length > 0) { wbxml.otag("Categories"); for (let i=0; i 0 && */ data.Body.Data) item.setProperty("description", eas.xmltools.checkString(data.Body.Data)); //EstimatedDataSize is optional } }, getItemBody: function (item, syncData) { let asversion = syncData.accountData.getAccountProperty("asversion"); let wbxml = eas.wbxmltools.createWBXML("", syncData.type); //init wbxml with "" and not with precodes, also activate type codePage (Calendar, Tasks, Contacts etc) let description = (item.hasProperty("description")) ? item.getProperty("description") : ""; if (asversion == "2.5") { wbxml.atag("Body", description); } else { wbxml.switchpage("AirSyncBase"); wbxml.otag("Body"); wbxml.atag("Type", "1"); wbxml.atag("EstimatedDataSize", "" + description.length); wbxml.atag("Data", description); wbxml.ctag(); //does not work with horde at the moment, does not work with task, does not work with exceptions //if (syncData.accountData.getAccountProperty("horde") == "0") wbxml.atag("NativeBodyType", "1"); //return to code page of this type wbxml.switchpage(syncData.type); } return wbxml.getBytes(); }, //item is a native lightning item setItemRecurrence: function (item, syncData, data, timezone) { if (data.Recurrence) { item.recurrenceInfo = TbSync.lightning.cal.createRecurrenceInfo(); item.recurrenceInfo.item = item; let recRule = TbSync.lightning.cal.createRecurrenceRule(); switch (data.Recurrence.Type) { case "0": recRule.type = "DAILY"; break; case "1": recRule.type = "WEEKLY"; break; case "2": case "3": recRule.type = "MONTHLY"; break; case "5": case "6": recRule.type = "YEARLY"; break; } if (data.Recurrence.CalendarType) { // TODO } if (data.Recurrence.DayOfMonth) { recRule.setComponent("BYMONTHDAY", 1, [data.Recurrence.DayOfMonth]); } if (data.Recurrence.DayOfWeek) { let DOW = data.Recurrence.DayOfWeek; if (DOW == 127 && (recRule.type == "MONTHLY" || recRule.type == "YEARLY")) { recRule.setComponent("BYMONTHDAY", 1, [-1]); } else { let days = []; for (let i = 0; i < 7; ++i) { if (DOW & 1 << i) days.push(i + 1); } if (data.Recurrence.WeekOfMonth) { for (let i = 0; i < days.length; ++i) { if (data.Recurrence.WeekOfMonth == 5) { days[i] = -1 * (days[i] + 8); } else { days[i] += 8 * (data.Recurrence.WeekOfMonth - 0); } } } recRule.setComponent("BYDAY", days.length, days); } } if (data.Recurrence.FirstDayOfWeek) { //recRule.setComponent("WKST", 1, [data.Recurrence.FirstDayOfWeek]); // WKST is not a valid component //recRule.weekStart = data.Recurrence.FirstDayOfWeek; // - (NS_ERROR_NOT_IMPLEMENTED) [calIRecurrenceRule.weekStart] TbSync.eventlog.add("info", syncData.eventLogInfo, "FirstDayOfWeek tag ignored (not supported).", item.icalString); } if (data.Recurrence.Interval) { recRule.interval = data.Recurrence.Interval; } if (data.Recurrence.IsLeapMonth) { // TODO } if (data.Recurrence.MonthOfYear) { recRule.setComponent("BYMONTH", 1, [data.Recurrence.MonthOfYear]); } if (data.Recurrence.Occurrences) { recRule.count = data.Recurrence.Occurrences; } if (data.Recurrence.Until) { //time string could be in compact/basic or extended form of ISO 8601, //cal.createDateTime only supports compact/basic, our own method takes both styles recRule.untilDate = eas.tools.createDateTime(data.Recurrence.Until); } if (data.Recurrence.Start) { TbSync.eventlog.add("info", syncData.eventLogInfo, "Start tag in recurring task is ignored, recurrence will start with first entry.", item.icalString); } item.recurrenceInfo.insertRecurrenceItemAt(recRule, 0); if (data.Exceptions && syncData.type == "Calendar") { // only events, tasks cannot have exceptions // Exception could be an object or an array of objects let exceptions = [].concat(data.Exceptions.Exception); for (let exception of exceptions) { //exception.ExceptionStartTime is in UTC, but the Recurrence Object is in local timezone let dateTime = TbSync.lightning.cal.createDateTime(exception.ExceptionStartTime).getInTimezone(timezone); if (data.AllDayEvent == "1") { dateTime.isDate = true; // Pass to replacement event unless overriden if (!exception.AllDayEvent) { exception.AllDayEvent = "1"; } } if (exception.Deleted == "1") { item.recurrenceInfo.removeOccurrenceAt(dateTime); } else { let replacement = item.recurrenceInfo.getOccurrenceFor(dateTime); // replacement is a native lightning item, so we can access its id via .id eas.sync[syncData.type].setThunderbirdItemFromWbxml(replacement, exception, replacement.id, syncData); // Reminders should carry over from parent, but setThunderbirdItemFromWbxml clears all alarms if (!exception.Reminder && item.getAlarms({}).length) { replacement.addAlarm(item.getAlarms({})[0]); } // Removing a reminder requires EAS 16.0 item.recurrenceInfo.modifyException(replacement, true); } } } } }, getItemRecurrence: function (item, syncData, localStartDate = null) { let asversion = syncData.accountData.getAccountProperty("asversion"); let wbxml = eas.wbxmltools.createWBXML("", syncData.type); //init wbxml with "" and not with precodes, also activate type codePage (Calendar, Tasks etc) if (item.recurrenceInfo && (syncData.type == "Calendar" || syncData.type == "Tasks")) { let deleted = []; let hasRecurrence = false; let startDate = (syncData.type == "Calendar") ? item.startDate : item.entryDate; for (let recRule of item.recurrenceInfo.getRecurrenceItems({})) { if (recRule.date) { if (recRule.isNegative) { // EXDATE deleted.push(recRule); } else { // RDATE TbSync.eventlog.add("info", syncData.eventLogInfo, "Ignoring RDATE rule (not supported)", recRule.icalString); } continue; } if (recRule.isNegative) { // EXRULE TbSync.eventlog.add("info", syncData.eventLogInfo, "Ignoring EXRULE rule (not supported)", recRule.icalString); continue; } // RRULE wbxml.otag("Recurrence"); hasRecurrence = true; let type = 0; let monthDays = recRule.getComponent("BYMONTHDAY", {}); let weekDays = recRule.getComponent("BYDAY", {}); let months = recRule.getComponent("BYMONTH", {}); let weeks = []; // Unpack 1MO style days for (let i = 0; i < weekDays.length; ++i) { if (weekDays[i] > 8) { weeks[i] = Math.floor(weekDays[i] / 8); weekDays[i] = weekDays[i] % 8; } else if (weekDays[i] < -8) { // EAS only supports last week as a special value, treat // all as last week or assume every month has 5 weeks? // Change to last week //weeks[i] = 5; // Assumes 5 weeks per month for week <= -2 weeks[i] = 6 - Math.floor(-weekDays[i] / 8); weekDays[i] = -weekDays[i] % 8; } } if (monthDays[0] && monthDays[0] == -1) { weeks = [5]; weekDays = [1, 2, 3, 4, 5, 6, 7]; // 127 monthDays[0] = null; } // Type if (recRule.type == "WEEKLY") { type = 1; if (!weekDays.length) { weekDays = [startDate.weekday + 1]; } } else if (recRule.type == "MONTHLY" && weeks.length) { type = 3; } else if (recRule.type == "MONTHLY") { type = 2; if (!monthDays.length) { monthDays = [startDate.day]; } } else if (recRule.type == "YEARLY" && weeks.length) { type = 6; } else if (recRule.type == "YEARLY") { type = 5; if (!monthDays.length) { monthDays = [startDate.day]; } if (!months.length) { months = [startDate.month + 1]; } } wbxml.atag("Type", type.toString()); //Tasks need a Start tag, but we cannot allow a start date different from the start of the main item (thunderbird does not support that) if (localStartDate) wbxml.atag("Start", localStartDate); // TODO: CalendarType: 14.0 and up // DayOfMonth if (monthDays[0]) { // TODO: Multiple days of month - multiple Recurrence tags? wbxml.atag("DayOfMonth", monthDays[0].toString()); } // DayOfWeek if (weekDays.length) { let bitfield = 0; for (let day of weekDays) { bitfield |= 1 << (day - 1); } wbxml.atag("DayOfWeek", bitfield.toString()); } // FirstDayOfWeek: 14.1 and up //wbxml.atag("FirstDayOfWeek", recRule.weekStart); - (NS_ERROR_NOT_IMPLEMENTED) [calIRecurrenceRule.weekStart] // Interval wbxml.atag("Interval", recRule.interval.toString()); // TODO: IsLeapMonth: 14.0 and up // MonthOfYear if (months.length) { wbxml.atag("MonthOfYear", months[0].toString()); } // Occurrences if (recRule.isByCount) { wbxml.atag("Occurrences", recRule.count.toString()); } // Until else if (recRule.untilDate != null) { //Events need the Until data in compact form, Tasks in the basic form wbxml.atag("Until", eas.tools.getIsoUtcString(recRule.untilDate, (syncData.type == "Tasks"))); } // WeekOfMonth if (weeks.length) { wbxml.atag("WeekOfMonth", weeks[0].toString()); } wbxml.ctag(); } if (syncData.type == "Calendar" && hasRecurrence) { //Exceptions only allowed in Calendar and only if a valid Recurrence was added let modifiedIds = item.recurrenceInfo.getExceptionIds({}); if (deleted.length || modifiedIds.length) { wbxml.otag("Exceptions"); for (let exception of deleted) { wbxml.otag("Exception"); wbxml.atag("ExceptionStartTime", eas.tools.getIsoUtcString(exception.date)); wbxml.atag("Deleted", "1"); //Docs say it is allowed, but if present, it does not work //if (asversion == "2.5") { // wbxml.atag("UID", item.id); //item.id is not valid, use UID or primaryKey //} wbxml.ctag(); } for (let exceptionId of modifiedIds) { let replacement = item.recurrenceInfo.getExceptionFor(exceptionId); wbxml.otag("Exception"); wbxml.atag("ExceptionStartTime", eas.tools.getIsoUtcString(exceptionId)); wbxml.append(eas.sync.getWbxmlFromThunderbirdItem(replacement, syncData, true)); wbxml.ctag(); } wbxml.ctag(); } } } return wbxml.getBytes(); } } EAS-4-TbSync-1.12/content/includes/tasksync.js000066400000000000000000000174661362346370100211060ustar00rootroot00000000000000/* * This file is part of EAS-4-TbSync. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; var Tasks = { // --------------------------------------------------------------------------- // // Read WBXML and set Thunderbird item // --------------------------------------------------------------------------- // setThunderbirdItemFromWbxml: function (tbItem, data, id, syncdata) { let item = tbItem instanceof TbSync.lightning.TbItem ? tbItem.nativeItem : tbItem; let asversion = syncdata.accountData.getAccountProperty("asversion"); item.id = id; eas.sync.setItemBody(item, syncdata, data); eas.sync.setItemSubject(item, syncdata, data); eas.sync.setItemCategories(item, syncdata, data); eas.sync.setItemRecurrence(item, syncdata, data); let dueDate = null; if (data.DueDate && data.UtcDueDate) { //extract offset from EAS data let DueDate = new Date(data.DueDate); let UtcDueDate = new Date(data.UtcDueDate); let offset = (UtcDueDate.getTime() - DueDate.getTime())/60000; //timezone is identified by its offset let utc = cal.createDateTime(eas.tools.dateToBasicISOString(UtcDueDate)); //format "19800101T000000Z" - UTC dueDate = utc.getInTimezone(eas.tools.guessTimezoneByCurrentOffset(offset, utc)); item.dueDate = dueDate; } if (data.StartDate && data.UtcStartDate) { //extract offset from EAS data let StartDate = new Date(data.StartDate); let UtcStartDate = new Date(data.UtcStartDate); let offset = (UtcStartDate.getTime() - StartDate.getTime())/60000; //timezone is identified by its offset let utc = cal.createDateTime(eas.tools.dateToBasicISOString(UtcStartDate)); //format "19800101T000000Z" - UTC item.entryDate = utc.getInTimezone(eas.tools.guessTimezoneByCurrentOffset(offset, utc)); } else { //there is no start date? if this is a recurring item, we MUST add an entryDate, otherwise Thunderbird will not display the recurring items if (data.Recurrence) { if (dueDate) { item.entryDate = dueDate; TbSync.eventlog.add("info", syncdata, "Copy task dueData to task startDate, because Thunderbird needs a startDate for recurring items.", item.icalString); } else { TbSync.eventlog.add("info", syncdata, "Task without startDate and without dueDate but with recurrence info is not supported by Thunderbird. Recurrence will be lost.", item.icalString); } } } eas.sync.mapEasPropertyToThunderbird ("Sensitivity", "CLASS", data, item); eas.sync.mapEasPropertyToThunderbird ("Importance", "PRIORITY", data, item); item.clearAlarms(); if (data.ReminderSet && data.ReminderTime && data.UtcStartDate) { let UtcDate = eas.tools.createDateTime(data.UtcStartDate); let UtcAlarmDate = eas.tools.createDateTime(data.ReminderTime); let alarm = cal.createAlarm(); alarm.related = Components.interfaces.calIAlarm.ALARM_RELATED_START; //TB saves new alarms as offsets, so we add them as such as well alarm.offset = UtcAlarmDate.subtractDate(UtcDate); alarm.action = "DISPLAY"; item.addAlarm(alarm); } //status/percentage cannot be mapped if (data.Complete) { if (data.Complete == "0") { item.isCompleted = false; } else { item.isCompleted = true; if (data.DateCompleted) item.completedDate = eas.tools.createDateTime(data.DateCompleted); } } }, /* Regenerate: After complete, the completed task is removed from the series and stored as an new entry. The series starts an week (as set) after complete date with one less occurence */ // --------------------------------------------------------------------------- // //read TB event and return its data as WBXML // --------------------------------------------------------------------------- // getWbxmlFromThunderbirdItem: function (tbItem, syncdata) { let item = tbItem instanceof TbSync.lightning.TbItem ? tbItem.nativeItem : tbItem; let asversion = syncdata.accountData.getAccountProperty("asversion"); let wbxml = eas.wbxmltools.createWBXML("", syncdata.type); //init wbxml with "" and not with precodes, and set initial codepage //Order of tags taken from: https://msdn.microsoft.com/en-us/library/dn338924(v=exchg.80).aspx //Subject wbxml.atag("Subject", (item.title) ? item.title : ""); //Body wbxml.append(eas.sync.getItemBody(item, syncdata)); //Importance wbxml.atag("Importance", eas.sync.mapThunderbirdPropertyToEas("PRIORITY", "Importance", item)); //tasks is using extended ISO 8601 (2019-01-18T00:00:00.000Z) instead of basic (20190118T000000Z), //eas.tools.getIsoUtcString returns extended if true as second parameter is present let localStartDate = null; if (item.entryDate || item.dueDate) { wbxml.atag("UtcStartDate", eas.tools.getIsoUtcString(item.entryDate ? item.entryDate : item.dueDate, true)); //to fake the local time as UTC, getIsoUtcString needs the third parameter to be true localStartDate = eas.tools.getIsoUtcString(item.entryDate ? item.entryDate : item.dueDate, true, true); wbxml.atag("StartDate", localStartDate); wbxml.atag("UtcDueDate", eas.tools.getIsoUtcString(item.dueDate ? item.dueDate : item.entryDate, true)); //to fake the local time as UTC, getIsoUtcString needs the third parameter to be true wbxml.atag("DueDate", eas.tools.getIsoUtcString(item.dueDate ? item.dueDate : item.entryDate, true, true)); } //Categories wbxml.append(eas.sync.getItemCategories(item, syncdata)); //Recurrence (only if localStartDate has been set) if (localStartDate) wbxml.append(eas.sync.getItemRecurrence(item, syncdata, localStartDate)); //Complete if (item.isCompleted) { wbxml.atag("Complete", "1"); wbxml.atag("DateCompleted", eas.tools.getIsoUtcString(item.completedDate, true)); } else { wbxml.atag("Complete", "0"); } //Sensitivity wbxml.atag("Sensitivity", eas.sync.mapThunderbirdPropertyToEas("CLASS", "Sensitivity", item)); //ReminderTime and ReminderSet let alarms = item.getAlarms({}); if (alarms.length>0 && (item.entryDate || item.dueDate)) { let reminderTime; if (alarms[0].offset) { //create Date obj from entryDate by converting item.entryDate to an extended UTC ISO string, which can be parsed by Date //if entryDate is missing, the startDate of this object is set to its dueDate let UtcDate = new Date(eas.tools.getIsoUtcString(item.entryDate ? item.entryDate : item.dueDate, true)); //add offset UtcDate.setSeconds(UtcDate.getSeconds() + alarms[0].offset.inSeconds); reminderTime = UtcDate.toISOString(); } else { reminderTime = eas.tools.getIsoUtcString(alarms[0].alarmDate, true); } wbxml.atag("ReminderTime", reminderTime); wbxml.atag("ReminderSet", "1"); } else { wbxml.atag("ReminderSet", "0"); } return wbxml.getBytes(); }, } EAS-4-TbSync-1.12/content/includes/tools.js000066400000000000000000000606351362346370100204030ustar00rootroot00000000000000/* * This file is part of EAS-4-TbSync. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; var tools = { setCalItemProperty: function (item, prop, value) { if (value == "unset") item.deleteProperty(prop); else item.setProperty(prop, value); }, getCalItemProperty: function (item, prop) { if (item.hasProperty(prop)) return item.getProperty(prop); else return "unset"; }, isString: function (s) { return (typeof s == 'string' || s instanceof String); }, getIdentityKey: function (email) { let acctMgr = Components.classes["@mozilla.org/messenger/account-manager;1"].getService(Components.interfaces.nsIMsgAccountManager); let accounts = acctMgr.accounts; for (let a = 0; a < accounts.length; a++) { let account = accounts.queryElementAt(a, Components.interfaces.nsIMsgAccount); if (account.defaultIdentity && account.defaultIdentity.email == email) return account.defaultIdentity.key; } return ""; }, parentIsTrash: function (folderData) { let parentID = folderData.getFolderProperty("parentID"); if (parentID == "0") return false; let parentFolder = folderData.accountData.getFolder("serverID", parentID); if (parentFolder && parentFolder.getFolderProperty("type") == "4") return true; return false; }, getNewDeviceId: function () { //taken from https://jsfiddle.net/briguy37/2MVFd/ let d = new Date().getTime(); let uuid = 'xxxxxxxxxxxxxxxxyxxxxxxxxxxx'.replace(/[xy]/g, function(c) { let r = (d + Math.random()*16)%16 | 0; d = Math.floor(d/16); return (c=='x' ? r : (r&0x3|0x8)).toString(16); }); return "MZTB" + uuid; }, getUriFromDirectoryId: function(ownerId) { let directories = MailServices.ab.directories; while (directories.hasMoreElements()) { let directory = directories.getNext(); if (directory instanceof Components.interfaces.nsIAbDirectory) { if (ownerId.startsWith(directory.dirPrefId)) return directory.URI; } } return null; }, //function to get correct uri of current card for global book as well for mailLists getSelectedUri : function(aUri, aCard) { if (aUri == "moz-abdirectory://?") { //get parent via card owner return eas.tools.getUriFromDirectoryId(aCard.directoryId); } else if (MailServices.ab.getDirectory(aUri).isMailList) { //MailList suck, we have to cut the url to get the parent return aUri.substring(0, aUri.lastIndexOf("/")) } else { return aUri; } }, //read file from within the XPI package fetchFile: function (aURL, returnType = "Array") { return new Promise((resolve, reject) => { let uri = Services.io.newURI(aURL); let channel = Services.io.newChannelFromURI(uri, null, Services.scriptSecurityManager.getSystemPrincipal(), null, Components.interfaces.nsILoadInfo.SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS, Components.interfaces.nsIContentPolicy.TYPE_OTHER); NetUtil.asyncFetch(channel, (inputStream, status) => { if (!Components.isSuccessCode(status)) { reject(status); return; } try { let data = NetUtil.readInputStreamToString(inputStream, inputStream.available()); if (returnType == "Array") { resolve(data.replace("\r","").split("\n")) } else { resolve(data); } } catch (ex) { reject(ex); } }); }); }, // TIMEZONE STUFF TimeZoneDataStructure : class { constructor() { this.buf = new DataView(new ArrayBuffer(172)); } /* Buffer structure: @000 utcOffset (4x8bit as 1xLONG) @004 standardName (64x8bit as 32xWCHAR) @068 standardDate (16x8 as 1xSYSTEMTIME) @084 standardBias (4x8bit as 1xLONG) @088 daylightName (64x8bit as 32xWCHAR) @152 daylightDate (16x8 as 1xSTRUCT) @168 daylightBias (4x8bit as 1xLONG) */ set easTimeZone64 (b64) { //clear buffer for (let i=0; i<172; i++) this.buf.setUint8(i, 0); //load content into buffer let content = (b64 == "") ? "" : atob(b64); for (let i=0; i if found, does the stdOffset match? -> if so, done 2. Try to parse our own format, split name and test each chunk for IANA -> if found, does the stdOffset match? -> if so, done 3. Try if one of the chunks matches international code -> if found, does the stdOffset match? -> if so, done 4. Fallback: Use just the offsets */ //check for windows timezone name if (eas.windowsTimezoneMap[stdName] && eas.cachedTimezoneData.iana[eas.windowsTimezoneMap[stdName]] && eas.cachedTimezoneData.iana[eas.windowsTimezoneMap[stdName]].std.offset == stdOffset ) { //the windows timezone maps multiple IANA zones to one (Berlin*, Rome, Bruessel) //check the windowsZoneName of the default TZ and of the winning, if they match, use default TZ //so Rome could win, even Berlin is the default IANA zone if (eas.defaultTimezoneInfo.std.windowsZoneName && eas.windowsTimezoneMap[stdName] != eas.defaultTimezoneInfo.std.id && eas.cachedTimezoneData.iana[eas.windowsTimezoneMap[stdName]].std.offset == eas.defaultTimezoneInfo.std.offset && stdName == eas.defaultTimezoneInfo.std.windowsZoneName) { TbSync.dump("Timezone matched via windows timezone name ("+stdName+") with default TZ overtake", eas.windowsTimezoneMap[stdName] + " -> " + eas.defaultTimezoneInfo.std.id); return eas.defaultTimezoneInfo.timezone; } TbSync.dump("Timezone matched via windows timezone name ("+stdName+")", eas.windowsTimezoneMap[stdName]); return eas.cachedTimezoneData.iana[eas.windowsTimezoneMap[stdName]].timezone; } let parts = stdName.replace(/[;,()\[\]]/g," ").split(" "); for (let i = 0; i < parts.length; i++) { //check for IANA if (eas.cachedTimezoneData.iana[parts[i]] && eas.cachedTimezoneData.iana[parts[i]].std.offset == stdOffset) { TbSync.dump("Timezone matched via IANA", parts[i]); return eas.cachedTimezoneData.iana[parts[i]].timezone; } //check for international abbreviation for standard period (CET, CAT, ...) if (eas.cachedTimezoneData.abbreviations[parts[i]] && eas.cachedTimezoneData.iana[eas.cachedTimezoneData.abbreviations[parts[i]]] && eas.cachedTimezoneData.iana[eas.cachedTimezoneData.abbreviations[parts[i]]].std.offset == stdOffset) { TbSync.dump("Timezone matched via international abbreviation (" + parts[i] +")", eas.cachedTimezoneData.abbreviations[parts[i]]); return eas.cachedTimezoneData.iana[eas.cachedTimezoneData.abbreviations[parts[i]]].timezone; } } //fallback to zone based on stdOffset and dstOffset, if we have that cached if (eas.cachedTimezoneData.bothOffsets[stdOffset+":"+dstOffset]) { TbSync.dump("Timezone matched via both offsets (std:" + stdOffset +", dst:" + dstOffset + ")", eas.cachedTimezoneData.bothOffsets[stdOffset+":"+dstOffset].tzid); return eas.cachedTimezoneData.bothOffsets[stdOffset+":"+dstOffset]; } //fallback to zone based on stdOffset only, if we have that cached if (eas.cachedTimezoneData.stdOffset[stdOffset]) { TbSync.dump("Timezone matched via std offset (" + stdOffset +")", eas.cachedTimezoneData.stdOffset[stdOffset].tzid); return eas.cachedTimezoneData.stdOffset[stdOffset]; } //return default timezone, if everything else fails TbSync.dump("Timezone could not be matched via offsets (std:" + stdOffset +", dst:" + dstOffset + "), using default timezone", eas.defaultTimezoneInfo.std.id); return eas.defaultTimezoneInfo.timezone; }, //extract standard and daylight timezone data getTimezoneInfo: function (timezone) { let tzInfo = {}; tzInfo.std = eas.tools.getTimezoneInfoObject(timezone, "standard"); tzInfo.dst = eas.tools.getTimezoneInfoObject(timezone, "daylight"); if (tzInfo.dst === null) tzInfo.dst = tzInfo.std; tzInfo.timezone = timezone; return tzInfo; }, //get timezone info for standard/daylight getTimezoneInfoObject: function (timezone, standardOrDaylight) { //handle UTC if (timezone.isUTC) { let obj = {} obj.id = "UTC"; obj.offset = 0; obj.abbreviation = "UTC"; obj.displayname = "Coordinated Universal Time (UTC)"; return obj; } //we could parse the icalstring by ourself, but I wanted to use ICAL.parse - TODO try catch let info = TbSync.lightning.ICAL.parse("BEGIN:VCALENDAR\r\n" + timezone.icalComponent.toString() + "\r\nEND:VCALENDAR"); let comp = new TbSync.lightning.ICAL.Component(info); let vtimezone =comp.getFirstSubcomponent("vtimezone"); let id = vtimezone.getFirstPropertyValue("tzid").toString(); let zone = vtimezone.getFirstSubcomponent(standardOrDaylight); if (zone) { let obj = {}; obj.id = id; //get offset let utcOffset = zone.getFirstPropertyValue("tzoffsetto").toString(); let o = parseInt(utcOffset.replace(":","")); //-330 = - 3h 30min let h = Math.floor(o / 100); //-3 -> -180min let m = o - (h*100) //-330 - -300 = -30 obj.offset = -1*((h*60) + m); //get international abbreviation (CEST, CET, CAT ... ) obj.abbreviation = ""; try { obj.abbreviation = zone.getFirstPropertyValue("tzname").toString(); } catch(e) { TbSync.dump("Failed TZ", timezone.icalComponent.toString()); } //get displayname obj.displayname = /*"("+utcOffset+") " +*/ obj.id;// + ", " + obj.abbreviation; //get DST switch date let rrule = zone.getFirstPropertyValue("rrule"); let dtstart = zone.getFirstPropertyValue("dtstart"); if (rrule && dtstart) { /* THE switchdate PART OF THE OBJECT IS MICROSOFT SPECIFIC, EVERYTHING ELSE IS THUNDERBIRD GENERIC, I LET IT SIT HERE ANYHOW https://msdn.microsoft.com/en-us/library/windows/desktop/ms725481(v=vs.85).aspx To select the correct day in the month, set the wYear member to zero, the wHour and wMinute members to the transition time, the wDayOfWeek member to the appropriate weekday, and the wDay member to indicate the occurrence of the day of the week within the month (1 to 5, where 5 indicates the final occurrence during the month if that day of the week does not occur 5 times). Using this notation, specify 02:00 on the first Sunday in April as follows: wHour = 2, wMonth = 4, wDayOfWeek = 0, wDay = 1. Specify 02:00 on the last Thursday in October as follows: wHour = 2, wMonth = 10, wDayOfWeek = 4, wDay = 5. So we have to parse the RRULE to exract wDay RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10 */ let parts =rrule.toString().split(";"); let rules = {}; for (let i = 0; i< parts.length; i++) { let sub = parts[i].split("="); if (sub.length == 2) rules[sub[0]] = sub[1]; } if (rules.FREQ == "YEARLY" && rules.BYDAY && rules.BYMONTH && rules.BYDAY.length > 2) { obj.switchdate = {}; obj.switchdate.month = parseInt(rules.BYMONTH); let days = ["SU","MO","TU","WE","TH","FR","SA"]; obj.switchdate.dayOfWeek = days.indexOf(rules.BYDAY.substring(rules.BYDAY.length-2)); obj.switchdate.weekOfMonth = parseInt(rules.BYDAY.substring(0, rules.BYDAY.length-2)); if (obj.switchdate.weekOfMonth<0 || obj.switchdate.weekOfMonth>5) obj.switchdate.weekOfMonth = 5; //get switch time from dtstart let dttime = eas.tools.createDateTime(dtstart.toString()); obj.switchdate.hour = dttime.hour; obj.switchdate.minute = dttime.minute; obj.switchdate.second = dttime.second; } } return obj; } return null; }, } //TODO: Invites /* if (TbSync.lightningIsAvailable()) { cal.itip.checkAndSendOrigial = cal.itip.checkAndSend; cal.itip.checkAndSend = function(aOpType, aItem, aOriginalItem) { //if this item is added_by_user, do not call checkAndSend yet, because the UID is wrong, we need to sync first to get the correct ID - TODO TbSync.dump("cal.checkAndSend", aOpType); cal.itip.checkAndSendOrigial(aOpType, aItem, aOriginalItem); } } */ EAS-4-TbSync-1.12/content/includes/wbxmltools.js000066400000000000000000000741771362346370100214630ustar00rootroot00000000000000/* * This file is part of EAS-4-TbSync. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; var wbxmltools = { // Convert a WBXML (WAP Binary XML) to plain XML - returns save xml with all special chars in the user data encoded by encodeURIComponent convert2xml: function (wbxml) { let num = 4; //skip the 4 first bytes which are mostly 0x03 (WBXML Version 1.3), 0x01 (unknown public identifier), 0x6A (utf-8), 0x00 (Length of string table) //the main code page will be set to the the first codepage used let mainCodePage = null; let tagStack = []; let xml = ""; let codepage = 0; while (num < wbxml.length) { let data = wbxml.substr(num, 1).charCodeAt(0); let token = data & 0x3F; //removes content bit(6) and attribute bit(7) let tokenHasContent = ((data & 0x40) != 0); //checks if content bit is set let tokenHasAttributes = ((data & 0x80) != 0); //checks if attribute bit is set switch(token) { case 0x00: // switch of codepage (new codepage is next byte) num = num + 1; codepage = (wbxml.substr(num, 1)).charCodeAt(0) & 0xFF; break; case 0x01: // Indicates the end of an attribute list or the end of an element // tagStack contains a list of opened tags, which await to be closed xml = xml + tagStack.pop(); break; case 0x02: // A character entity. Followed by a mb_u_int32 encoding the character entity number. TbSync.dump("wbxml", "Encoded character entity has not yet been implemented. Sorry."); return false; break; case 0x03: // Inline string followed by a termstr. (0x00) let termpos = wbxml.indexOf(String.fromCharCode(0x00), num); //encode all special chars in the user data by encodeURIComponent which does not encode the apostrophe, so we need to do that by hand xml = xml + encodeURIComponent(wbxml.substring(num + 1, termpos)).replace(/'/g, "%27"); num = termpos; break; case 0x04: // An unknown tag or attribute name. Followed by an mb_u_int32 that encodes an offset into the string table. case 0x40: // Inline string document-type-specific extension token. Token is followed by a termstr. case 0x41: // Inline string document-type-specific extension token. Token is followed by a termstr. case 0x42: // Inline string document-type-specific extension token. Token is followed by a termstr. case 0x43: // Processing instruction. case 0x44: // Unknown tag, with content. case 0x80: // Inline integer document-type-specific extension token. Token is followed by a mb_uint_32. case 0x81: // Inline integer document-type-specific extension token. Token is followed by a mb_uint_32. case 0x82: // Inline integer document-type-specific extension token. Token is followed by a mb_uint_32. case 0x83: // String table reference. Followed by a mb_u_int32 encoding a byte offset from the beginning of the string table. case 0x84: // Unknown tag, with attributes. case 0xC0: // Single-byte document-type-specific extension token. case 0xC1: // Single-byte document-type-specific extension token. case 0xC2: // Single-byte document-type-specific extension token. case 0xC3: // Opaque document-type-specific data. case 0xC4: // Unknown tag, with content and attributes. TbSync.dump("wbxml", "Global token <" + token + "> has not yet been implemented. Sorry."); return false; break; default: // if this code page is not the mainCodePage (or mainCodePage is not yet set = very first tag), add codePageTag with current codepage let codePageTag = (codepage != mainCodePage) ? " xmlns='" + this.getNamespace(codepage) + "'" : ""; // if no mainCodePage has been defined yet, use the current codepage, which is either the initialized/default value of codepage or a value set by SWITCH_PAGE if (mainCodePage === null) mainCodePage = codepage; if (!tokenHasContent) { xml = xml + "<" + this. getCodepageToken(codepage, token) + codePageTag + "/>"; } else { xml = xml + "<" +this. getCodepageToken(codepage, token) + codePageTag +">"; //add the closing tag to the stack, so it can get properly closed later tagStack.push(""); } if (this.isUnknownToken(codepage, token)) { TbSync.eventlog.add("warning", null, "WBXML: Unknown token <" + token + "> for codepage <"+codepage+">."); } } num = num + 1; } return (xml == "") ? "" : '' + xml; }, isUnknownToken: function (codepage, token) { if (this.codepages[codepage] && token in this.codepages[codepage]) return false; else return true; }, getNamespace: function (codepage) { return (this.namespaces[codepage]) ? this.namespaces[codepage] : "UnknownCodePage" + codepage ; }, getCodepageToken: function (codepage, token) { return this.isUnknownToken(codepage, token) ? "Unknown." + codepage + "." + token : this.codepages[codepage][token]; }, // This returns a wbxml object, which allows to add tags (using names), switch codepages, or open and close tags, it is also possible to append pure (binary) wbxml // If no wbxmlstring is present, default to the "init" string ( WBXML Version 1.3, unknown public identifier, UTF-8, Length of string table) createWBXML: function (wbxmlstring = String.fromCharCode(0x03, 0x01, 0x6A, 0x00), initialCodepage = "") { let wbxml = { _codepage : 0, _wbxml : wbxmlstring, append : function (wbxmlstring) { this._wbxml = this._wbxml + wbxmlstring; }, // adding a string content tag as contentstring atag : function (tokenname, content = "") { //check if tokenname is in current codepage if ((this._codepage in wbxmltools.codepages2) == false) throw "[wbxmltools] Unknown codepage <"+this._codepage+">"; if ((tokenname in wbxmltools.codepages2[this._codepage]) == false) throw "[wbxmltools] Unknown tokenname <"+tokenname+"> for codepage <"+wbxmltools.namespaces[this._codepage]+">"; if (content == "") { //empty, just add token this._wbxml += String.fromCharCode(wbxmltools.codepages2[this._codepage][tokenname]); } else { //not empty,add token with enabled content bit and also add inlinestringidentifier this._wbxml += String.fromCharCode(wbxmltools.codepages2[this._codepage][tokenname] | 0x40, 0x03); //add content for (let i=0; i< content.length; i++) this._wbxml += String.fromCharCode(content.charCodeAt(i)); //add string termination and tag close this._wbxml += String.fromCharCode(0x00, 0x01); } }, switchpage : function (name) { let codepage = wbxmltools.namespaces.indexOf(name); if (codepage == -1) throw "[wbxmltools] Unknown codepage <"+ name +">"; this._codepage = codepage; this._wbxml += String.fromCharCode(0x00, codepage); }, ctag : function () { this._wbxml += String.fromCharCode(0x01); }, //opentag is assumed to add a token with content, otherwise use addtag otag : function (tokenname) { this._wbxml += String.fromCharCode(wbxmltools.codepages2[this._codepage][tokenname] | 0x40); }, getCharCodes : function () { let value = ""; for (let i=0; i 1 || (TbSync.prefs.getIntPref("log.userdatalevel") == 1 && printApplicationData)) { let dump = JSON.stringify(data); TbSync.dump("Extracted XML data", "\n" + dump); } }, getDataFromXMLString: function (str) { let data = null; let xml = ""; if (str == "") return data; let oParser = (Services.vc.compare(Services.appinfo.platformVersion, "61.*") >= 0) ? new DOMParser() : Components.classes["@mozilla.org/xmlextras/domparser;1"].createInstance(Components.interfaces.nsIDOMParser); try { xml = oParser.parseFromString(str, "application/xml"); } catch (e) { //however, domparser does not throw an error, it returns an error document //https://developer.mozilla.org/de/docs/Web/API/DOMParser //just in case throw eas.sync.finish("error", "malformed-xml"); } //check if xml is error document if (xml.documentElement.nodeName == "parsererror") { TbSync.dump("BAD XML", "The above XML and WBXML could not be parsed correctly, something is wrong."); throw eas.sync.finish("error", "malformed-xml"); } try { data = this.getDataFromXML(xml); } catch (e) { throw eas.sync.finish("error", "mailformed-data"); } return data; }, //create data object from XML node getDataFromXML : function (nodes) { /* * The passed nodes value could be an entire document in a single node (type 9) or a * single element node (type 1) as returned by getElementById. It could however also * be an array of nodes as returned by getElementsByTagName or a nodeList as returned * by childNodes. In that case node.length is defined. */ // create the return object let obj = {}; let nodeList = []; let multiplicity = {}; if (nodes.length === undefined) nodeList.push(nodes); else nodeList = nodes; // nodelist contains all childs, if two childs have the same name, we cannot add the chils as an object, but as an array of objects for (let node of nodeList) { if (node.nodeType == 1 || node.nodeType == 3) { if (!multiplicity.hasOwnProperty(node.nodeName)) multiplicity[node.nodeName] = 0; multiplicity[node.nodeName]++; //if this nodeName has multiplicity > 1, prepare obj (but only once) if (multiplicity[node.nodeName]==2) obj[node.nodeName] = []; } } // process nodes for (let node of nodeList) { switch (node.nodeType) { case 9: //document node, dive directly and process all children if (node.hasChildNodes) obj = this.getDataFromXML(node.childNodes); break; case 1: //element node if (node.hasChildNodes) { //if this is an element with only one text child, do not dive, but get text childs value let o; if (node.childNodes.length == 1 && node.childNodes.item(0).nodeType==3) { //the passed xml is a save xml with all special chars in the user data encoded by encodeURIComponent o = decodeURIComponent(node.childNodes.item(0).nodeValue); } else { o = this.getDataFromXML(node.childNodes); } //check, if we can add the object directly, or if we have to push it into an array if (multiplicity[node.nodeName]>1) obj[node.nodeName].push(o) else obj[node.nodeName] = o; } break; } } return obj; } }; EAS-4-TbSync-1.12/content/manager/000077500000000000000000000000001362346370100164775ustar00rootroot00000000000000EAS-4-TbSync-1.12/content/manager/createAccount.js000066400000000000000000000252521362346370100216230ustar00rootroot00000000000000/* * This file is part of EAS-4-TbSync. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; var { TbSync } = ChromeUtils.import("chrome://tbsync/content/tbsync.jsm"); const eas = TbSync.providers.eas; var tbSyncEasNewAccount = { startTime: 0, maxTimeout: 30, validating: false, onClose: function () { //disallow closing of wizard while validating return !this.validating; }, onCancel: function (event) { //disallow closing of wizard while validating if (this.validating) { event.preventDefault(); } }, onLoad: function () { this.providerData = new TbSync.ProviderData("eas"); this.elementName = document.getElementById('tbsync.newaccount.name'); this.elementUser = document.getElementById('tbsync.newaccount.user'); this.elementUrl = document.getElementById('tbsync.newaccount.url'); this.elementPass = document.getElementById('tbsync.newaccount.password'); this.elementServertype = document.getElementById('tbsync.newaccount.servertype'); document.documentElement.getButton("back").hidden = true; this.onUserDropdown(); document.getElementById("tbsync.error").hidden = true; document.getElementById("tbsync.spinner").hidden = true; document.addEventListener("wizardfinish", tbSyncEasNewAccount.onFinish.bind(this)); document.addEventListener("wizardcancel", tbSyncEasNewAccount.onCancel.bind(this)); }, onUnload: function () { }, onUserTextInput: function () { document.getElementById("tbsync.error").hidden = true; switch (this.elementServertype.value) { case "select": document.documentElement.getButton("finish").disabled = true; break; case "auto": document.documentElement.getButton("finish").disabled = (this.elementName.value.trim() == "" || this.elementUser.value == "" || this.elementPass.value == ""); break; case "office365": document.documentElement.getButton("finish").disabled = (this.elementName.value.trim() == "" || this.elementUser.value == ""); break; case "custom": default: document.documentElement.getButton("finish").disabled = (this.elementName.value.trim() == "" || this.elementUser.value == "" || this.elementPass.value == "" || this.elementUrl.value.trim() == ""); break; } }, onUserDropdown: function () { if (this.elementServertype) { switch (this.elementServertype.value) { case "select": document.getElementById('tbsync.newaccount.user.box').hidden = true; document.getElementById('tbsync.newaccount.url.box').hidden = true; document.getElementById('tbsync.newaccount.password.box').hidden = true; document.documentElement.getButton("finish").label = TbSync.getString("newaccount.add_custom","eas"); break; case "auto": document.getElementById('tbsync.newaccount.user.box').hidden = false; document.getElementById('tbsync.newaccount.url.box').hidden = true; document.getElementById('tbsync.newaccount.password.box').hidden = false; document.documentElement.getButton("finish").label = TbSync.getString("newaccount.add_auto","eas"); break; case "office365": document.getElementById('tbsync.newaccount.user.box').hidden = false; document.getElementById('tbsync.newaccount.url.box').hidden = true; document.getElementById('tbsync.newaccount.password.box').hidden = true; document.documentElement.getButton("finish").label = TbSync.getString("newaccount.add_custom","eas"); break; case "custom": default: document.getElementById('tbsync.newaccount.user.box').hidden = false; document.getElementById('tbsync.newaccount.url.box').hidden = false; document.getElementById('tbsync.newaccount.password.box').hidden = false; document.documentElement.getButton("finish").label = TbSync.getString("newaccount.add_custom","eas"); break; } this.onUserTextInput(); //document.getElementById("tbsync.newaccount.name").focus(); } }, onFinish: function (event) { if (document.documentElement.getButton("finish").disabled == false) { //initiate validation of server connection this.validate(); } event.preventDefault(); }, validate: async function () { let user = this.elementUser.value; let servertype = this.elementServertype.value; let accountname = this.elementName.value.trim(); let url = (servertype == "custom") ?this.elementUrl.value.trim() : ""; let password = (servertype == "auto" || servertype == "custom") ? this.elementPass.value : ""; if ((servertype == "auto" || servertype == "office365") && user.split("@").length != 2) { alert(TbSync.getString("autodiscover.NeedEmail","eas")) return; } this.validating = true; let error = ""; //document.getElementById("tbsync.newaccount.wizard").canRewind = false; document.getElementById("tbsync.error").hidden = true; document.documentElement.getButton("cancel").disabled = true; document.documentElement.getButton("finish").disabled = true; document.getElementById("tbsync.newaccount.name").disabled = true; document.getElementById("tbsync.newaccount.user").disabled = true; document.getElementById("tbsync.newaccount.password").disabled = true; document.getElementById("tbsync.newaccount.servertype").disabled = true; tbSyncEasNewAccount.startTime = Date.now(); tbSyncEasNewAccount.updateAutodiscoverStatus(); document.getElementById("tbsync.spinner").hidden = false; //do autodiscover if (servertype == "office365" || servertype == "auto") { let updateTimer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer); updateTimer.initWithCallback({notify : function () {tbSyncEasNewAccount.updateAutodiscoverStatus()}}, 1000, 3); if (servertype == "office365") { let v2 = await eas.network.getServerConnectionViaAutodiscoverV2JsonRequest("https://autodiscover-s.outlook.com/autodiscover/autodiscover.json?Email="+encodeURIComponent(user)+"&Protocol=ActiveSync"); let oauthData = eas.network.getOAuthObj({ host: v2.server, user, accountname, servertype }); if (oauthData) { // ask for token document.getElementById("tbsync.spinner").hidden = true; let _rv = {}; if (await oauthData.asyncConnect(_rv)) { password = _rv.tokens; } else { error = TbSync.getString("status." + _rv.error, "eas"); } document.getElementById("tbsync.spinner").hidden = false; url=v2.server; } else { error = TbSync.getString("status.404", "eas"); } } else { let result = await eas.network.getServerConnectionViaAutodiscover(user, password, tbSyncEasNewAccount.maxTimeout*1000); if (result.server) { user = result.user; url = result.server; } else { error = result.error; // is a localized string } } updateTimer.cancel(); } //now validate the information if (!error) { if (!password) error = TbSync.getString("status.401", "eas"); } //add if valid if (!error) { tbSyncEasNewAccount.addAccount(user, password, servertype, accountname, url); } //end validation document.getElementById("tbsync.newaccount.name").disabled = false; document.getElementById("tbsync.newaccount.user").disabled = false; document.getElementById("tbsync.newaccount.password").disabled = false; document.getElementById("tbsync.newaccount.servertype").disabled = false; document.documentElement.getButton("cancel").disabled = false; document.documentElement.getButton("finish").disabled = false; document.getElementById("tbsync.spinner").hidden = true; //document.getElementById("tbsync.newaccount.wizard").canRewind = true; this.validating = false; //close wizard, if done if (!error) { document.getElementById("tbsync.newaccount.wizard").cancel(); } else { document.getElementById("tbsync.error.message").textContent = error; document.getElementById("tbsync.error").hidden = false; } }, updateAutodiscoverStatus: function () { let offset = Math.round(((Date.now() - tbSyncEasNewAccount.startTime)/1000)); let timeout = (offset>2) ? " (" + (tbSyncEasNewAccount.maxTimeout - offset) + ")" : ""; document.getElementById('tbsync.newaccount.autodiscoverstatus').value = TbSync.getString("autodiscover.Querying","eas") + timeout; }, addAccount (user, password, servertype, accountname, url) { let newAccountEntry = this.providerData.getDefaultAccountEntries(); newAccountEntry.user = user; newAccountEntry.servertype = servertype; if (url) { //if no protocoll is given, prepend "https://" if (url.substring(0,4) != "http" || url.indexOf("://") == -1) url = "https://" + url.split("://").join("/"); newAccountEntry.host = eas.network.stripAutodiscoverUrl(url); newAccountEntry.https = (url.substring(0,5) == "https"); } // Add the new account. let newAccountData = this.providerData.addAccount(accountname, newAccountEntry); eas.network.getAuthData(newAccountData).updateLoginData(user, password); window.close(); } }; EAS-4-TbSync-1.12/content/manager/createAccount.xul000066400000000000000000000105171362346370100220150ustar00rootroot00000000000000 %tbsyncDTD; %easDTD; ]>