Version in base suite: 0.12-1 Base version: eas4tbsync_0.12-1 Target version: eas4tbsync_1.12-1~deb10u1 Base file: /srv/ftp-master.debian.org/ftp/pool/main/e/eas4tbsync/eas4tbsync_0.12-1.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/e/eas4tbsync/eas4tbsync_1.12-1~deb10u1.dsc /srv/release.debian.org/tmp/j7DDauNFIG/eas4tbsync-1.12/skin/365_16.png |binary /srv/release.debian.org/tmp/j7DDauNFIG/eas4tbsync-1.12/skin/365_32.png |binary /srv/release.debian.org/tmp/j7DDauNFIG/eas4tbsync-1.12/skin/365_48.png |binary /srv/release.debian.org/tmp/j7DDauNFIG/eas4tbsync-1.12/skin/eas_300.png |binary eas4tbsync-1.12/CONTRIBUTORS.md | 19 eas4tbsync-1.12/Makebeta | 36 eas4tbsync-1.12/Makefile.bat | 4 eas4tbsync-1.12/README.md | 11 eas4tbsync-1.12/_locales/Readme.txt | 8 eas4tbsync-1.12/_locales/bg/eas.dtd | 86 eas4tbsync-1.12/_locales/bg/eas.properties | 148 eas4tbsync-1.12/_locales/bg/messages.json | 8 eas4tbsync-1.12/_locales/de/eas.dtd | 86 eas4tbsync-1.12/_locales/de/eas.properties | 148 eas4tbsync-1.12/_locales/de/messages.json | 8 eas4tbsync-1.12/_locales/en-US/eas.dtd | 86 eas4tbsync-1.12/_locales/en-US/eas.properties | 148 eas4tbsync-1.12/_locales/en-US/messages.json | 8 eas4tbsync-1.12/_locales/fr/eas.dtd | 86 eas4tbsync-1.12/_locales/fr/eas.properties | 148 eas4tbsync-1.12/_locales/fr/messages.json | 8 eas4tbsync-1.12/_locales/hu/eas.dtd | 86 eas4tbsync-1.12/_locales/hu/eas.properties | 148 eas4tbsync-1.12/_locales/hu/messages.json | 8 eas4tbsync-1.12/_locales/it/eas.dtd | 86 eas4tbsync-1.12/_locales/it/eas.properties | 148 eas4tbsync-1.12/_locales/it/messages.json | 8 eas4tbsync-1.12/_locales/pl/eas.dtd | 86 eas4tbsync-1.12/_locales/pl/eas.properties | 148 eas4tbsync-1.12/_locales/pl/messages.json | 8 eas4tbsync-1.12/_locales/pt_BR/eas.dtd | 86 eas4tbsync-1.12/_locales/pt_BR/eas.properties | 148 eas4tbsync-1.12/_locales/pt_BR/messages.json | 8 eas4tbsync-1.12/_locales/ru/eas.dtd | 86 eas4tbsync-1.12/_locales/ru/eas.properties | 148 eas4tbsync-1.12/_locales/ru/messages.json | 8 eas4tbsync-1.12/beta-release-channel-update.json | 13 eas4tbsync-1.12/beta-release-channel-update.rdf | 26 eas4tbsync-1.12/bootstrap.js | 56 eas4tbsync-1.12/chrome.manifest | 20 eas4tbsync-1.12/content/OAuth2_1.jsm | 257 + eas4tbsync-1.12/content/includes/calendarsync.js | 423 + eas4tbsync-1.12/content/includes/contactsync.js | 357 + eas4tbsync-1.12/content/includes/network.js | 1443 ++++++ eas4tbsync-1.12/content/includes/sync.js | 1477 ++++++ eas4tbsync-1.12/content/includes/tasksync.js | 172 eas4tbsync-1.12/content/includes/tools.js | 539 ++ eas4tbsync-1.12/content/includes/wbxmltools.js | 877 +++ eas4tbsync-1.12/content/includes/xmltools.js | 155 eas4tbsync-1.12/content/manager/createAccount.js | 250 - eas4tbsync-1.12/content/manager/createAccount.xul | 99 eas4tbsync-1.12/content/manager/editAccountOverlay.js | 56 eas4tbsync-1.12/content/manager/editAccountOverlay.xul | 56 eas4tbsync-1.12/content/overlays/abCSS.xul | 8 eas4tbsync-1.12/content/overlays/abCardWindow.js | 107 eas4tbsync-1.12/content/overlays/abCardWindow.xul | 159 eas4tbsync-1.12/content/overlays/abNewCardWindow.js | 30 eas4tbsync-1.12/content/overlays/abNewCardWindow.xul | 14 eas4tbsync-1.12/content/overlays/abServerSearch.js | 212 eas4tbsync-1.12/content/overlays/abServerSearch.xul | 10 eas4tbsync-1.12/content/overlays/addressbookdetailsoverlay.js | 94 eas4tbsync-1.12/content/overlays/addressbookdetailsoverlay.xul | 27 eas4tbsync-1.12/content/overlays/addressbookoverlay.js | 42 eas4tbsync-1.12/content/overlays/addressbookoverlay.xul | 11 eas4tbsync-1.12/content/provider.js | 765 +++ eas4tbsync-1.12/content/provider/eas/calendarsync.js | 398 - eas4tbsync-1.12/content/provider/eas/contactsync.js | 369 - eas4tbsync-1.12/content/provider/eas/eas.js | 2265 ---------- eas4tbsync-1.12/content/provider/eas/overlays/abCardWindow.js | 146 eas4tbsync-1.12/content/provider/eas/overlays/abCardWindow.xul | 159 eas4tbsync-1.12/content/provider/eas/overlays/abNewCardWindow.js | 30 eas4tbsync-1.12/content/provider/eas/overlays/abNewCardWindow.xul | 15 eas4tbsync-1.12/content/provider/eas/overlays/addressbookdetailsoverlay.js | 101 eas4tbsync-1.12/content/provider/eas/overlays/addressbookdetailsoverlay.xul | 27 eas4tbsync-1.12/content/provider/eas/overlays/addressbookoverlay.js | 42 eas4tbsync-1.12/content/provider/eas/overlays/addressbookoverlay.xul | 12 eas4tbsync-1.12/content/provider/eas/sync.js | 1139 ----- eas4tbsync-1.12/content/provider/eas/tasksync.js | 172 eas4tbsync-1.12/content/provider/eas/tools.js | 415 - eas4tbsync-1.12/content/provider/eas/wbxmltools.js | 877 --- eas4tbsync-1.12/content/provider/eas/xmltools.js | 155 eas4tbsync-1.12/crowdin.yml | 5 eas4tbsync-1.12/debian/changelog | 96 eas4tbsync-1.12/debian/compat | 1 eas4tbsync-1.12/debian/control | 16 eas4tbsync-1.12/debian/copyright | 22 eas4tbsync-1.12/debian/rules | 11 eas4tbsync-1.12/debian/salsa-ci.yml | 3 eas4tbsync-1.12/debian/upstream/metadata | 3 eas4tbsync-1.12/debian/watch | 6 eas4tbsync-1.12/debian/webext-eas4tbsync.docs | 2 eas4tbsync-1.12/debian/webext-eas4tbsync.install | 8 eas4tbsync-1.12/debian/webext-eas4tbsync.links | 2 eas4tbsync-1.12/install.rdf | 144 eas4tbsync-1.12/locale/de/eas.dtd | 71 eas4tbsync-1.12/locale/de/eas.strings | 150 eas4tbsync-1.12/locale/en-US/eas.dtd | 71 eas4tbsync-1.12/locale/en-US/eas.strings | 150 eas4tbsync-1.12/locale/hu/eas.dtd | 71 eas4tbsync-1.12/locale/hu/eas.strings | 150 eas4tbsync-1.12/locale/it/eas.dtd | 71 eas4tbsync-1.12/locale/it/eas.strings | 150 eas4tbsync-1.12/locale/pt-BR/eas.dtd | 71 eas4tbsync-1.12/locale/pt-BR/eas.strings | 150 eas4tbsync-1.12/locale/ru/eas.dtd | 71 eas4tbsync-1.12/locale/ru/eas.strings | 150 eas4tbsync-1.12/manifest.json | 22 eas4tbsync-1.12/skin/365.svg | 12 eas4tbsync-1.12/skin/ab.css | 13 109 files changed, 9999 insertions(+), 8026 deletions(-) diff -Nru eas4tbsync-0.12/CONTRIBUTORS.md eas4tbsync-1.12/CONTRIBUTORS.md --- eas4tbsync-0.12/CONTRIBUTORS.md 1970-01-01 00:00:00.000000000 +0000 +++ eas4tbsync-1.12/CONTRIBUTORS.md 2020-02-20 11:04:33.000000000 +0000 @@ -0,0 +1,19 @@ +## 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) diff -Nru eas4tbsync-0.12/Makebeta eas4tbsync-1.12/Makebeta --- eas4tbsync-0.12/Makebeta 2019-02-26 16:26:17.000000000 +0000 +++ eas4tbsync-1.12/Makebeta 2020-02-20 11:04:33.000000000 +0000 @@ -8,29 +8,27 @@ # $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 -# $4 name of update.rdf : update-eas.rdf git clean -df git checkout -- . git pull -version=$(cat install.rdf | grep -oPm1 "(?<=)[^<]+") -vlevels=$(grep -o '\.' <<< "$version" | grep -c .) +version=$(cat manifest.json | jq -r .version) +updatefile=update-eas.json -sed -i "s/%VERSION%/$version/g" "beta-release-channel-update.rdf" -sed -i "s|%LINK%|$1/$3|g" "beta-release-channel-update.rdf" - -#if [ $vlevels -gt 1 ] -#then - echo "Releasing version $version via beta release channel (will include updateURL)" - sed -i "s| | \n $1/$4|g" "install.rdf" - sed -i "s|| [Beta Release Channel]|g" "install.rdf" -#else -# echo "Version $version is a stable release (will NOT include updateURL)" -#fi - -cp beta-release-channel-update.rdf $2/$4 - -rm $3 -zip -r $3 content locale README.md bootstrap.js install.rdf chrome.manifest LICENSE skin +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 diff -Nru eas4tbsync-0.12/Makefile.bat eas4tbsync-1.12/Makefile.bat --- eas4tbsync-0.12/Makefile.bat 2019-02-26 16:26:17.000000000 +0000 +++ eas4tbsync-1.12/Makefile.bat 2020-02-20 11:04:33.000000000 +0000 @@ -6,6 +6,4 @@ 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 locale skin chrome.manifest install.rdf LICENSE README.md bootstrap.js - - +"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 diff -Nru eas4tbsync-0.12/README.md eas4tbsync-1.12/README.md --- eas4tbsync-0.12/README.md 2019-02-26 16:26:17.000000000 +0000 +++ eas4tbsync-1.12/README.md 2020-02-20 11:04:33.000000000 +0000 @@ -4,11 +4,20 @@ 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 -* [eas16.png] by [FatCow Web Hosting](https://www.iconfinder.com/icons/64484/exchange_ms_icon) +* [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/) diff -Nru eas4tbsync-0.12/_locales/Readme.txt eas4tbsync-1.12/_locales/Readme.txt --- eas4tbsync-0.12/_locales/Readme.txt 1970-01-01 00:00:00.000000000 +0000 +++ eas4tbsync-1.12/_locales/Readme.txt 2020-02-20 11:04:33.000000000 +0000 @@ -0,0 +1,8 @@ +Want 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. diff -Nru eas4tbsync-0.12/_locales/bg/eas.dtd eas4tbsync-1.12/_locales/bg/eas.dtd --- eas4tbsync-0.12/_locales/bg/eas.dtd 1970-01-01 00:00:00.000000000 +0000 +++ eas4tbsync-1.12/_locales/bg/eas.dtd 2020-02-20 11:04:33.000000000 +0000 @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -Nru eas4tbsync-0.12/_locales/bg/eas.properties eas4tbsync-1.12/_locales/bg/eas.properties --- eas4tbsync-0.12/_locales/bg/eas.properties 1970-01-01 00:00:00.000000000 +0000 +++ eas4tbsync-1.12/_locales/bg/eas.properties 2020-02-20 11:04:33.000000000 +0000 @@ -0,0 +1,148 @@ +newaccount.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 diff -Nru eas4tbsync-0.12/_locales/bg/messages.json eas4tbsync-1.12/_locales/bg/messages.json --- eas4tbsync-0.12/_locales/bg/messages.json 1970-01-01 00:00:00.000000000 +0000 +++ eas4tbsync-1.12/_locales/bg/messages.json 2020-02-20 11:04:33.000000000 +0000 @@ -0,0 +1,8 @@ +{ + "extensionName": { + "message": "Provider for Exchange ActiveSync" + }, + "extensionDescription": { + "message": "Add sync support for Exchange ActiveSync accounts to TbSync (contacts, tasks and calendars)." + } +} \ No newline at end of file diff -Nru eas4tbsync-0.12/_locales/de/eas.dtd eas4tbsync-1.12/_locales/de/eas.dtd --- eas4tbsync-0.12/_locales/de/eas.dtd 1970-01-01 00:00:00.000000000 +0000 +++ eas4tbsync-1.12/_locales/de/eas.dtd 2020-02-20 11:04:33.000000000 +0000 @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -Nru eas4tbsync-0.12/_locales/de/eas.properties eas4tbsync-1.12/_locales/de/eas.properties --- eas4tbsync-0.12/_locales/de/eas.properties 1970-01-01 00:00:00.000000000 +0000 +++ eas4tbsync-1.12/_locales/de/eas.properties 2020-02-20 11:04:33.000000000 +0000 @@ -0,0 +1,148 @@ +newaccount.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 diff -Nru eas4tbsync-0.12/_locales/de/messages.json eas4tbsync-1.12/_locales/de/messages.json --- eas4tbsync-0.12/_locales/de/messages.json 1970-01-01 00:00:00.000000000 +0000 +++ eas4tbsync-1.12/_locales/de/messages.json 2020-02-20 11:04:33.000000000 +0000 @@ -0,0 +1,8 @@ +{ + "extensionName": { + "message": "Provider für Exchange ActiveSync" + }, + "extensionDescription": { + "message": "Erweitert TbSync und erlaubt die Synchronisation von Exchange ActiveSync Konten (Kontakte, Aufgaben und Kalender)." + } +} \ No newline at end of file diff -Nru eas4tbsync-0.12/_locales/en-US/eas.dtd eas4tbsync-1.12/_locales/en-US/eas.dtd --- eas4tbsync-0.12/_locales/en-US/eas.dtd 1970-01-01 00:00:00.000000000 +0000 +++ eas4tbsync-1.12/_locales/en-US/eas.dtd 2020-02-20 11:04:33.000000000 +0000 @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -Nru eas4tbsync-0.12/_locales/en-US/eas.properties eas4tbsync-1.12/_locales/en-US/eas.properties --- eas4tbsync-0.12/_locales/en-US/eas.properties 1970-01-01 00:00:00.000000000 +0000 +++ eas4tbsync-1.12/_locales/en-US/eas.properties 2020-02-20 11:04:33.000000000 +0000 @@ -0,0 +1,148 @@ +newaccount.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 diff -Nru eas4tbsync-0.12/_locales/en-US/messages.json eas4tbsync-1.12/_locales/en-US/messages.json --- eas4tbsync-0.12/_locales/en-US/messages.json 1970-01-01 00:00:00.000000000 +0000 +++ eas4tbsync-1.12/_locales/en-US/messages.json 2020-02-20 11:04:33.000000000 +0000 @@ -0,0 +1,8 @@ +{ + "extensionName": { + "message": "Provider for Exchange ActiveSync" + }, + "extensionDescription": { + "message": "Add sync support for Exchange ActiveSync accounts to TbSync (contacts, tasks and calendars)." + } +} diff -Nru eas4tbsync-0.12/_locales/fr/eas.dtd eas4tbsync-1.12/_locales/fr/eas.dtd --- eas4tbsync-0.12/_locales/fr/eas.dtd 1970-01-01 00:00:00.000000000 +0000 +++ eas4tbsync-1.12/_locales/fr/eas.dtd 2020-02-20 11:04:33.000000000 +0000 @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -Nru eas4tbsync-0.12/_locales/fr/eas.properties eas4tbsync-1.12/_locales/fr/eas.properties --- eas4tbsync-0.12/_locales/fr/eas.properties 1970-01-01 00:00:00.000000000 +0000 +++ eas4tbsync-1.12/_locales/fr/eas.properties 2020-02-20 11:04:33.000000000 +0000 @@ -0,0 +1,148 @@ +newaccount.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 diff -Nru eas4tbsync-0.12/_locales/fr/messages.json eas4tbsync-1.12/_locales/fr/messages.json --- eas4tbsync-0.12/_locales/fr/messages.json 1970-01-01 00:00:00.000000000 +0000 +++ eas4tbsync-1.12/_locales/fr/messages.json 2020-02-20 11:04:33.000000000 +0000 @@ -0,0 +1,8 @@ +{ + "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)." + } +} diff -Nru eas4tbsync-0.12/_locales/hu/eas.dtd eas4tbsync-1.12/_locales/hu/eas.dtd --- eas4tbsync-0.12/_locales/hu/eas.dtd 1970-01-01 00:00:00.000000000 +0000 +++ eas4tbsync-1.12/_locales/hu/eas.dtd 2020-02-20 11:04:33.000000000 +0000 @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -Nru eas4tbsync-0.12/_locales/hu/eas.properties eas4tbsync-1.12/_locales/hu/eas.properties --- eas4tbsync-0.12/_locales/hu/eas.properties 1970-01-01 00:00:00.000000000 +0000 +++ eas4tbsync-1.12/_locales/hu/eas.properties 2020-02-20 11:04:33.000000000 +0000 @@ -0,0 +1,148 @@ +newaccount.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 diff -Nru eas4tbsync-0.12/_locales/hu/messages.json eas4tbsync-1.12/_locales/hu/messages.json --- eas4tbsync-0.12/_locales/hu/messages.json 1970-01-01 00:00:00.000000000 +0000 +++ eas4tbsync-1.12/_locales/hu/messages.json 2020-02-20 11:04:33.000000000 +0000 @@ -0,0 +1,8 @@ +{ + "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)." + } +} \ No newline at end of file diff -Nru eas4tbsync-0.12/_locales/it/eas.dtd eas4tbsync-1.12/_locales/it/eas.dtd --- eas4tbsync-0.12/_locales/it/eas.dtd 1970-01-01 00:00:00.000000000 +0000 +++ eas4tbsync-1.12/_locales/it/eas.dtd 2020-02-20 11:04:33.000000000 +0000 @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -Nru eas4tbsync-0.12/_locales/it/eas.properties eas4tbsync-1.12/_locales/it/eas.properties --- eas4tbsync-0.12/_locales/it/eas.properties 1970-01-01 00:00:00.000000000 +0000 +++ eas4tbsync-1.12/_locales/it/eas.properties 2020-02-20 11:04:33.000000000 +0000 @@ -0,0 +1,148 @@ +newaccount.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 diff -Nru eas4tbsync-0.12/_locales/it/messages.json eas4tbsync-1.12/_locales/it/messages.json --- eas4tbsync-0.12/_locales/it/messages.json 1970-01-01 00:00:00.000000000 +0000 +++ eas4tbsync-1.12/_locales/it/messages.json 2020-02-20 11:04:33.000000000 +0000 @@ -0,0 +1,8 @@ +{ + "extensionName": { + "message": "Provider Exchange ActiveSync" + }, + "extensionDescription": { + "message": "Aggiunge il supporto per la sincronizzazione di account Exchange ActiveSync (contatti, attività e calendari) a TbSync." + } +} \ No newline at end of file diff -Nru eas4tbsync-0.12/_locales/pl/eas.dtd eas4tbsync-1.12/_locales/pl/eas.dtd --- eas4tbsync-0.12/_locales/pl/eas.dtd 1970-01-01 00:00:00.000000000 +0000 +++ eas4tbsync-1.12/_locales/pl/eas.dtd 2020-02-20 11:04:33.000000000 +0000 @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -Nru eas4tbsync-0.12/_locales/pl/eas.properties eas4tbsync-1.12/_locales/pl/eas.properties --- eas4tbsync-0.12/_locales/pl/eas.properties 1970-01-01 00:00:00.000000000 +0000 +++ eas4tbsync-1.12/_locales/pl/eas.properties 2020-02-20 11:04:33.000000000 +0000 @@ -0,0 +1,148 @@ +newaccount.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 diff -Nru eas4tbsync-0.12/_locales/pl/messages.json eas4tbsync-1.12/_locales/pl/messages.json --- eas4tbsync-0.12/_locales/pl/messages.json 1970-01-01 00:00:00.000000000 +0000 +++ eas4tbsync-1.12/_locales/pl/messages.json 2020-02-20 11:04:33.000000000 +0000 @@ -0,0 +1,8 @@ +{ + "extensionName": { + "message": "Dostawca dla Exchange ActiveSync" + }, + "extensionDescription": { + "message": "Dodaj obsługę synchronizacji kont Exchange ActiveSync do TbSync (kontakty, zadania i kalendarze)." + } +} \ No newline at end of file diff -Nru eas4tbsync-0.12/_locales/pt_BR/eas.dtd eas4tbsync-1.12/_locales/pt_BR/eas.dtd --- eas4tbsync-0.12/_locales/pt_BR/eas.dtd 1970-01-01 00:00:00.000000000 +0000 +++ eas4tbsync-1.12/_locales/pt_BR/eas.dtd 2020-02-20 11:04:33.000000000 +0000 @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -Nru eas4tbsync-0.12/_locales/pt_BR/eas.properties eas4tbsync-1.12/_locales/pt_BR/eas.properties --- eas4tbsync-0.12/_locales/pt_BR/eas.properties 1970-01-01 00:00:00.000000000 +0000 +++ eas4tbsync-1.12/_locales/pt_BR/eas.properties 2020-02-20 11:04:33.000000000 +0000 @@ -0,0 +1,148 @@ +newaccount.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 diff -Nru eas4tbsync-0.12/_locales/pt_BR/messages.json eas4tbsync-1.12/_locales/pt_BR/messages.json --- eas4tbsync-0.12/_locales/pt_BR/messages.json 1970-01-01 00:00:00.000000000 +0000 +++ eas4tbsync-1.12/_locales/pt_BR/messages.json 2020-02-20 11:04:33.000000000 +0000 @@ -0,0 +1,8 @@ +{ + "extensionName": { + "message": "Provedor Exchange ActiveSync" + }, + "extensionDescription": { + "message": "Adiciona suporte para sincronizar contas do Exchange ActiveSync (contatos, tarefas e calendários) para o TbSync." + } +} \ No newline at end of file diff -Nru eas4tbsync-0.12/_locales/ru/eas.dtd eas4tbsync-1.12/_locales/ru/eas.dtd --- eas4tbsync-0.12/_locales/ru/eas.dtd 1970-01-01 00:00:00.000000000 +0000 +++ eas4tbsync-1.12/_locales/ru/eas.dtd 2020-02-20 11:04:33.000000000 +0000 @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -Nru eas4tbsync-0.12/_locales/ru/eas.properties eas4tbsync-1.12/_locales/ru/eas.properties --- eas4tbsync-0.12/_locales/ru/eas.properties 1970-01-01 00:00:00.000000000 +0000 +++ eas4tbsync-1.12/_locales/ru/eas.properties 2020-02-20 11:04:33.000000000 +0000 @@ -0,0 +1,148 @@ +newaccount.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 diff -Nru eas4tbsync-0.12/_locales/ru/messages.json eas4tbsync-1.12/_locales/ru/messages.json --- eas4tbsync-0.12/_locales/ru/messages.json 1970-01-01 00:00:00.000000000 +0000 +++ eas4tbsync-1.12/_locales/ru/messages.json 2020-02-20 11:04:33.000000000 +0000 @@ -0,0 +1,8 @@ +{ + "extensionName": { + "message": "Provider for Exchange ActiveSync" + }, + "extensionDescription": { + "message": "Добавляет в TbSync поддержку синхронизации для учетных записей Exchange через базирующийся на http/https протокол ActiveSync (контакты, задачи и календари)." + } +} \ No newline at end of file diff -Nru eas4tbsync-0.12/beta-release-channel-update.json eas4tbsync-1.12/beta-release-channel-update.json --- eas4tbsync-0.12/beta-release-channel-update.json 1970-01-01 00:00:00.000000000 +0000 +++ eas4tbsync-1.12/beta-release-channel-update.json 2020-02-20 11:04:33.000000000 +0000 @@ -0,0 +1,13 @@ +{ + "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" } } } + ] + } + } +} \ No newline at end of file diff -Nru eas4tbsync-0.12/beta-release-channel-update.rdf eas4tbsync-1.12/beta-release-channel-update.rdf --- eas4tbsync-0.12/beta-release-channel-update.rdf 2019-02-26 16:26:17.000000000 +0000 +++ eas4tbsync-1.12/beta-release-channel-update.rdf 1970-01-01 00:00:00.000000000 +0000 @@ -1,26 +0,0 @@ - - - - - - - - - %VERSION% - - - {3550f703-e582-4d05-9a08-453d09bdfdc6} - 60.0 - 60.* - %LINK% - https://github.com/jobisoft/EAS-4-TbSync/releases - - - - - - - - - - diff -Nru eas4tbsync-0.12/bootstrap.js eas4tbsync-1.12/bootstrap.js --- eas4tbsync-0.12/bootstrap.js 2019-02-26 16:26:17.000000000 +0000 +++ eas4tbsync-1.12/bootstrap.js 2020-02-20 11:04:33.000000000 +0000 @@ -6,28 +6,27 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -//no need to create namespace, we are in a sandbox +// no need to create namespace, we are in a sandbox -Components.utils.import("resource://gre/modules/Services.jsm"); -Components.utils.import("resource://gre/modules/Task.jsm"); +var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); let thisID = ""; let onInitDoneObserver = { - observe: Task.async (function* (aSubject, aTopic, aData) { + observe: async function (aSubject, aTopic, aData) { let valid = false; try { - Components.utils.import("chrome://tbsync/content/tbsync.jsm"); - valid = tbSync.enabled; + 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 + // 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) { - yield tbSync.loadProvider(thisID, "eas", "//eas4tbsync/content/provider/eas/eas.js"); + await TbSync.providers.loadProvider(thisID, "eas", "chrome://eas4tbsync/content/provider.js"); } - }) + } } function install(data, reason) { @@ -37,33 +36,36 @@ } function startup(data, reason) { - //possible reasons: APP_STARTUP, ADDON_ENABLE, ADDON_INSTALL, ADDON_UPGRADE, or ADDON_DOWNGRADE. - - //set default prefs - let branch = Services.prefs.getDefaultBranch("extensions.tbsync."); - branch.setIntPref("eas.synclimit", 7); - branch.setIntPref("eas.maxitems", 50); - branch.setCharPref("eas.clientID.type", "TbSync"); - branch.setCharPref("eas.clientID.useragent", "Thunderbird ActiveSync"); + // Possible reasons: APP_STARTUP, ADDON_ENABLE, ADDON_INSTALL, ADDON_UPGRADE, or ADDON_DOWNGRADE. thisID = data.id; - Services.obs.addObserver(onInitDoneObserver, "tbsync.init.done", false); - - //during app startup, the load of the provider will be triggered by a "tbsync.init.done" notification, - //if load happens later, we need load manually + 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) { - Services.obs.removeObserver(onInitDoneObserver, "tbsync.init.done"); + // 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; + } - //unload this provider add-on and all its loaded providers from TbSync + Services.obs.removeObserver(onInitDoneObserver, "tbsync.observer.initialized"); + //unload this provider add-on from TbSync try { - tbSync.unloadProviderAddon(data.id); + var { TbSync } = ChromeUtils.import("chrome://tbsync/content/tbsync.jsm"); + TbSync.providers.unloadProvider("eas"); } catch (e) { - //if this fails, tbSync has been unloaded already but has unloaded this addon as well + //if this fails, TbSync has been unloaded already and has unloaded this addon as well } - Services.obs.notifyObservers(null, "chrome-flush-caches", null); + Services.obs.notifyObservers(null, "chrome-flush-caches", null); } diff -Nru eas4tbsync-0.12/chrome.manifest eas4tbsync-1.12/chrome.manifest --- eas4tbsync-0.12/chrome.manifest 2019-02-26 16:26:17.000000000 +0000 +++ eas4tbsync-1.12/chrome.manifest 2020-02-20 11:04:33.000000000 +0000 @@ -1,8 +1,14 @@ content eas4tbsync content/ -skin eas4tbsync classic/1.0 skin/ -locale eas4tbsync en-US locale/en-US/ -locale eas4tbsync de locale/de/ -locale eas4tbsync hu locale/hu/ -locale eas4tbsync it locale/it/ -locale eas4tbsync ru locale/ru/ -locale eas4tbsync pt-BR locale/pt-BR/ + +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/ + diff -Nru eas4tbsync-0.12/content/OAuth2_1.jsm eas4tbsync-1.12/content/OAuth2_1.jsm --- eas4tbsync-0.12/content/OAuth2_1.jsm 1970-01-01 00:00:00.000000000 +0000 +++ eas4tbsync-1.12/content/OAuth2_1.jsm 2020-02-20 11:04:33.000000000 +0000 @@ -0,0 +1,257 @@ +/* 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); + }); + }, +}; diff -Nru eas4tbsync-0.12/content/includes/calendarsync.js eas4tbsync-1.12/content/includes/calendarsync.js --- eas4tbsync-0.12/content/includes/calendarsync.js 1970-01-01 00:00:00.000000000 +0000 +++ eas4tbsync-1.12/content/includes/calendarsync.js 2020-02-20 11:04:33.000000000 +0000 @@ -0,0 +1,423 @@ +/* + * 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); + } + */ + +} diff -Nru eas4tbsync-0.12/content/includes/contactsync.js eas4tbsync-1.12/content/includes/contactsync.js --- eas4tbsync-0.12/content/includes/contactsync.js 1970-01-01 00:00:00.000000000 +0000 +++ eas4tbsync-1.12/content/includes/contactsync.js 2020-02-20 11:04:33.000000000 +0000 @@ -0,0 +1,357 @@ +/* + * 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); diff -Nru eas4tbsync-0.12/content/includes/network.js eas4tbsync-1.12/content/includes/network.js --- eas4tbsync-0.12/content/includes/network.js 1970-01-01 00:00:00.000000000 +0000 +++ eas4tbsync-1.12/content/includes/network.js 2020-02-20 11:04:33.000000000 +0000 @@ -0,0 +1,1443 @@ +/* + * 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(); + }); + } +} diff -Nru eas4tbsync-0.12/content/includes/sync.js eas4tbsync-1.12/content/includes/sync.js --- eas4tbsync-0.12/content/includes/sync.js 1970-01-01 00:00:00.000000000 +0000 +++ eas4tbsync-1.12/content/includes/sync.js 2020-02-20 11:04:33.000000000 +0000 @@ -0,0 +1,1477 @@ +/* + * 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(); + } + +} diff -Nru eas4tbsync-0.12/content/includes/tasksync.js eas4tbsync-1.12/content/includes/tasksync.js --- eas4tbsync-0.12/content/includes/tasksync.js 1970-01-01 00:00:00.000000000 +0000 +++ eas4tbsync-1.12/content/includes/tasksync.js 2020-02-20 11:04:33.000000000 +0000 @@ -0,0 +1,172 @@ +/* + * 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(); + }, +} diff -Nru eas4tbsync-0.12/content/includes/tools.js eas4tbsync-1.12/content/includes/tools.js --- eas4tbsync-0.12/content/includes/tools.js 1970-01-01 00:00:00.000000000 +0000 +++ eas4tbsync-1.12/content/includes/tools.js 2020-02-20 11:04:33.000000000 +0000 @@ -0,0 +1,539 @@ +/* + * 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); + } +} +*/ diff -Nru eas4tbsync-0.12/content/includes/wbxmltools.js eas4tbsync-1.12/content/includes/wbxmltools.js --- eas4tbsync-0.12/content/includes/wbxmltools.js 1970-01-01 00:00:00.000000000 +0000 +++ eas4tbsync-1.12/content/includes/wbxmltools.js 2020-02-20 11:04:33.000000000 +0000 @@ -0,0 +1,877 @@ +/* + * 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; + } + +}; diff -Nru eas4tbsync-0.12/content/manager/createAccount.js eas4tbsync-1.12/content/manager/createAccount.js --- eas4tbsync-0.12/content/manager/createAccount.js 2019-02-26 16:26:17.000000000 +0000 +++ eas4tbsync-1.12/content/manager/createAccount.js 2020-02-20 11:04:33.000000000 +0000 @@ -8,136 +8,234 @@ "use strict"; -Components.utils.import("resource://gre/modules/Task.jsm"); -Components.utils.import("chrome://tbsync/content/tbsync.jsm"); +var { TbSync } = ChromeUtils.import("chrome://tbsync/content/tbsync.jsm"); + +const eas = TbSync.providers.eas; var tbSyncEasNewAccount = { startTime: 0, maxTimeout: 30, + validating: false, onClose: function () { - //tbSync.dump("onClose", tbSync.addAccountWindowOpen); - return !document.documentElement.getButton("cancel").disabled; + //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("extra1").disabled = true; - document.documentElement.getButton("extra1").label = tbSync.getLocalizedMessage("newaccount.add_auto","eas"); - document.getElementById('tbsync.newaccount.autodiscoverlabel').hidden = true; - document.getElementById('tbsync.newaccount.autodiscoverstatus').hidden = true; + document.documentElement.getButton("back").hidden = true; + this.onUserDropdown(); - document.getElementById('tbsync.newaccount.url.box').style.visibility = (this.elementServertype.value != "custom") ? "hidden" : "visible"; - document.getElementById("tbsync.newaccount.name").focus(); + 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 () { - if (this.elementServertype.value != "custom") { - document.documentElement.getButton("extra1").disabled = (this.elementName.value.trim() == "" || this.elementUser.value == "" || this.elementPass.value == ""); - } else { - document.documentElement.getButton("extra1").disabled = (this.elementName.value.trim() == "" || this.elementUser.value == "" || this.elementPass.value == "" || this.elementUrl.value.trim() == ""); + 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 () { - document.documentElement.getButton("extra1").label = tbSync.getLocalizedMessage("newaccount.add_" + this.elementServertype.value,"eas"); - document.getElementById('tbsync.newaccount.url.box').style.visibility = (this.elementServertype.value != "custom") ? "hidden" : "visible"; - this.onUserTextInput(); - }, - - onAdd: Task.async (function* () { - if (document.documentElement.getButton("extra1").disabled == false) { - let user = this.elementUser.value; - let password = this.elementPass.value; - let servertype = this.elementServertype.value; - let accountname = this.elementName.value.trim(); - let url = this.elementUrl.value.trim(); - - if (servertype == "custom") { - tbSyncEasNewAccount.addAccount(user, password, servertype, accountname, url); + 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; } - - if (servertype == "auto") { - - if (user.split("@").length != 2) { - alert(tbSync.getLocalizedMessage("autodiscover.NeedEmail","eas")) - return - } - - document.documentElement.getButton("cancel").disabled = true; - document.documentElement.getButton("extra1").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; - - let updateTimer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer); - updateTimer.initWithCallback({notify : function () {tbSyncEasNewAccount.updateAutodiscoverStatus()}}, 1000, 3); + this.onUserTextInput(); + //document.getElementById("tbsync.newaccount.name").focus(); + } + }, - tbSyncEasNewAccount.startTime = Date.now(); - tbSyncEasNewAccount.updateAutodiscoverStatus(); + onFinish: function (event) { + if (document.documentElement.getButton("finish").disabled == false) { + //initiate validation of server connection + this.validate(); + } + event.preventDefault(); + }, - let result = yield tbSync.eas.getServerConnectionViaAutodiscover(user, password, tbSyncEasNewAccount.maxTimeout*1000); - updateTimer.cancel(); + validate: async function () { + let user = this.elementUser.value; + let servertype = this.elementServertype.value; + let accountname = this.elementName.value.trim(); - document.getElementById('tbsync.newaccount.autodiscoverlabel').hidden = true; - document.getElementById('tbsync.newaccount.autodiscoverstatus').hidden = true; + 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) { - alert(tbSync.getLocalizedMessage("autodiscover.Ok","eas")); - //add account with found server url - tbSyncEasNewAccount.addAccount(result.user, password, servertype, accountname, result.server); + user = result.user; + url = result.server; } else { - alert(tbSync.getLocalizedMessage("autodiscover.Failed","eas").replace("##user##", result.user) + "\n\n" + result.error); + error = result.error; // is a localized string } + } - 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; + updateTimer.cancel(); + } - document.documentElement.getButton("cancel").disabled = false; - document.documentElement.getButton("extra1").disabled = false; - } + //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 () { - document.getElementById('tbsync.newaccount.autodiscoverstatus').hidden = false; - let offset = Math.round(((Date.now()-tbSyncEasNewAccount.startTime)/1000)); + let offset = Math.round(((Date.now() - tbSyncEasNewAccount.startTime)/1000)); let timeout = (offset>2) ? " (" + (tbSyncEasNewAccount.maxTimeout - offset) + ")" : ""; - document.getElementById('tbsync.newaccount.autodiscoverstatus').value = tbSync.getLocalizedMessage("autodiscover.Querying","eas") + timeout; + document.getElementById('tbsync.newaccount.autodiscoverstatus').value = TbSync.getString("autodiscover.Querying","eas") + timeout; }, addAccount (user, password, servertype, accountname, url) { - let newAccountEntry = tbSync.eas.getDefaultAccountEntries(); - newAccountEntry.accountname = accountname; + 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 = tbSync.eas.stripAutodiscoverUrl(url); - newAccountEntry.https = (url.substring(0,5) == "https") ? "1" : "0"; - //also update password in PasswordManager (only works if url is present) - tbSync.eas.setPassword (newAccountEntry, password); + newAccountEntry.host = eas.network.stripAutodiscoverUrl(url); + newAccountEntry.https = (url.substring(0,5) == "https"); } - //create a new EAS account and pass its id to updateAccountsList, which will select it - //the onSelect event of the List will load the selected account - window.opener.tbSyncAccounts.updateAccountsList(tbSync.db.addAccount(newAccountEntry)); + // Add the new account. + let newAccountData = this.providerData.addAccount(accountname, newAccountEntry); + eas.network.getAuthData(newAccountData).updateLoginData(user, password); window.close(); } diff -Nru eas4tbsync-0.12/content/manager/createAccount.xul eas4tbsync-1.12/content/manager/createAccount.xul --- eas4tbsync-0.12/content/manager/createAccount.xul 2019-02-26 16:26:17.000000000 +0000 +++ eas4tbsync-1.12/content/manager/createAccount.xul 2020-02-20 11:04:33.000000000 +0000 @@ -1,50 +1,68 @@ - - - + %tbsyncDTD; + + %easDTD; +]> + + + onclose="return tbSyncEasNewAccount.onClose()" >