Version in base suite: 0.15-1 Base version: dav4tbsync_0.15-1 Target version: dav4tbsync_1.9-1~deb10u1 Base file: /srv/ftp-master.debian.org/ftp/pool/main/d/dav4tbsync/dav4tbsync_0.15-1.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/d/dav4tbsync/dav4tbsync_1.9-1~deb10u1.dsc /srv/release.debian.org/tmp/zowEFX7w9C/dav4tbsync-1.9/skin/dragdrop.png |binary /srv/release.debian.org/tmp/zowEFX7w9C/dav4tbsync-1.9/skin/google16.png |binary /srv/release.debian.org/tmp/zowEFX7w9C/dav4tbsync-1.9/skin/google32.png |binary /srv/release.debian.org/tmp/zowEFX7w9C/dav4tbsync-1.9/skin/google48.png |binary /srv/release.debian.org/tmp/zowEFX7w9C/dav4tbsync-1.9/skin/mbo16.png |binary /srv/release.debian.org/tmp/zowEFX7w9C/dav4tbsync-1.9/skin/mbo32.png |binary /srv/release.debian.org/tmp/zowEFX7w9C/dav4tbsync-1.9/skin/mbo48.png |binary /srv/release.debian.org/tmp/zowEFX7w9C/dav4tbsync-1.9/skin/posteo16.png |binary /srv/release.debian.org/tmp/zowEFX7w9C/dav4tbsync-1.9/skin/posteo32.png |binary /srv/release.debian.org/tmp/zowEFX7w9C/dav4tbsync-1.9/skin/posteo48.png |binary /srv/release.debian.org/tmp/zowEFX7w9C/dav4tbsync-1.9/skin/web16.png |binary /srv/release.debian.org/tmp/zowEFX7w9C/dav4tbsync-1.9/skin/web32.png |binary /srv/release.debian.org/tmp/zowEFX7w9C/dav4tbsync-1.9/skin/web48.png |binary /srv/release.debian.org/tmp/zowEFX7w9C/dav4tbsync-1.9/unused/_mbo16.png |binary /srv/release.debian.org/tmp/zowEFX7w9C/dav4tbsync-1.9/unused/_mbo32.png |binary /srv/release.debian.org/tmp/zowEFX7w9C/dav4tbsync-1.9/unused/_mbo48.png |binary /srv/release.debian.org/tmp/zowEFX7w9C/dav4tbsync-1.9/unused/iconfinder_17_939744_16.png |binary /srv/release.debian.org/tmp/zowEFX7w9C/dav4tbsync-1.9/unused/iconfinder_17_939744_32.png |binary /srv/release.debian.org/tmp/zowEFX7w9C/dav4tbsync-1.9/unused/iconfinder_17_939744_64.png |binary /srv/release.debian.org/tmp/zowEFX7w9C/dav4tbsync-1.9/unused/iconfinder_Apple_1298725_16.png |binary /srv/release.debian.org/tmp/zowEFX7w9C/dav4tbsync-1.9/unused/iconfinder_Apple_1298725_32.png |binary /srv/release.debian.org/tmp/zowEFX7w9C/dav4tbsync-1.9/unused/iconfinder_Apple_1298725_64.png |binary /srv/release.debian.org/tmp/zowEFX7w9C/dav4tbsync-1.9/unused/mbo48_2.png |binary dav4tbsync-1.9/CONTRIBUTORS.md | 18 dav4tbsync-1.9/Makebeta | 36 dav4tbsync-1.9/Makefile.bat | 2 dav4tbsync-1.9/README.md | 40 dav4tbsync-1.9/_locales/Readme.txt | 8 dav4tbsync-1.9/_locales/bg/dav.dtd | 57 dav4tbsync-1.9/_locales/bg/dav.properties | 87 dav4tbsync-1.9/_locales/bg/messages.json | 8 dav4tbsync-1.9/_locales/de/dav.dtd | 57 dav4tbsync-1.9/_locales/de/dav.properties | 87 dav4tbsync-1.9/_locales/de/messages.json | 8 dav4tbsync-1.9/_locales/en-US/dav.dtd | 57 dav4tbsync-1.9/_locales/en-US/dav.properties | 87 dav4tbsync-1.9/_locales/en-US/messages.json | 8 dav4tbsync-1.9/_locales/fr/dav.dtd | 57 dav4tbsync-1.9/_locales/fr/dav.properties | 87 dav4tbsync-1.9/_locales/fr/messages.json | 8 dav4tbsync-1.9/_locales/hu/dav.dtd | 57 dav4tbsync-1.9/_locales/hu/dav.properties | 87 dav4tbsync-1.9/_locales/hu/messages.json | 8 dav4tbsync-1.9/_locales/it/dav.dtd | 57 dav4tbsync-1.9/_locales/it/dav.properties | 87 dav4tbsync-1.9/_locales/it/messages.json | 8 dav4tbsync-1.9/_locales/pl/dav.dtd | 57 dav4tbsync-1.9/_locales/pl/dav.properties | 87 dav4tbsync-1.9/_locales/pl/messages.json | 8 dav4tbsync-1.9/_locales/pt_BR/dav.dtd | 57 dav4tbsync-1.9/_locales/pt_BR/dav.properties | 87 dav4tbsync-1.9/_locales/pt_BR/messages.json | 8 dav4tbsync-1.9/_locales/ru/dav.dtd | 57 dav4tbsync-1.9/_locales/ru/dav.properties | 87 dav4tbsync-1.9/_locales/ru/messages.json | 8 dav4tbsync-1.9/beta-release-channel-update.json | 13 dav4tbsync-1.9/beta-release-channel-update.rdf | 26 dav4tbsync-1.9/bootstrap.js | 66 dav4tbsync-1.9/chrome.manifest | 20 dav4tbsync-1.9/content/dav.js | 909 ---- dav4tbsync-1.9/content/includes/abUI.js | 478 ++ dav4tbsync-1.9/content/includes/network.js | 649 +++ dav4tbsync-1.9/content/includes/sync.js | 921 ++++ dav4tbsync-1.9/content/includes/tbSyncDavCalendar.js | 228 + dav4tbsync-1.9/content/includes/tools.js | 1295 ++++++ dav4tbsync-1.9/content/includes/vcard/LICENSE | 21 dav4tbsync-1.9/content/includes/vcard/SOURCE | 1 dav4tbsync-1.9/content/includes/vcard/vcard.js | 305 + dav4tbsync-1.9/content/manager/createAccount.js | 668 ++- dav4tbsync-1.9/content/manager/createAccount.xul | 81 dav4tbsync-1.9/content/manager/editAccountOverlay.js | 46 dav4tbsync-1.9/content/manager/editAccountOverlay.xul | 18 dav4tbsync-1.9/content/overlays/abCSS.xul | 8 dav4tbsync-1.9/content/overlays/abCardWindow.js | 75 dav4tbsync-1.9/content/overlays/abCardWindow.xul | 31 dav4tbsync-1.9/content/overlays/abNewCardWindow.js | 6 dav4tbsync-1.9/content/overlays/abNewCardWindow.xul | 1 dav4tbsync-1.9/content/overlays/addressbookdetailsoverlay.js | 44 dav4tbsync-1.9/content/overlays/addressbookoverlay.js | 10 dav4tbsync-1.9/content/overlays/addressbookoverlay.xul | 1 dav4tbsync-1.9/content/provider.js | 779 +++ dav4tbsync-1.9/content/sync.js | 868 ---- dav4tbsync-1.9/content/tools.js | 2036 ---------- dav4tbsync-1.9/content/vcard/LICENSE | 21 dav4tbsync-1.9/content/vcard/SOURCE | 1 dav4tbsync-1.9/content/vcard/vcard.js | 296 - dav4tbsync-1.9/crowdin.yml | 5 dav4tbsync-1.9/debian/changelog | 96 dav4tbsync-1.9/debian/compat | 1 dav4tbsync-1.9/debian/control | 16 dav4tbsync-1.9/debian/copyright | 8 dav4tbsync-1.9/debian/rules | 11 dav4tbsync-1.9/debian/salsa-ci.yml | 3 dav4tbsync-1.9/debian/source/lintian-overrides | 6 dav4tbsync-1.9/debian/upstream/metadata | 3 dav4tbsync-1.9/debian/watch | 4 dav4tbsync-1.9/debian/webext-dav4tbsync.docs | 5 dav4tbsync-1.9/debian/webext-dav4tbsync.install | 7 dav4tbsync-1.9/debian/webext-dav4tbsync.links | 2 dav4tbsync-1.9/install.rdf | 132 dav4tbsync-1.9/locale/de/dav.dtd | 60 dav4tbsync-1.9/locale/de/dav.strings | 97 dav4tbsync-1.9/locale/en-US/dav.dtd | 60 dav4tbsync-1.9/locale/en-US/dav.strings | 97 dav4tbsync-1.9/locale/hu/dav.dtd | 60 dav4tbsync-1.9/locale/hu/dav.strings | 97 dav4tbsync-1.9/locale/it/dav.dtd | 60 dav4tbsync-1.9/locale/it/dav.strings | 97 dav4tbsync-1.9/locale/pt-BR/dav.dtd | 60 dav4tbsync-1.9/locale/pt-BR/dav.strings | 97 dav4tbsync-1.9/locale/ru/dav.dtd | 60 dav4tbsync-1.9/locale/ru/dav.strings | 97 dav4tbsync-1.9/manifest.json | 22 dav4tbsync-1.9/skin/ab.css | 36 dav4tbsync-1.9/unused/google | 13 115 files changed, 7020 insertions(+), 5686 deletions(-) diff -Nru dav4tbsync-0.15/CONTRIBUTORS.md dav4tbsync-1.9/CONTRIBUTORS.md --- dav4tbsync-0.15/CONTRIBUTORS.md 1970-01-01 00:00:00.000000000 +0000 +++ dav4tbsync-1.9/CONTRIBUTORS.md 2020-02-20 12:39:38.000000000 +0000 @@ -0,0 +1,18 @@ +## Creator +* John Bieling + +## Contributors +* John Bieling +* Brad2014 +* Nathan Gauër +* Alexandr Heymdall (vCard parser) + +## Translators +* Ettore Atalan (de) +* John Bieling (de, en-US) +* Wanderlei Hüttel (pt-BR) +* Alessandro Menti (it) +* Óvári (hu) +* Alexey Sinitsyn (ru) +* Jérémie Parisel (fr) +* Daniel Wróblewski (pl) diff -Nru dav4tbsync-0.15/Makebeta dav4tbsync-1.9/Makebeta --- dav4tbsync-0.15/Makebeta 2019-02-26 16:26:01.000000000 +0000 +++ dav4tbsync-1.9/Makebeta 2020-02-20 12:39:38.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 : DAV-4-TbSync.xpi -# $4 name of update.rdf : update-dav.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-dav.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\": \"DAV 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 dav4tbsync-0.15/Makefile.bat dav4tbsync-1.9/Makefile.bat --- dav4tbsync-0.15/Makefile.bat 2019-02-26 16:26:01.000000000 +0000 +++ dav4tbsync-1.9/Makefile.bat 2020-02-20 12:39:38.000000000 +0000 @@ -6,6 +6,6 @@ REM file, You can obtain one at http://mozilla.org/MPL/2.0/. del DAV-4-TbSync.xpi -"C:\Program Files\7-Zip\7zG.exe" a -tzip DAV-4-TbSync.xpi content locale skin chrome.manifest install.rdf bootstrap.js LICENSE +"C:\Program Files\7-Zip\7zG.exe" a -tzip DAV-4-TbSync.xpi content _locales skin chrome.manifest manifest.json bootstrap.js LICENSE CONTRIBUTORS.md diff -Nru dav4tbsync-0.15/README.md dav4tbsync-1.9/README.md --- dav4tbsync-0.15/README.md 2019-02-26 16:26:01.000000000 +0000 +++ dav4tbsync-1.9/README.md 2020-02-20 12:39:38.000000000 +0000 @@ -3,21 +3,33 @@ More information can be found in the [wiki](https://github.com/jobisoft/DAV-4-TbSync/wiki/About:-Provider-for-CalDAV-&-CardDAV) 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. + + ## Icon sources and attributions +#### Public Domain + +* posteo.de icons by [posteo.de](https://commons.wikimedia.org/wiki/File:Posteo.png) +* mailbox.org icons by [mailbox.org](https://commons.wikimedia.org/wiki/File:Logo_mailbox.org_RGB_658x358.jpg) + #### CC-BY 3.0 -* [ics16.png] by [FatCow Web Hosting](https://www.iconfinder.com/icons/35803/) -* [icloud16.png] by [Five Icons](https://www.iconfinder.com/icons/252111/apple_icon) -* [yahoo16.png] by [Five Icons](https://www.iconfinder.com/icons/252070/yahoo_icon) -* [gmx16.png] by [CloudSponge](https://www.iconfinder.com/icons/1175604/address_book_contact_contacts_email_gmx_square_icon) -* [type.pref.png] by [Steve Schoger](https://www.iconfinder.com/icons/3671863/) -* [type.other.png] by [Steve Schoger](https://www.iconfinder.com/icons/3671671/) -* [type.work.png] by [Steve Schoger](https://www.iconfinder.com/icons/3671695/) -* [type.home.png] by [Steve Schoger](https://www.iconfinder.com/icons/3671775/) -* [type.car.png] by [Steve Schoger](https://www.iconfinder.com/icons/3671885/) -* [type.cell.png] by [Steve Schoger](https://www.iconfinder.com/icons/3671810/) -* [type.fax.png] by [Steve Schoger](https://www.iconfinder.com/icons/3671840/) -* [type.video.png] by [Steve Schoger](https://www.iconfinder.com/icons/3671900/) -* [type.voice.png] by [Steve Schoger](https://www.iconfinder.com/icons/3671831/) -* [type.pager.png] by [Steve Schoger](https://www.iconfinder.com/icons/3671720/) +* ics16.png by [FatCow Web Hosting](https://www.iconfinder.com/icons/35803/) +* google icons by [Just UI](https://www.iconfinder.com/icons/1298745/) +* icloud icons by [Five Icons](https://www.iconfinder.com/icons/252111/apple_icon) +* yahoo icons by [Five Icons](https://www.iconfinder.com/icons/252070/yahoo_icon) +* gmx icons by [CloudSponge](https://www.iconfinder.com/icons/1175604/address_book_contact_contacts_email_gmx_square_icon) +* web icons by [CloudSponge](https://www.iconfinder.com/icons/1175616/address_book_contact_contacts_email_mail_square_webde_icon) +* type.pref.png by [Steve Schoger](https://www.iconfinder.com/icons/3671863/) +* type.other.png by [Steve Schoger](https://www.iconfinder.com/icons/3671671/) +* type.work.png by [Steve Schoger](https://www.iconfinder.com/icons/3671695/) +* type.home.png by [Steve Schoger](https://www.iconfinder.com/icons/3671775/) +* type.car.png by [Steve Schoger](https://www.iconfinder.com/icons/3671885/) +* type.cell.png by [Steve Schoger](https://www.iconfinder.com/icons/3671810/) +* type.fax.png by [Steve Schoger](https://www.iconfinder.com/icons/3671840/) +* type.video.png by [Steve Schoger](https://www.iconfinder.com/icons/3671900/) +* type.voice.png by [Steve Schoger](https://www.iconfinder.com/icons/3671831/) +* type.pager.png by [Steve Schoger](https://www.iconfinder.com/icons/3671720/) diff -Nru dav4tbsync-0.15/_locales/Readme.txt dav4tbsync-1.9/_locales/Readme.txt --- dav4tbsync-0.15/_locales/Readme.txt 1970-01-01 00:00:00.000000000 +0000 +++ dav4tbsync-1.9/_locales/Readme.txt 2020-02-20 12:39:38.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 dav4tbsync-0.15/_locales/bg/dav.dtd dav4tbsync-1.9/_locales/bg/dav.dtd --- dav4tbsync-0.15/_locales/bg/dav.dtd 1970-01-01 00:00:00.000000000 +0000 +++ dav4tbsync-1.9/_locales/bg/dav.dtd 2020-02-20 12:39:38.000000000 +0000 @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -Nru dav4tbsync-0.15/_locales/bg/dav.properties dav4tbsync-1.9/_locales/bg/dav.properties --- dav4tbsync-0.15/_locales/bg/dav.properties 1970-01-01 00:00:00.000000000 +0000 +++ dav4tbsync-1.9/_locales/bg/dav.properties 2020-02-20 12:39:38.000000000 +0000 @@ -0,0 +1,87 @@ +menu.name=CalDAV и CardDAV + +defaultname.calendar=Календар +defaultname.contacts=Контакти + +syncstate.send.getfolders=Запитване за списък с ресурсите +syncstate.eval.folders=Обновяване на списъка с ресурсите + +syncstate.prepare.request.localchanges=Изпращане на местните промени +syncstate.send.request.localchanges=Изчакване за потвърждение, че местните промени са изпратени +syncstate.eval.response.localchanges=Обработка на потвърждението за местните промени + +syncstate.send.request.remotechanges=Изчакване на данни от сървъра за промени +syncstate.eval.response.remotechanges=Обработка на новите данни от сървъра + +status.networkerror=Не можах да се свръжа със сървъра. +status.404=HTTP грешка 404 (поисканият ресурс не беше намерен). +status.403=Връзката беше отхвърнена от сървъра (забранена). +status.401=Достъпът отказан, проверете потребителското име и паролата. +status.500=Непозната грешка от сървъра (HTTP грешка 500). +status.503=Услугата е недостъпна. +status.softerror=Пренебрегната грешка (##replace.1##) + +status.success.managed-by-lightning=Lightning +status.info.restored=Поради частично недостатъчни права за правене на промени, някои действия бяха отхвърлени от сървъра и местните промени бяха изтрити. +status.malformed-xml=Отговорът не е правилен XML. Синхронизацията беше прекъсната. Проверете протокола със събитията за подробности. +helplink.malformed-xml=https://github.com/jobisoft/DAV-4-TbSync/issues/104 +status.service-discovery-failed=Откриването на настройките по RFC6764 за „##replace.1##“ не проработи. Въведете друг адрес на сървър или направете ръчно настройване, за да въведете сами адресите. +status.rfc6764-lookup-failed=Запитването за „##replace.1##“ не намери адресите за CalDAV и CardDAV услугите. Въведете името на сървъра, за да продължи настройването. +status.missing-permission=Недостатъчни права: ##replace.1## +status.caldavservernotfound=Не беше намерен CalDAV сървър. +status.carddavservernotfound=Не беше намерен CardDAV сървър. + +config.custom=CalDAV и CardDAV настройки на сървъра + +add.serverprofile.discovery=Автоматична настройка +add.serverprofile.discovery.description=Повечето сървъри поддържат автоматично настойване, за което е нужно да въведете само електронен адрес или потребителско име и сървър. +add.serverprofile.discovery.details1=Въведете за автоматичното откриване на CalDAV и CardDAV адресите вашите данни за вход и съответния сървър (н.пр. “cloud.example.bg”) +add.serverprofile.discovery.details2=Ако потребителското ви име е адрес на електронна поща, указването на сървър не е задължително, тъй като информацията за настройките може да се извлече от доставчика (по RFC 6764). +add.serverprofile.discovery.server-optional=по желание + +add.serverprofile.custom=Ръчно настройване +add.serverprofile.custom.description=Адресите за CalDAV и CardDAV услугите може да се настроят ръчно. +add.serverprofile.custom.details1=Необходимите CalDAV и CardDAV адреси, съответно WebDAV адреса на потребителя, ще ви ги даде вашият доставчик. +add.serverprofile.custom.details2=Ако оставите един от двата адреса празни, съответната услуга ще бъде изключена за вашата регистрация. + +add.serverprofile.fruux=fruux +add.serverprofile.fruux.description=fruux е услуга, която синхронизира контакти, календари и задачи. Услугата се предлага от фирмата зад sabre/dav и седалището ѝ е в Германия. +add.serverprofile.posteo=Posteo +add.serverprofile.posteo.description=https://www.posteo.de +add.serverprofile.mbo=mailbox.org +add.serverprofile.mbo.description=mailbox.org е германски доставчик от германия с акцент върху сигурността за частни и бизнес клиенти. Предлага календари, контакти и място в облака. + +add.serverprofile.icloud=iCloud +add.serverprofile.icloud.description=https://www.icloud.com +add.serverprofile.icloud.details1=Потребителското име е вашият Apple ID. Паролата обаче не е паролата за вашия Apple ID! За да синхронизира TbSync вашите контакти и календари е необходимо да включите регистрацията с два фактора (2FA) за вашата iCloud-регистрация и да създадете отделна парола за приложението TbSync. +add.serverprofile.icloud.details2=Това е допълнително ниво на защита, наложено от Apple, което не дава достъп на приложенията до вашата Apple-регистрация. + +add.serverprofile.yahoo=Yahoo! +add.serverprofile.yahoo.description=https://www.yahoo.com +add.serverprofile.yahoo.details1=The requested password is an app-specific password for TbSync which you can create in your Yahoo! web portal. It is not your standard Yahoo! password. + +add.serverprofile.gmx.net=GMX.net (Europa) +add.serverprofile.gmx.net.description=https://www.gmx.net +add.serverprofile.gmx.com=GMX.com (USA) +add.serverprofile.gmx.com.description=https://www.gmx.com +add.serverprofile.web.de=WEB.de +add.serverprofile.web.de.description=https://www.web.de +add.serverprofile.google=Гугъл +add.serverprofile.google.description=https://accounts.google.com +add.serverprofile.google.details1=Гугъл използва съвременен подход за удостоверяване без потребителско име и парола. След като изберете „Напред >“, ще се пръкне джам. Влезте чрез него в Гугъл и позволете на “Provider for CalDAV & CardDAV” достъп до данните ви. + +acl.readwrite=Права за писане на сървъра: ##replace.1## +acl.readonly=Достъп до сървъра само за четене (изтрива местните промени) +acl.modify=промяна +acl.add=вмъквате +acl.delete=изтриване +acl.none=никакви + +add.spinner.validating=Проверяване на връзката към сървъра +add.spinner.query=Изпращане на RFC 6764 запитване до „##replace.1##“ + +autocomplete.WORK=служебен +autocomplete.HOME=личен +autocomplete.PREF=предпочитан + +status.gContactSync=Има несъвместимост с gContactSync при включена синхронизация на групите от контакти. Изключете едно от двете, докато грешката не бъде отстранена. diff -Nru dav4tbsync-0.15/_locales/bg/messages.json dav4tbsync-1.9/_locales/bg/messages.json --- dav4tbsync-0.15/_locales/bg/messages.json 1970-01-01 00:00:00.000000000 +0000 +++ dav4tbsync-1.9/_locales/bg/messages.json 2020-02-20 12:39:38.000000000 +0000 @@ -0,0 +1,8 @@ +{ + "extensionName": { + "message": "Доставчик за CalDAV и CardDAV" + }, + "extensionDescription": { + "message": "Разширява TbSync и позволява синхронизацията с CalDAV и CardDAV регистрации (контакти, задачи, календари)." + } +} \ No newline at end of file diff -Nru dav4tbsync-0.15/_locales/de/dav.dtd dav4tbsync-1.9/_locales/de/dav.dtd --- dav4tbsync-0.15/_locales/de/dav.dtd 1970-01-01 00:00:00.000000000 +0000 +++ dav4tbsync-1.9/_locales/de/dav.dtd 2020-02-20 12:39:38.000000000 +0000 @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -Nru dav4tbsync-0.15/_locales/de/dav.properties dav4tbsync-1.9/_locales/de/dav.properties --- dav4tbsync-0.15/_locales/de/dav.properties 1970-01-01 00:00:00.000000000 +0000 +++ dav4tbsync-1.9/_locales/de/dav.properties 2020-02-20 12:39:38.000000000 +0000 @@ -0,0 +1,87 @@ +menu.name=CalDAV & CardDAV + +defaultname.calendar=Kalender +defaultname.contacts=Kontakte + +syncstate.send.getfolders=Sende Anfrage bzgl. Ordnerliste +syncstate.eval.folders=Verarbeite Ordnerliste + +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.send.request.remotechanges=Warte auf Daten vom Server +syncstate.eval.response.remotechanges=Verarbeite Serverdaten + +status.networkerror=Verbindung zum Server fehlgeschlagen. +status.404=HTTP Fehler 404 (angeforderte Resource nicht gefunden). +status.403=Verbindung vom Server abgelehnt (nicht erlaubt). +status.401=Authentifizierung fehlgeschlagen, überprüfen Sie den Benutzernamen und das Passwort. +status.500=Unbekannter Server Fehler (HTTP Fehler 500). +status.503=Service nicht erreichbar. +status.softerror=Ignorierter Fehler (##replace.1##) + +status.success.managed-by-lightning=Lightning +status.info.restored=Wegen teilweise fehlender Schreibrechte wurden einige Aktionen vom Server zurückgewiesen und lokal rückgängig gemacht. +status.malformed-xml=Antwort enthält fehlerhaftes XML, Sync abgebrochen. Prüfen Sie bitte das Ereignisprotokoll für weitere Details. +helplink.malformed-xml=https://github.com/jobisoft/DAV-4-TbSync/issues/104 +status.service-discovery-failed=Die automatische Erkennung der CalDAV & CardDAV Service-Endpunkte von „##replace.1##“ war nicht erfolgreich. Versuchen Sie es unter Angabe einer anderen Serveradresse erneut oder wechseln Sie zur benutzerdefinierten Konfiguration, um die Service-Endpunkte selbst anzugeben. +status.rfc6764-lookup-failed=Die Abfrage von „##replace.1##“ lieferte nicht die benötigten Informationen bzgl. der CalDAV und CardDAV Service-Endpunkte. Bitte geben den Hostnamen ihres Servers an, um mit der automatischen Konfiguration fortzufahren. +status.missing-permission=Fehlende Berechtigung: ##replace.1## +status.caldavservernotfound=Es wurde kein CalDAV Server gefunden. +status.carddavservernotfound=Es wurde kein CardDAV Server gefunden. + +config.custom=CalDAV & CardDAV Server Konfiguration + +add.serverprofile.discovery=Automatische Konfiguration +add.serverprofile.discovery.description=Viele Dienstanbieter und Server unterstützen eine automatische Konfiguration, bei der nur eine E-Mail Adresse bzw. ein Benutzername und eine Serveradresse angegeben werden müssen. +add.serverprofile.discovery.details1=Geben Sie für die automatische Erkennung der CalDAV- und CardDAV-Dienstendpunkte Ihre Zugangsdaten und den Hostnamen Ihres Servers ein (z.B. „cloud.myserver.de“). +add.serverprofile.discovery.details2=Ist Ihr Benutzername eine E-Mail Adresse, wird die Angabe des Servers optional, da die Informationen bzgl. der Service-Endpunkte evtl. über eine RFC6764-Anfrage direkt von Ihrem Dienstanbieter bezogen werden können (falls dieser das unterstützt). +add.serverprofile.discovery.server-optional=optionale Angabe + +add.serverprofile.custom=Benutzerdefinierte Konfiguration +add.serverprofile.custom.description=Die CalDAV und CardDAV Service-Endpunkte können manuell konfiguriert werden. +add.serverprofile.custom.details1=Die benötigten CalDAV & CardDAV Service-Endpunkte bzw. die Prinzipal-Adressen sollten Sie bei Ihrem Serviceanbieter erfragen können. +add.serverprofile.custom.details2=Wenn Sie eine der beiden Adressen leer lassen, wird der entsprechende Dienst für dieses Konto deaktiviert. + +add.serverprofile.fruux=fruux +add.serverprofile.fruux.description=fruux ist ein Dienst, der Kontakte, Kalender und Aufgaben synchronisiert. Sie wird von der Firma hinter sabre/dav angetrieben und hat ihren Sitz in Deutschland. +add.serverprofile.posteo=Posteo +add.serverprofile.posteo.description=https://www.posteo.de +add.serverprofile.mbo=mailbox.org +add.serverprofile.mbo.description=mailbox.org ist ein sicherer, deutscher E-Mail-Anbieter für Privat- und Geschäftskunden, der auch Kalender, Kontakte und Cloud-Speicher bietet. + +add.serverprofile.icloud=iCloud +add.serverprofile.icloud.description=https://www.icloud.com +add.serverprofile.icloud.details1=Der angeforderte Benutzername ist Ihre Apple ID. Das angeforderte Passwort ist jedoch nicht das Passwort für Ihr Apple ID! Um mit TbSync auf Ihre Kontakte und Kalender zugreifen zu können, müssen Sie zwingend die Zwei-Faktor-Autorisierung für Ihr iCloud-Konto aktivieren und ein separates App-spezifisches Kennwort für TbSync erstellen. +add.serverprofile.icloud.details2=Dies ist eine von Apple eingeführte zusätzliche Sicherheitsebene, sodass Drittanbieter-Clients keinen Zugriff auf Ihr Apple-Konto erhalten. + +add.serverprofile.yahoo=Yahoo! +add.serverprofile.yahoo.description=https://www.yahoo.com +add.serverprofile.yahoo.details1=The requested password is an app-specific password for TbSync which you can create in your Yahoo! web portal. It is not your standard Yahoo! password. + +add.serverprofile.gmx.net=GMX.net (Europa) +add.serverprofile.gmx.net.description=https://www.gmx.net +add.serverprofile.gmx.com=GMX.com (USA) +add.serverprofile.gmx.com.description=https://www.gmx.com +add.serverprofile.web.de=WEB.de +add.serverprofile.web.de.description=https://www.web.de +add.serverprofile.google=Google +add.serverprofile.google.description=https://accounts.google.com +add.serverprofile.google.details1=Google verwendet eine moderne Authentifizierungsmethode und TbSync muss weder Ihren Benutzernamen noch Ihr Passwort kennen. Nachdem Sie auf "Weiter >" geklickt haben, wird sich ein Browserfenster öffnen, indem Sie sich an Ihrem Google-Konto anmelden können und dem "Provider for CalDAV & CardDAV" Zugriff auf Ihre Kontakte und Kalender erlauben können. + +acl.readwrite=Schreibrechte auf Server: ##replace.1## +acl.readonly=Serverzugriff nur lesend (verwerfe lokale Änderungen) +acl.modify=bearbeiten +acl.add=hinzufügen +acl.delete=löschen +acl.none=keine + +add.spinner.validating=Überprüfe Verbindung zum Server +add.spinner.query=Sende RFC6764-Anfrage an „##replace.1##“ + +autocomplete.WORK=dienstlich +autocomplete.HOME=privat +autocomplete.PREF=bevorzugt + +status.gContactSync=Es besteht eine Inkompatibilität mit gContactSync bei aktivierter Synchronisation der Kontaktgruppen. Bitte deaktivieren eines von beiden, solange der Fehler nicht behoben ist. diff -Nru dav4tbsync-0.15/_locales/de/messages.json dav4tbsync-1.9/_locales/de/messages.json --- dav4tbsync-0.15/_locales/de/messages.json 1970-01-01 00:00:00.000000000 +0000 +++ dav4tbsync-1.9/_locales/de/messages.json 2020-02-20 12:39:38.000000000 +0000 @@ -0,0 +1,8 @@ +{ + "extensionName": { + "message": "Provider für CalDAV & CardDAV" + }, + "extensionDescription": { + "message": "Erweitert TbSync und erlaubt die Synchronisation von CalDAV & CardDAV Konten (Kontakte, Aufgaben und Kalender)." + } +} \ No newline at end of file diff -Nru dav4tbsync-0.15/_locales/en-US/dav.dtd dav4tbsync-1.9/_locales/en-US/dav.dtd --- dav4tbsync-0.15/_locales/en-US/dav.dtd 1970-01-01 00:00:00.000000000 +0000 +++ dav4tbsync-1.9/_locales/en-US/dav.dtd 2020-02-20 12:39:38.000000000 +0000 @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -Nru dav4tbsync-0.15/_locales/en-US/dav.properties dav4tbsync-1.9/_locales/en-US/dav.properties --- dav4tbsync-0.15/_locales/en-US/dav.properties 1970-01-01 00:00:00.000000000 +0000 +++ dav4tbsync-1.9/_locales/en-US/dav.properties 2020-02-20 12:39:38.000000000 +0000 @@ -0,0 +1,87 @@ +menu.name=CalDAV & CardDAV + +defaultname.calendar=Calendar +defaultname.contacts=Contacts + +syncstate.send.getfolders=Requesting folder list +syncstate.eval.folders=Updating folder list + +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.send.request.remotechanges=Waiting for remote changes +syncstate.eval.response.remotechanges=Processing remote changes + +status.networkerror=Could not connect to server. +status.404=HTTP Error 404 (requested resource not found). +status.403=Server rejected connection (forbidden). +status.401=Could not authenticate, check username and password. +status.500=Unknown Server Error (HTTP Error 500). +status.503=Service unavailable. +status.softerror=Non fatal error (##replace.1##) + +status.success.managed-by-lightning=Lightning +status.info.restored=Due to partially missing write permissions, some actions were rejected by the server and reversed locally. +status.malformed-xml=Could not parse XML. Check event log for details. +helplink.malformed-xml=https://github.com/jobisoft/DAV-4-TbSync/issues/104 +status.service-discovery-failed=Automatic discovery of CalDAV & CardDAV service endpoints of “##replace.1##” has failed. Try again, specifying a different server address, or switch to the custom configuration to manually specify the service endpoints. +status.rfc6764-lookup-failed=The query of “##replace.1##” did not provide the required information regarding the CalDAV and CardDAV service endpoints. Please enter the hostname of your server to proceed with the automatic configuration. +status.missing-permission=Missing permission: ##replace.1## +status.caldavservernotfound=Could not find a CalDAV server. +status.carddavservernotfound=Could not find a CardDAV server. + +config.custom=CalDAV & CardDAV server configuration + +add.serverprofile.discovery=Automatic Configuration +add.serverprofile.discovery.description=Many service providers and servers support an automatic configuration process, which requires only an e-mail address or a username and a server address. +add.serverprofile.discovery.details1=For the automatic discovery of the CalDAV & CardDAV service endpoints, please enter your credentials and the host name of your server (e.g. "cloud.myserver.de"). +add.serverprofile.discovery.details2=If your username is an e-mail address, specifying the server becomes optional, as the information about the service endpoints may be obtained directly from your service provider via an RFC6764 request (if supported). +add.serverprofile.discovery.server-optional=optional + +add.serverprofile.custom=Manual Configuration +add.serverprofile.custom.description=The CalDAV and CardDAV service endpoints can be configured manually. +add.serverprofile.custom.details1=The required CalDAV & CardDAV service endpoints or the so called principal addresses should be provided by your service provider. +add.serverprofile.custom.details2=If you leave either address empty, the corresponding service will be disabled for this account. + +add.serverprofile.fruux=fruux +add.serverprofile.fruux.description=fruux is a service that syncs contacts, calendars and tasks. It's powered by the company behind sabre/dav and is based in Germany. +add.serverprofile.posteo=Posteo +add.serverprofile.posteo.description=https://www.posteo.de +add.serverprofile.mbo=mailbox.org +add.serverprofile.mbo.description=mailbox.org is a secure German e-mail provider for private and business customers, which also offers calendars, contacts and cloud storage. + +add.serverprofile.icloud=iCloud +add.serverprofile.icloud.description=https://www.icloud.com +add.serverprofile.icloud.details1=The requested user name is your Apple ID. Please note, that you may not use your Apple ID password here. You MUST enable two-factor authentication (2FA) for your iCloud account and create a separate app-specific password for TbSync. +add.serverprofile.icloud.details2=This is a security layer enforced by Apple, so that 3rd party clients do not gain access to your Apple account. + +add.serverprofile.yahoo=Yahoo! +add.serverprofile.yahoo.description=https://www.yahoo.com +add.serverprofile.yahoo.details1=The requested password is an app-specific password for TbSync which you can create in your Yahoo! web portal. It is not your standard Yahoo! password. + +add.serverprofile.gmx.net=GMX.net (Europe) +add.serverprofile.gmx.net.description=https://www.gmx.net +add.serverprofile.gmx.com=GMX.com (USA) +add.serverprofile.gmx.com.description=https://www.gmx.com +add.serverprofile.web.de=WEB.de +add.serverprofile.web.de.description=https://www.web.de +add.serverprofile.google=Google +add.serverprofile.google.description=https://accounts.google.com +add.serverprofile.google.details1=Google uses a modern authentication method and TbSync does not need to know your username or your password. After clicking "Next >", a browser window will be opened, where you can log into your Google account and allow the "Provider for CalDAV & CardDAV" to access your contacts and calendars. + +acl.readwrite=Server write permissions: ##replace.1## +acl.readonly=Read-only server access (revert local changes) +acl.modify=modify +acl.add=add +acl.delete=delete +acl.none=none + +add.spinner.validating=Verifying connection to server +add.spinner.query=Sending RFC6764 request to “##replace.1##” + +autocomplete.WORK=business +autocomplete.HOME=private +autocomplete.PREF=preferred + +status.gContactSync=There is an incompatibility with gContactSync with activated contact group synchronization. Please deactivate one of them as long as the error is not resolved. diff -Nru dav4tbsync-0.15/_locales/en-US/messages.json dav4tbsync-1.9/_locales/en-US/messages.json --- dav4tbsync-0.15/_locales/en-US/messages.json 1970-01-01 00:00:00.000000000 +0000 +++ dav4tbsync-1.9/_locales/en-US/messages.json 2020-02-20 12:39:38.000000000 +0000 @@ -0,0 +1,8 @@ +{ + "extensionName": { + "message": "Provider for CalDAV & CardDAV" + }, + "extensionDescription": { + "message": "Add sync support for CalDAV & CardDAV accounts to TbSync." + } +} diff -Nru dav4tbsync-0.15/_locales/fr/dav.dtd dav4tbsync-1.9/_locales/fr/dav.dtd --- dav4tbsync-0.15/_locales/fr/dav.dtd 1970-01-01 00:00:00.000000000 +0000 +++ dav4tbsync-1.9/_locales/fr/dav.dtd 2020-02-20 12:39:38.000000000 +0000 @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -Nru dav4tbsync-0.15/_locales/fr/dav.properties dav4tbsync-1.9/_locales/fr/dav.properties --- dav4tbsync-0.15/_locales/fr/dav.properties 1970-01-01 00:00:00.000000000 +0000 +++ dav4tbsync-1.9/_locales/fr/dav.properties 2020-02-20 12:39:38.000000000 +0000 @@ -0,0 +1,87 @@ +menu.name=CalDAV & CardDAV + +defaultname.calendar=Agenda +defaultname.contacts=Contacts + +syncstate.send.getfolders=Demande de la liste des dossiers +syncstate.eval.folders=Mise à jour de la liste des dossiers + +syncstate.prepare.request.localchanges=Envoi des modifications locales +syncstate.send.request.localchanges=En attente de la confirmation de réception des modifications locales +syncstate.eval.response.localchanges=Traitement de la confirmation de réception des modifications locales + +syncstate.send.request.remotechanges=En attente des modifications distantes +syncstate.eval.response.remotechanges=En cours de traitement des modifications distantes + +status.networkerror=La connexion au serveur a échoué. +status.404=HTTP Erreur 404 (la ressource demandée n'a pas été trouvée). +status.403=Le serveur a refusé la connexion (accès interdit). +status.401=L'authentification a échoué. Veuillez vérifier le couple nom d'utilisateur/mot de passe. +status.500=Erreur serveur inconnue(Erreur HTTP 500). +status.503=Service indisponible. +status.softerror=Erreur non critique (##replace.1##) + +status.success.managed-by-lightning=Lightning +status.info.restored=À cause de droits en écriture partiellement insuffisants, certaines actions ont été refusées par le serveur et annulées localement. +status.malformed-xml=Analyse de l'XML impossible. Veuillez consulter le journal de débogage pour plus de détails. +helplink.malformed-xml=https://github.com/jobisoft/DAV-4-TbSync/issues/59#issuecomment-459685281 +status.service-discovery-failed=La découverte automatique des URL d'accès aux services CalDAV et CardDAV de “##replace.1###” a échoué. Veuillez réessayer, en spécifiant une adresse de serveur différente, ou passez à la configuration personnalisée, afin de spécifier manuellement les URL en question. +status.rfc6764-lookup-failed=L'appel à "##replace.1##” n'a pas permis de déterminer les informations nécessaires relatives aux URL des services CalDAV et CardDAV. Veuillez entrer le nom d'hôte de votre serveur afin de continuer la configuration automatique. +status.missing-permission=Droit manquant: ##replace.1## +status.caldavservernotfound=Impossible de trouver un serveur CalDAV. +status.carddavservernotfound=Impossible de trouver un serveur CardDAV. + +config.custom=Configuration des serveurs CalDAV et CardDAV + +add.serverprofile.discovery=Configuration automatique +add.serverprofile.discovery.description=De nombreux fournisseurs de services et de serveurs permettent un processus de configuration automatique, ce qui ne nécessite que l'adresse du serveur, ainsi qu'un nom d'utilisateur ou une adresse de courriel. +add.serverprofile.discovery.details1=Pour la découverte automatique des URL d'accès aux services CalDAV & CardDAV, veuillez entrer vos identifiants et le nom d'hôte de votre serveur (par exemple "cloud.monserveur.example"). +add.serverprofile.discovery.details2=Si votre nom d'utilisateur est une adresse e-mail, spécifier le serveur devient optionnel, car les informations sur les URL d'accès au service peuvent être obtenues automatiquement auprès de votre fournisseur de services via une requête RFC6764 (pour autant que votre fournisseur prenne en charge ce type de requêtes). +add.serverprofile.discovery.server-optional=facultatif + +add.serverprofile.custom=Configuration manuelle +add.serverprofile.custom.description=Les URL d'accès aux services CalDAV et CardDAV peuvent être configurées manuellement. +add.serverprofile.custom.details1=Les URL d'accès aux services CalDAV et CardDAV ou ce qu'on appelle les "adresses principales" devraient vous avoir été fournies par votre fournisseur de services. +add.serverprofile.custom.details2=Si vous laissez le champ d'adresse vide, le service correspondant sera désactivé pour ce compte. + +add.serverprofile.fruux=fruux +add.serverprofile.fruux.description=fruux est un service de synchronisation de contacts, d'agendas et de tâches. Il est fourni par la société éditrice de sabre/dav et est basé en Allemagne. +add.serverprofile.posteo=Posteo +add.serverprofile.posteo.description=https://www.posteo.de +add.serverprofile.mbo=mailbox.org +add.serverprofile.mbo.description=mailbox.org est un fournisseur de services de courriels allemand, à destination des particuliers et des entreprises. Ils fournissent également l'hébergement de calendriers, de contacts et des services cloud. + +add.serverprofile.icloud=iCloud +add.serverprofile.icloud.description=https://www.icloud.com +add.serverprofile.icloud.details1=Le nom d'utilisateur demandé est votre Apple ID. Veuillez noter que vous ne pouvez pas utiliser votre mot de passe Apple ID ici. Vous DEVEZ action l'authentification à deux facteurs (2FA) pour votre compte iCloud et créer un mot de passe d'application spécialement pour TbSync. +add.serverprofile.icloud.details2=Il s'agit d'une mesure de sécurité imposée par Apple, afin que les clients tiers n'aient pas accès à l'ensemble de votre compte Apple. + +add.serverprofile.yahoo=Yahoo! +add.serverprofile.yahoo.description=https://www.yahoo.com +add.serverprofile.yahoo.details1=The requested password is an app-specific password for TbSync which you can create in your Yahoo! web portal. It is not your standard Yahoo! password. + +add.serverprofile.gmx.net=GMX.net (Europe) +add.serverprofile.gmx.net.description=https://www.gmx.net +add.serverprofile.gmx.com=GMX.com (USA) +add.serverprofile.gmx.com.description=https://www.gmx.com +add.serverprofile.web.de=WEB.de +add.serverprofile.web.de.description=https://www.web.de +add.serverprofile.google=Google +add.serverprofile.google.description=https://accounts.google.com +add.serverprofile.google.details1=Google utilise une méthode d'authentification moderne, et Tbsync n'a pas besoin de connaître votre nom d'utilisateur ou votre mot de passe. Après avoir cliqué sur "Suivant", une fenêtre de navigateur s'ouvrira, dans laquelle vous pourrez vous connecter à votre compte Google et autoriser "Provider for CalDAV & CardDAV" à accéder à vos contacts et calendriers. + +acl.readwrite=Droits en écriture sur le serveur: ##replace.1## +acl.readonly=Accès au serveur en lecture seule (annule les modification locales) +acl.modify=modifier +acl.add=ajouter +acl.delete=supprimer +acl.none=aucun + +add.spinner.validating=Vérification de la connexion au serveur +add.spinner.query=Envoi d'une demande RFC6764 à «##replace.1## » + +autocomplete.WORK=travail +autocomplete.HOME=privé +autocomplete.PREF=favori + +status.gContactSync=Il y a une incompatibilité avec gContactSync, lorsque la synchronisation des groupes de contacts est activée. Tant que ce roblème n'est pas résolu, veuillez désactiver le compte ou la synchronisation des groupes. diff -Nru dav4tbsync-0.15/_locales/fr/messages.json dav4tbsync-1.9/_locales/fr/messages.json --- dav4tbsync-0.15/_locales/fr/messages.json 1970-01-01 00:00:00.000000000 +0000 +++ dav4tbsync-1.9/_locales/fr/messages.json 2020-02-20 12:39:38.000000000 +0000 @@ -0,0 +1,8 @@ +{ + "extensionName": { + "message": "Provider pour CalDAV & CardDAV" + }, + "extensionDescription": { + "message": "Ajoute à TbSync la prise en charge des comptes CalDAV & CardDAV." + } +} \ No newline at end of file diff -Nru dav4tbsync-0.15/_locales/hu/dav.dtd dav4tbsync-1.9/_locales/hu/dav.dtd --- dav4tbsync-0.15/_locales/hu/dav.dtd 1970-01-01 00:00:00.000000000 +0000 +++ dav4tbsync-1.9/_locales/hu/dav.dtd 2020-02-20 12:39:38.000000000 +0000 @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -Nru dav4tbsync-0.15/_locales/hu/dav.properties dav4tbsync-1.9/_locales/hu/dav.properties --- dav4tbsync-0.15/_locales/hu/dav.properties 1970-01-01 00:00:00.000000000 +0000 +++ dav4tbsync-1.9/_locales/hu/dav.properties 2020-02-20 12:39:38.000000000 +0000 @@ -0,0 +1,87 @@ +menu.name=CalDAV és CardDAV + +defaultname.calendar=naptár +defaultname.contacts=kapcsolatok + +syncstate.send.getfolders=Mappalisták lekérése +syncstate.eval.folders=Mappalisták frissítése + +syncstate.prepare.request.localchanges=Helyi változtatások küldése +syncstate.send.request.localchanges=Várakozás a helyi változtatások igazolásra +syncstate.eval.response.localchanges=Helyi változtatások igazolása feldolgozása + +syncstate.send.request.remotechanges=Várakozás a távoli változtatások +syncstate.eval.response.remotechanges=A távoli változtatások feldolgozása + +status.networkerror=Nem tudott csatlakozni a kiszolgálóhoz. +status.404=404-es HTTP hibakód (nem található). +status.403=403-as HTTP hibakód (hozzáférés megtagadva/tiltott). +status.401=Nem sikerült hitelesíteni, ellenőrizni a felhasználónevet és a jelszót. +status.500=500-as hibakód (belső kiszolgálóhiba). +status.503=503-as hibakód (a szolgáltatás nem érhető el). +status.softerror=Kihagyta a hibát (##replace.1##) + +status.success.managed-by-lightning=Lightning +status.info.restored=A hiányzó írási jogosultságok miatt egyes műveleteket a szerver elutasított, és helyileg visszafordított. +status.malformed-xml=Nem sikerült elemezni az XML-t. Ellenőrizze az eseménynaplót a részletekért. +helplink.malformed-xml=https://github.com/jobisoft/DAV-4-TbSync/issues/104 +status.service-discovery-failed=Automatic discovery of CalDAV & CardDAV service endpoints of “##replace.1##” has failed. Try again, specifying a different server address, or switch to the custom configuration to manually specify the service endpoints. +status.rfc6764-lookup-failed=The query of “##replace.1##” did not provide the required information regarding the CalDAV and CardDAV service endpoints. Please enter the hostname of your server to proceed with the automatic configuration. +status.missing-permission=Hiányzó engedély: ##replace.1## +status.caldavservernotfound=Nem található CalDAV szerver. +status.carddavservernotfound=Nem található CardDAV szerver. + +config.custom=A CalDAV és CardDAV kiszolgáló beállításai + +add.serverprofile.discovery=Önműködő konfigurálás +add.serverprofile.discovery.description=Many service providers and servers support an automatic configuration process, which requires only an e-mail address or a username and a server address. +add.serverprofile.discovery.details1=For the automatic discovery of the CalDAV & CardDAV service endpoints, please enter your credentials and the host name of your server (e.g. "cloud.myserver.de"). +add.serverprofile.discovery.details2=If your username is an e-mail address, specifying the server becomes optional, as the information about the service endpoints may be obtained directly from your service provider via an RFC6764 request (if supported). +add.serverprofile.discovery.server-optional=választható + +add.serverprofile.custom=Kézi beállítás +add.serverprofile.custom.description=A CalDAV és a CardDAV szolgáltatás végpontjai kézzel beállításhatók. +add.serverprofile.custom.details1=A szükséges CalDAV és CardDAV szolgáltatás végpontokat (principal addresses - főcímeket) a szolgáltatónak kell megadnia. +add.serverprofile.custom.details2=Ha a két címet üresen hagyja, akkor a megfelelő szolgáltatás le lesz tiltva erre a fiókra. + +add.serverprofile.fruux=fruux +add.serverprofile.fruux.description=A „fruux” egy szolgáltatás, amely összehangolja a névjegyzékeket, naptárakat és feladatokat. A németország cég támogatottja a sabre/dav. +add.serverprofile.posteo=Posteo +add.serverprofile.posteo.description=https://www.posteo.de +add.serverprofile.mbo=mailbox.org +add.serverprofile.mbo.description=mailbox.org is a secure German e-mail provider for private and business customers, which also offers calendars, contacts and cloud storage. + +add.serverprofile.icloud=iCloud +add.serverprofile.icloud.description=https://www.icloud.com +add.serverprofile.icloud.details1=A kért felhasználónév az Ön Apple-azonosítója. Vegye figyelembe, hogy itt nem használhatja az Apple-azonosító jelszavát. A 2FA (two-factor authentication – két faktoros hitelesítés) engedélyeznie kell iCloud-fiókja számára, és különálló alkalmazásspecifikus jelszót kell létrehoznia a Thunderbird-összehangolás számára. +add.serverprofile.icloud.details2=Ez egy olyan biztonsági réteg, amelyet az Apple hajt végre, így harmadik fél ügyfelei nem férnek hozzá az Ön Apple-fiókjához. + +add.serverprofile.yahoo=Yahoo! +add.serverprofile.yahoo.description=https://www.yahoo.com +add.serverprofile.yahoo.details1=The requested password is an app-specific password for TbSync which you can create in your Yahoo! web portal. It is not your standard Yahoo! password. + +add.serverprofile.gmx.net=GMX.net (Európa) +add.serverprofile.gmx.net.description=https://www.gmx.net +add.serverprofile.gmx.com=GMX.com (Amerikai egyesült államok) +add.serverprofile.gmx.com.description=https://www.gmx.com +add.serverprofile.web.de=WEB.de +add.serverprofile.web.de.description=https://www.web.de +add.serverprofile.google=Google +add.serverprofile.google.description=https://accounts.google.com +add.serverprofile.google.details1=Google uses a modern authentication method and TbSync does not need to know your username or your password. After clicking "Next >", a browser window will be opened, where you can log into your Google account and allow the "Provider for CalDAV & CardDAV" to access your contacts and calendars. + +acl.readwrite=írásjog: ##replace.1## +acl.readonly=csak olvasható +acl.modify=módosít +acl.add=hozzáad +acl.delete=töröl +acl.none=egyik sem + +add.spinner.validating=Ellenőrizze a kapcsolatot a szerverrel +add.spinner.query=RFC6764 kérés küldése a(z) „##replace.1##” címre + +autocomplete.WORK=üzlet +autocomplete.HOME=magán +autocomplete.PREF=előnyben részesített + +status.gContactSync=A gContactSync összeegyeztethetetlen az aktivált kapcsolattartó csoport összehangolásával. Kérjük, kapcsolja ki az egyiket, amíg a hiba nem oldódik meg. diff -Nru dav4tbsync-0.15/_locales/hu/messages.json dav4tbsync-1.9/_locales/hu/messages.json --- dav4tbsync-0.15/_locales/hu/messages.json 1970-01-01 00:00:00.000000000 +0000 +++ dav4tbsync-1.9/_locales/hu/messages.json 2020-02-20 12:39:38.000000000 +0000 @@ -0,0 +1,8 @@ +{ + "extensionName": { + "message": "A CalDAV és CardDAV szolgáltató" + }, + "extensionDescription": { + "message": "A CalDAV- és CardDAV-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 dav4tbsync-0.15/_locales/it/dav.dtd dav4tbsync-1.9/_locales/it/dav.dtd --- dav4tbsync-0.15/_locales/it/dav.dtd 1970-01-01 00:00:00.000000000 +0000 +++ dav4tbsync-1.9/_locales/it/dav.dtd 2020-02-20 12:39:38.000000000 +0000 @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -Nru dav4tbsync-0.15/_locales/it/dav.properties dav4tbsync-1.9/_locales/it/dav.properties --- dav4tbsync-0.15/_locales/it/dav.properties 1970-01-01 00:00:00.000000000 +0000 +++ dav4tbsync-1.9/_locales/it/dav.properties 2020-02-20 12:39:38.000000000 +0000 @@ -0,0 +1,87 @@ +menu.name=CalDAV e CardDAV + +defaultname.calendar=calendario +defaultname.contacts=contatti + +syncstate.send.getfolders=Richiesta elenco cartelle in corso +syncstate.eval.folders=Aggiornamento elenco cartelle 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.send.request.remotechanges=In attesa delle modifiche remote +syncstate.eval.response.remotechanges=Elaborazione modifiche remote in corso + +status.networkerror=Impossibile connettersi al server. +status.404=Errore HTTP 404 (risorsa richiesta non trovata). +status.403=Il server ha rifiutato la connessione (vietato). +status.401=Impossibile autenticarsi, controllare nome utente e password. +status.500=Errore server sconosciuto (errore HTTP 500). +status.503=Servizio non disponibile. +status.softerror=Errore saltato (##replace.1##) + +status.success.managed-by-lightning=Lightning +status.info.restored=A causa della mancanza di permessi di scrittura, alcune azioni sono state respinte dal server e annullate localmente. +status.malformed-xml=Impossibile analizzare XML. Controllare il registro eventi per i dettagli. +helplink.malformed-xml=https://github.com/jobisoft/DAV-4-TbSync/issues/104 +status.service-discovery-failed=L'individuazione automatica degli endpoint servizio CalDAV e CardDAV di "##replace.1##" non è riuscita. Riprova specificando un indirizzo server diverso o passa alla modalità configurazione personalizzata per specificare gli endpoint servizio manualmente. +status.rfc6764-lookup-failed=L'interrogazione di "##replace.1##" non ha fornito le informazioni richieste relative agli endpoint dei servizi CalDAV e CardDAV. Immettere il nome host del server per procedere con la configurazione automatica. +status.missing-permission=Permesso mancante: ##replace.1## +status.caldavservernotfound=Impossibile trovare un server CalDAV. +status.carddavservernotfound=Impossibile trovare un server CardDAV. + +config.custom=Configurazione server CalDAV & CardDAV + +add.serverprofile.discovery=Configurazione automatica +add.serverprofile.discovery.description=Molti provider di servizi e server supportano un processo di configurazione automatico che richiede solamente un indirizzo di posta elettronica o un nome utente e l'indirizzo del server. +add.serverprofile.discovery.details1=Per rilevare automaticamente gli endpoint servizio CalDAV e CardDAV, immetti le tue credenziali e il nome host del server (ad es. "cloud.myserver.de"). +add.serverprofile.discovery.details2=Se il tuo nome utente è un indirizzo di posta elettronica, specificare il server diventa facoltativo in quanto le informazioni sugli endpoint servizio possono essere ottenute direttamente dal provider di servizi tramite una richiesta RFC6764 (se supportata). +add.serverprofile.discovery.server-optional=facoltativo + +add.serverprofile.custom=Configurazione personalizzata +add.serverprofile.custom.description=Gli endpoint servizio CalDAV e CardDAV possono essere configurati manualmente. +add.serverprofile.custom.details1=Gli endpoint servizio CalDAV e CardDAV richiesti o i cosiddetti indirizzi principali dovrebbero essere forniti dal tuo provider di servizi. +add.serverprofile.custom.details2=Se si lascia un indirizzo vuoto, il servizio corrispondente sarà disabilitato per quest'account. + +add.serverprofile.fruux=fruux +add.serverprofile.fruux.description=fruux è un servizio che sincronizza contatti, calendari e attività. È alimentato dalla società dietro sabre/dav e ha sede in Germania. +add.serverprofile.posteo=Posteo +add.serverprofile.posteo.description=https://www.posteo.de +add.serverprofile.mbo=mailbox.org +add.serverprofile.mbo.description=mailbox.org è un provider di posta elettronica tedesco sicuro per i clienti privati e business che offre anche calendari, contatti e archiviazione cloud. + +add.serverprofile.icloud=iCloud +add.serverprofile.icloud.description=https://www.icloud.com +add.serverprofile.icloud.details1=Il nome utente richiesto è il tuo ID Apple. Nota che non puoi utilizzare la password del tuo ID Apple qui. DEVI abilitare l'autenticazione a due fattori per il tuo account iCloud e creare una password specifica dell'app per TbSync. +add.serverprofile.icloud.details2=Questa è una misura di sicurezza imposta da Apple in modo che client di terze parti non ottengano l'accesso al tuo account Apple. + +add.serverprofile.yahoo=Yahoo! +add.serverprofile.yahoo.description=https://www.yahoo.com +add.serverprofile.yahoo.details1=La password richiesta è una password specifica per l'app per TbSync che puoi creare nel tuo portale web Yahoo!. Non è la tua password standard di Yahoo. + +add.serverprofile.gmx.net=GMX.net (Europa) +add.serverprofile.gmx.net.description=https://www.gmx.net +add.serverprofile.gmx.com=GMX.com (Stati Uniti d'America) +add.serverprofile.gmx.com.description=https://www.gmx.com +add.serverprofile.web.de=WEB.de +add.serverprofile.web.de.description=https://www.web.de +add.serverprofile.google=Google +add.serverprofile.google.description=https://accounts.google.com +add.serverprofile.google.details1=Google utilizza un metodo di autenticazione moderno e TbSync non ha bisogno di conoscere il tuo nome utente o la tua password. Dopo aver fatto clic su "Avanti >" verrà aperta una finestra del browser dove puoi accedere al tuo account Google e consentire al "Provider for CalDAV & CardDAV" di accedere ai tuoi contatti e calendari. + +acl.readwrite=Scrivi permesso: ##replace.1## +acl.readonly=sola lettura +acl.modify=modificare +acl.add=inserisci +acl.delete=elimina +acl.none=nessuna + +add.spinner.validating=Controlla la connessione al server +add.spinner.query=Invio richiesta RFC6764 a "##replace.1##" in corso + +autocomplete.WORK=lavoro +autocomplete.HOME=personale +autocomplete.PREF=preferito + +status.gContactSync=Il software è incompatibile con gContactSync con la sincronizzazione gruppi contatti attivata. Disattivarne uno finché l'errore non sarà risolto. diff -Nru dav4tbsync-0.15/_locales/it/messages.json dav4tbsync-1.9/_locales/it/messages.json --- dav4tbsync-0.15/_locales/it/messages.json 1970-01-01 00:00:00.000000000 +0000 +++ dav4tbsync-1.9/_locales/it/messages.json 2020-02-20 12:39:38.000000000 +0000 @@ -0,0 +1,8 @@ +{ + "extensionName": { + "message": "Provider CalDAV e CardDAV" + }, + "extensionDescription": { + "message": "Aggiunge il supporto per la sincronizzazione degli account CalDAV e CardDAV a TbSync." + } +} \ No newline at end of file diff -Nru dav4tbsync-0.15/_locales/pl/dav.dtd dav4tbsync-1.9/_locales/pl/dav.dtd --- dav4tbsync-0.15/_locales/pl/dav.dtd 1970-01-01 00:00:00.000000000 +0000 +++ dav4tbsync-1.9/_locales/pl/dav.dtd 2020-02-20 12:39:38.000000000 +0000 @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -Nru dav4tbsync-0.15/_locales/pl/dav.properties dav4tbsync-1.9/_locales/pl/dav.properties --- dav4tbsync-0.15/_locales/pl/dav.properties 1970-01-01 00:00:00.000000000 +0000 +++ dav4tbsync-1.9/_locales/pl/dav.properties 2020-02-20 12:39:38.000000000 +0000 @@ -0,0 +1,87 @@ +menu.name=CalDAV i CardDAV + +defaultname.calendar=Kalendarz +defaultname.contacts=Kontakty + +syncstate.send.getfolders=Żądanie listy folderów +syncstate.eval.folders=Aktualizowanie listy folderów + +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.send.request.remotechanges=Oczekiwanie na zdalne zmiany +syncstate.eval.response.remotechanges=Przetwarzanie zdalnych zmian + +status.networkerror=Nie można połączyć z serwerem. +status.404=Błąd HTTP 404 (nie znaleziono żądanego zasobu). +status.403=Serwer odrzucił połączenie (zabronione). +status.401=Nie można uwierzytelnić, sprawdź nazwę użytkownika i hasło. +status.500=Nieznany błąd serwera (błąd HTTP 500). +status.503=Usługa niedostępna. +status.softerror=Błąd niekrytyczny (##replace.1##) + +status.success.managed-by-lightning=Lightning +status.info.restored=Z powodu częściowo brakujących uprawnień do zapisu, niektóre działania zostały odrzucone przez serwer i cofnięte lokalnie. +status.malformed-xml=Nie można przeanalizować XML. Sprawdź dziennik zdarzeń, aby uzyskać szczegółowe informacje. +helplink.malformed-xml=https://github.com/jobisoft/DAV-4-TbSync/issues/104 +status.service-discovery-failed=Automatyczne wykrywanie punktów końcowych usługi CalDAV i CardDAV dla “##replace.1##” nie powiodło się. Spróbuj ponownie, podając inny adres serwera lub przejdź do konfiguracji niestandardowej, aby ręcznie określić punkty końcowe usługi. +status.rfc6764-lookup-failed=Zapytanie “##replace.1##” nie dostarczyło wymaganych informacji dotyczących punktów końcowych usługi CalDAV i CardDAV. Wprowadź nazwę hosta swojego serwera, aby kontynuować automatyczną konfigurację. +status.missing-permission=Brak uprawnienia: ##replace.1## +status.caldavservernotfound=Nie można znaleźć serwera CalDAV. +status.carddavservernotfound=Nie można znaleźć serwera CardDAV. + +config.custom=Konfiguracja serwera CalDAV i CardDAV + +add.serverprofile.discovery=Automatyczna Konfiguracja +add.serverprofile.discovery.description=Wielu dostawców usług i serwerów wspiera proces automatycznej konfiguracji, który wymaga tylko adresu e-mail lub nazwy użytkownika i adresu serwera. +add.serverprofile.discovery.details1=Aby automatycznie wykryć punkty końcowe usługi CalDAV i CardDAV, wprowadź swoje dane uwierzytelniające i nazwę hosta swojego serwera (np. "cloud.myserver.de"). +add.serverprofile.discovery.details2=Jeśli nazwa użytkownika to adres e-mail, określenie serwera staje się opcjonalne, ponieważ informacje o punktach końcowych usługi można uzyskać bezpośrednio od usługodawcy za pośrednictwem żądania RFC6764 (jeśli jest obsługiwane). +add.serverprofile.discovery.server-optional=opcjonalnie + +add.serverprofile.custom=Ręczna Konfiguracja +add.serverprofile.custom.description=Punkty końcowe usługi CalDAV i CardDAV można skonfigurować ręcznie. +add.serverprofile.custom.details1=Wymagane punkty końcowe usługi CalDAV i CardDAV lub tak zwane adresy główne powinien podać dostawca usługi. +add.serverprofile.custom.details2=Jeśli którykolwiek z adresów pozostanie pusty, odpowiednia usługa zostanie wyłączona dla tego konta. + +add.serverprofile.fruux=fruux +add.serverprofile.fruux.description=fruux to usługa synchronizująca kontakty, kalendarze i zadania. Jest napędzana przez firmę opartą o sabre/dav i z siedzibą w Niemczech. +add.serverprofile.posteo=Posteo +add.serverprofile.posteo.description=https://www.posteo.de +add.serverprofile.mbo=mailbox.org +add.serverprofile.mbo.description=mailbox.org to bezpieczny, niemiecki dostawca poczty elektronicznej dla klientów prywatnych i biznesowych, który oferuje również kalendarze, kontakty i przechowywanie w chmurze. + +add.serverprofile.icloud=iCloud +add.serverprofile.icloud.description=https://www.icloud.com +add.serverprofile.icloud.details1=Żądana nazwa użytkownika to Twój Apple ID. Pamiętaj, że nie możesz tutaj używać hasła Apple ID. Musisz włączyć uwierzytelnianie dwuskładnikowe (2FA) dla swojego konta iCloud i utworzyć osobne hasło aplikacji dla TbSync. +add.serverprofile.icloud.details2=Jest to warstwa bezpieczeństwa wymuszona przez Apple, aby klienci zewnętrzni nie uzyskali dostępu do Twojego konta Apple. + +add.serverprofile.yahoo=Yahoo! +add.serverprofile.yahoo.description=https://www.yahoo.com +add.serverprofile.yahoo.details1=Żądane hasło jest hasłem specyficznym dla aplikacji dla TbSync, które możesz utworzyć w portalu Yahoo! To nie jest Twoje standardowe hasło Yahoo! + +add.serverprofile.gmx.net=GMX.net (Europe) +add.serverprofile.gmx.net.description=https://www.gmx.net +add.serverprofile.gmx.com=GMX.com (USA) +add.serverprofile.gmx.com.description=https://www.gmx.com +add.serverprofile.web.de=WEB.de +add.serverprofile.web.de.description=https://www.web.de +add.serverprofile.google=Google +add.serverprofile.google.description=https://accounts.google.com +add.serverprofile.google.details1=Google stosuje nowoczesną metodę uwierzytelniania, a TbSync nie musi znać Twojej nazwy użytkownika ani hasła. Po kliknięciu "Dalej >" otworzy się okno przeglądarki, w którym możesz zalogować się na swoje konto Google i zezwolić "Provider for CalDAV & CardDAV" na dostęp do Twoich kontaktów i kalendarzy. + +acl.readwrite=Uprawnienia do zapisu na serwerze: ##replace.1## +acl.readonly=Dostęp do serwera tylko do odczytu (cofnij zmiany lokalne) +acl.modify=zmień +acl.add=dodaj +acl.delete=usuń +acl.none=żaden + +add.spinner.validating=Weryfikowanie połączenia z serwerem +add.spinner.query=Wysyłanie żądania RFC6764 do “##replace.1##” + +autocomplete.WORK=służbowy +autocomplete.HOME=domowy +autocomplete.PREF=preferowane + +status.gContactSync=Występuje niezgodność z gContactSync z aktywowaną synchronizacją grupy kontaktów. Dezaktywuj jeden z nich, dopóki błąd nie zostanie rozwiązany. diff -Nru dav4tbsync-0.15/_locales/pl/messages.json dav4tbsync-1.9/_locales/pl/messages.json --- dav4tbsync-0.15/_locales/pl/messages.json 1970-01-01 00:00:00.000000000 +0000 +++ dav4tbsync-1.9/_locales/pl/messages.json 2020-02-20 12:39:38.000000000 +0000 @@ -0,0 +1,8 @@ +{ + "extensionName": { + "message": "Dostawca dla CalDAV i CardDAV" + }, + "extensionDescription": { + "message": "Dodaj do TbSync wsparcie synchronizacji dla CalDAV i CardDAV." + } +} \ No newline at end of file diff -Nru dav4tbsync-0.15/_locales/pt_BR/dav.dtd dav4tbsync-1.9/_locales/pt_BR/dav.dtd --- dav4tbsync-0.15/_locales/pt_BR/dav.dtd 1970-01-01 00:00:00.000000000 +0000 +++ dav4tbsync-1.9/_locales/pt_BR/dav.dtd 2020-02-20 12:39:38.000000000 +0000 @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -Nru dav4tbsync-0.15/_locales/pt_BR/dav.properties dav4tbsync-1.9/_locales/pt_BR/dav.properties --- dav4tbsync-0.15/_locales/pt_BR/dav.properties 1970-01-01 00:00:00.000000000 +0000 +++ dav4tbsync-1.9/_locales/pt_BR/dav.properties 2020-02-20 12:39:38.000000000 +0000 @@ -0,0 +1,87 @@ +menu.name=CalDAV e CardDAV + +defaultname.calendar=calendário +defaultname.contacts=contatos + +syncstate.send.getfolders=Solicitando lista de pastas +syncstate.eval.folders=Atualizando lista de pastas + +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.send.request.remotechanges=Esperando por alterações remotas +syncstate.eval.response.remotechanges=Processando alterações remotas + +status.networkerror=Não foi possível conectar-se ao servidor. +status.404=HTTP Erro 404 (recurso solicitado não encontrado). +status.403=Conexão rejeitada pelo servidor (proibida). +status.401=Não foi possível autenticar, verifique o nome de usuário e a senha. +status.500=Erro de servidor desconhecido (HTTP Erro 500). +status.503=Serviço indisponível. +status.softerror=Erro não fatal (##replace.1##) + +status.success.managed-by-lightning=Lightning +status.info.restored=Devido à falta parcial de permissões de gravação, algumas ações foram rejeitadas pelo servidor e revertidas localmente. +status.malformed-xml=Não foi possível analisar XML. Verifique o log de eventos para obter detalhes. +helplink.malformed-xml=https://github.com/jobisoft/DAV-4-TbSync/issues/104 +status.service-discovery-failed=A descoberta automática dos serviços de CalDAV & CardDAV de "##replace.1##" falhou. Tente novamente, especificando um endereço de servidor diferente ou altere para a configuração personalizada para especificar manualmente as configurações. +status.rfc6764-lookup-failed=A consulta de "##replace.1##" não forneceu as informações necessárias sobre os serviços CalDAV e CardDAV. Por favor, digite o nome do host do seu servidor para prosseguir com a configuração automática. +status.missing-permission=Permissão ausente: ##replace.1## +status.caldavservernotfound=Não foi possível encontrar um servidor CalDAV. +status.carddavservernotfound=Não foi possível encontrar um servidor CardDAV. + +config.custom=Configurações do servidor CalDAV & CardDAV + +add.serverprofile.discovery=Configuração Automática +add.serverprofile.discovery.description=Muitos provedores de serviço e servidores suportam um processo de configuração automática, que requer apenas um endereço de e-mail ou um nome de usuário e um endereço de servidor. +add.serverprofile.discovery.details1=Para a descoberta automática dos pontos de extremidade do serviço CalDAV & CardDAV, por favor insira suas credenciais e o nome do host do seu servidor (ex.: "cloud.myserver.de"). +add.serverprofile.discovery.details2=Se seu nome de usuário é um endereço de e-mail, especificar o servidor torna-se opcional, como as informações sobre os pontos de extremidade do serviço podem ser obtidas diretamente do seu provedor de serviços através de um pedido RFC6764 (se suportado). +add.serverprofile.discovery.server-optional=opcional + +add.serverprofile.custom=Configuração manual +add.serverprofile.custom.description=Os serviços de CalDAV e CardDAV podem ser configurados manualmente. +add.serverprofile.custom.details1=Os serviços de CalDAV e CardDAV necessários ou endereços principais devem ser fornecidos pelo seu provedor de serviços. +add.serverprofile.custom.details2=Se você deixar o endereço vazio, o serviço correspondente será desativado para essa conta. + +add.serverprofile.fruux=fruux +add.serverprofile.fruux.description=fruux é um serviço que sincroniza contatos, calendários e tarefas. É alimentado pela empresa por trás de saber/dav e é baseado na Alemanha. +add.serverprofile.posteo=Posteo +add.serverprofile.posteo.description=https://www.posteo.de +add.serverprofile.mbo=mailbox.org +add.serverprofile.mbo.description=mailbox.org é um provedor de e-mail alemão seguro para uso pessoal e para negócios, que também oferece calendários, contatos e armazenamento na nuvem. + +add.serverprofile.icloud=iCloud +add.serverprofile.icloud.description=https://www.icloud.com +add.serverprofile.icloud.details1=O nome do usuário solicitado é seu ID da Apple. Por favor, note que você não pode usar sua senha da Apple ID aqui. Você DEVE ativar a autenticação de dois fatores (2FA) para sua conta do iCloud e criar uma senha específica do aplicativo separada para o TbSync. +add.serverprofile.icloud.details2=Essa é uma camada de segurança imposta pela Apple, portanto, os clientes de terceiros não obtêm acesso à sua conta da Apple. + +add.serverprofile.yahoo=Yahoo! +add.serverprofile.yahoo.description=https://www.yahoo.com +add.serverprofile.yahoo.details1=The requested password is an app-specific password for TbSync which you can create in your Yahoo! web portal. It is not your standard Yahoo! password. + +add.serverprofile.gmx.net=GMX.net (Europa) +add.serverprofile.gmx.net.description=https://www.gmx.net +add.serverprofile.gmx.com=GMX.com (E.U.A.) +add.serverprofile.gmx.com.description=https://www.gmx.com +add.serverprofile.web.de=WEB.de +add.serverprofile.web.de.description=https://www.web.de +add.serverprofile.google=Google +add.serverprofile.google.description=https://accounts.google.com +add.serverprofile.google.details1=O Google usa um método moderno de autenticação e o TbSync não precisa saber seu nome de usuário nem sua senha. Depois de clicar em "Avançar>", uma janela do navegador será aberta, onde você poderá fazer login na sua conta do Google e permitir que o "Provider for CalDAV & CardDAV" acesse seus contatos e calendários. + +acl.readwrite=Permissões de gravação do servidor: ##replace.1## +acl.readonly=Acesso ao servidor somente leitura (reverter alterações locais) +acl.modify=modificar +acl.add=adicionar +acl.delete=excluir +acl.none=nenhum + +add.spinner.validating=Verifique a conexão com o servidor +add.spinner.query=Enviando pedido RFC6764 para "##replace.1##" + +autocomplete.WORK=empresa +autocomplete.HOME=particular +autocomplete.PREF=preferido + +status.gContactSync=Existe uma incompatibilidade com o gContactSync com a sincronização de grupo de contatos ativado. Desative um deles, desde até que o erro não seja resolvido. diff -Nru dav4tbsync-0.15/_locales/pt_BR/messages.json dav4tbsync-1.9/_locales/pt_BR/messages.json --- dav4tbsync-0.15/_locales/pt_BR/messages.json 1970-01-01 00:00:00.000000000 +0000 +++ dav4tbsync-1.9/_locales/pt_BR/messages.json 2020-02-20 12:39:38.000000000 +0000 @@ -0,0 +1,8 @@ +{ + "extensionName": { + "message": "Provedor CalDAV e CardDAV" + }, + "extensionDescription": { + "message": "Adiciona suporte para sincronizar contas CalDAV e CardDAV com o TbSync." + } +} \ No newline at end of file diff -Nru dav4tbsync-0.15/_locales/ru/dav.dtd dav4tbsync-1.9/_locales/ru/dav.dtd --- dav4tbsync-0.15/_locales/ru/dav.dtd 1970-01-01 00:00:00.000000000 +0000 +++ dav4tbsync-1.9/_locales/ru/dav.dtd 2020-02-20 12:39:38.000000000 +0000 @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -Nru dav4tbsync-0.15/_locales/ru/dav.properties dav4tbsync-1.9/_locales/ru/dav.properties --- dav4tbsync-0.15/_locales/ru/dav.properties 1970-01-01 00:00:00.000000000 +0000 +++ dav4tbsync-1.9/_locales/ru/dav.properties 2020-02-20 12:39:38.000000000 +0000 @@ -0,0 +1,87 @@ +menu.name=CalDAV & CardDAV + +defaultname.calendar=календарь +defaultname.contacts=контакты + +syncstate.send.getfolders=Запрос списка папок +syncstate.eval.folders=Обновление списка папок + +syncstate.prepare.request.localchanges=Отправка локальных изменений +syncstate.send.request.localchanges=Ожидание подтверждения локальных изменений +syncstate.eval.response.localchanges=Обработка подтверждения локальных изменений + +syncstate.send.request.remotechanges=Ожидание удаленных изменений +syncstate.eval.response.remotechanges=Обработка удаленных изменений + +status.networkerror=Не удалось подключиться к серверу. +status.404=Запрошенный ресурс не найден (HTTP Ошибка 404). +status.403=Сервер отклонил соединение (запрещено) (HTTP Ошибка 403). +status.401=Не удалось аутентифицировать, проверить имя пользователя и пароль. (HTTP Ошибка 401). +status.500=Неизвестная ошибка сервера (HTTP Ошибка 500). +status.503=Сервис недоступен (HTTP Ошибка 503). +status.softerror=пропущенная ошибка (##replace.1##) + +status.success.managed-by-lightning=Lightning +status.info.restored=Из-за частично отсутствующих разрешений на запись некоторые действия были отклонены сервером и отменены локально. +status.malformed-xml=Не удалось разобрать XML. Проверьте журнал событий для деталей. +helplink.malformed-xml=https://github.com/jobisoft/DAV-4-TbSync/issues/104 +status.service-discovery-failed=Automatic discovery of CalDAV & CardDAV service endpoints of “##replace.1##” has failed. Try again, specifying a different server address, or switch to the custom configuration to manually specify the service endpoints. +status.rfc6764-lookup-failed=The query of “##replace.1##” did not provide the required information regarding the CalDAV and CardDAV service endpoints. Please enter the hostname of your server to proceed with the automatic configuration. +status.missing-permission=Отсутствует разрешение: ##replace.1## +status.caldavservernotfound=Не удалось найти сервер CalDAV. +status.carddavservernotfound=Не удалось найти сервер CardDAV. + +config.custom=CalDAV & CardDAV конфигурация сервера + +add.serverprofile.discovery=Автоматическая настройка +add.serverprofile.discovery.description=Many service providers and servers support an automatic configuration process, which requires only an e-mail address or a username and a server address. +add.serverprofile.discovery.details1=For the automatic discovery of the CalDAV & CardDAV service endpoints, please enter your credentials and the host name of your server (e.g. "cloud.myserver.de"). +add.serverprofile.discovery.details2=If your username is an e-mail address, specifying the server becomes optional, as the information about the service endpoints may be obtained directly from your service provider via an RFC6764 request (if supported). +add.serverprofile.discovery.server-optional=optional + +add.serverprofile.custom=Пользовательская конфигурация +add.serverprofile.custom.description=The CalDAV and CardDAV service endpoints can be configured manually. +add.serverprofile.custom.details1=The required CalDAV & CardDAV service endpoints or the so called principal addresses should be provided by your service provider. +add.serverprofile.custom.details2=Если вы оставите любой адрес пустым, соответствующая служба будет отключена для этого аккаунта. + +add.serverprofile.fruux=fruux +add.serverprofile.fruux.description=fruux - это сервис, который синхронизирует контакты, календари и задачи. Он работает на базе компании поддерживающей протокол sabre/dav и базируется в Германии. +add.serverprofile.posteo=Posteo +add.serverprofile.posteo.description=https://www.posteo.de +add.serverprofile.mbo=mailbox.org +add.serverprofile.mbo.description=mailbox.org is a secure German e-mail provider for private and business customers, which also offers calendars, contacts and cloud storage. + +add.serverprofile.icloud=iCloud +add.serverprofile.icloud.description=https://www.icloud.com +add.serverprofile.icloud.details1=Запрашиваемое имя пользователя - ваш Apple-ID. Обратите внимание, что вы не можете использовать свой пароль Apple-ID здесь. Вы ДОЛЖНЫ включить двухфакторную авторизацию для своей учетной записи iCloud и создать отдельный пароль приложения для TbSync. +add.serverprofile.icloud.details2=Это уровень безопасности, применяемый Apple, поэтому сторонние клиенты не могут получить доступ к вашей учетной записи Apple. + +add.serverprofile.yahoo=Yahoo! +add.serverprofile.yahoo.description=https://www.yahoo.com +add.serverprofile.yahoo.details1=The requested password is an app-specific password for TbSync which you can create in your Yahoo! web portal. It is not your standard Yahoo! password. + +add.serverprofile.gmx.net=GMX.net (Европа) +add.serverprofile.gmx.net.description=https://www.gmx.net +add.serverprofile.gmx.com=GMX.com (США) +add.serverprofile.gmx.com.description=https://www.gmx.com +add.serverprofile.web.de=WEB.de +add.serverprofile.web.de.description=https://www.web.de +add.serverprofile.google=Google +add.serverprofile.google.description=https://accounts.google.com +add.serverprofile.google.details1=Google uses a modern authentication method and TbSync does not need to know your username or your password. After clicking "Next >", a browser window will be opened, where you can log into your Google account and allow the "Provider for CalDAV & CardDAV" to access your contacts and calendars. + +acl.readwrite=Разрешение на запись: ##replace.1## +acl.readonly=только для чтения +acl.modify=модифицировать +acl.add=добавлять +acl.delete=удалять +acl.none=никто + +add.spinner.validating=Проверьте соединение с сервером +add.spinner.query=Sending RFC6764 request to “##replace.1##” + +autocomplete.WORK=business +autocomplete.HOME=private +autocomplete.PREF=preferred + +status.gContactSync=There is an incompatibility with gContactSync with activated contact group synchronization. Please deactivate one of them as long as the error is not resolved. diff -Nru dav4tbsync-0.15/_locales/ru/messages.json dav4tbsync-1.9/_locales/ru/messages.json --- dav4tbsync-0.15/_locales/ru/messages.json 1970-01-01 00:00:00.000000000 +0000 +++ dav4tbsync-1.9/_locales/ru/messages.json 2020-02-20 12:39:38.000000000 +0000 @@ -0,0 +1,8 @@ +{ + "extensionName": { + "message": "Provider for CalDAV & CardDAV" + }, + "extensionDescription": { + "message": "Добавляет в TbSync поддержку базирующегося на http/https протокола синхронизации для учетных записей CalDAV & CardDAV (контакты, задачи и календари)." + } +} \ No newline at end of file diff -Nru dav4tbsync-0.15/beta-release-channel-update.json dav4tbsync-1.9/beta-release-channel-update.json --- dav4tbsync-0.15/beta-release-channel-update.json 1970-01-01 00:00:00.000000000 +0000 +++ dav4tbsync-1.9/beta-release-channel-update.json 2020-02-20 12:39:38.000000000 +0000 @@ -0,0 +1,13 @@ +{ + "addons": { + "dav4tbsync@jobisoft.de": { + "updates": [ + { "version": "%VERSION%", + "update_info_url": "https://github.com/jobisoft/DAV-4-TbSync/releases", + "update_link": "%LINK%", + "applications": { + "gecko": { "strict_min_version": "68.0" } } } + ] + } + } +} \ No newline at end of file diff -Nru dav4tbsync-0.15/beta-release-channel-update.rdf dav4tbsync-1.9/beta-release-channel-update.rdf --- dav4tbsync-0.15/beta-release-channel-update.rdf 2019-02-26 16:26:01.000000000 +0000 +++ dav4tbsync-1.9/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/DAV-4-TbSync/releases - - - - - - - - - - diff -Nru dav4tbsync-0.15/bootstrap.js dav4tbsync-1.9/bootstrap.js --- dav4tbsync-0.15/bootstrap.js 2019-02-26 16:26:01.000000000 +0000 +++ dav4tbsync-1.9/bootstrap.js 2020-02-20 12:39:38.000000000 +0000 @@ -6,28 +6,28 @@ * 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 component = {}; 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, "dav", "//dav4tbsync/content/dav.js"); + await TbSync.providers.loadProvider(thisID, "dav", "chrome://dav4tbsync/content/provider.js"); } - }) + } } function install(data, reason) { @@ -37,33 +37,51 @@ } function startup(data, reason) { - //possible reasons: APP_STARTUP, ADDON_ENABLE, ADDON_INSTALL, ADDON_UPGRADE, or ADDON_DOWNGRADE. - - //set default prefs (examples) - let branch = Services.prefs.getDefaultBranch("extensions.dav4tbsync."); - branch.setIntPref("maxitems", 50); - branch.setCharPref("clientID.type", "TbSync"); - branch.setCharPref("clientID.useragent", "Thunderbird CalDAV/CardDAV"); - branch.setBoolPref("addCredentialsToUrl", false); + // Possible reasons: APP_STARTUP, ADDON_ENABLE, ADDON_INSTALL, ADDON_UPGRADE, or ADDON_DOWNGRADE. thisID = data.id; - Services.obs.addObserver(onInitDoneObserver, "tbsync.init.done", false); + Services.obs.addObserver(onInitDoneObserver, "tbsync.observer.initialized", 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.scriptloader.loadSubScript("chrome://dav4tbsync/content/includes/tbSyncDavCalendar.js", component); + let registrar = Components.manager.QueryInterface(Components.interfaces.nsIComponentRegistrar); + registrar.registerFactory( + component.tbSyncDavCalendar.prototype.classID, + component.tbSyncDavCalendar.prototype.classDescription, + component.tbSyncDavCalendar.prototype.contractID, + component.NSGetFactory(component.tbSyncDavCalendar.prototype.classID) + ); + + // 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 + let registrar = Components.manager.QueryInterface(Components.interfaces.nsIComponentRegistrar); + registrar.unregisterFactory( + component.tbSyncDavCalendar.prototype.classID, + component.NSGetFactory(component.tbSyncDavCalendar.prototype.classID) + ); + + 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("dav"); } 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); } diff -Nru dav4tbsync-0.15/chrome.manifest dav4tbsync-1.9/chrome.manifest --- dav4tbsync-0.15/chrome.manifest 2019-02-26 16:26:01.000000000 +0000 +++ dav4tbsync-1.9/chrome.manifest 2020-02-20 12:39:38.000000000 +0000 @@ -1,8 +1,14 @@ content dav4tbsync content/ -skin dav4tbsync classic/1.0 skin/ -locale dav4tbsync de locale/de/ -locale dav4tbsync en-US locale/en-US/ -locale dav4tbsync hu locale/hu/ -locale dav4tbsync it locale/it/ -locale dav4tbsync ru locale/ru/ -locale dav4tbsync pt-BR locale/pt-BR/ + +skin dav4tbsync classic/1.0 skin/ + +locale dav4tbsync bg _locales/bg/ +locale dav4tbsync de _locales/de/ +locale dav4tbsync en-US _locales/en-US/ +locale dav4tbsync fr _locales/fr/ +locale dav4tbsync hu _locales/hu/ +locale dav4tbsync it _locales/it/ +locale dav4tbsync pl _locales/pl/ +locale dav4tbsync pt-BR _locales/pt_BR/ +locale dav4tbsync ru _locales/ru/ + diff -Nru dav4tbsync-0.15/content/dav.js dav4tbsync-1.9/content/dav.js --- dav4tbsync-0.15/content/dav.js 2019-02-26 16:26:01.000000000 +0000 +++ dav4tbsync-1.9/content/dav.js 1970-01-01 00:00:00.000000000 +0000 @@ -1,909 +0,0 @@ -/* - * This file is part of DAV-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"; - -/** - * Implements the TbSync interface for external provider extensions. - */ -var dav = { - bundle: Services.strings.createBundle("chrome://dav4tbsync/locale/dav.strings"), - prefSettings: Services.prefs.getBranch("extensions.dav4tbsync."), - listOfRealms: {}, - - ns: { - d: "DAV:", - cal: "urn:ietf:params:xml:ns:caldav" , - card: "urn:ietf:params:xml:ns:carddav" , - cs: "http://calendarserver.org/ns/", - s: "http://sabredav.org/ns", - apple: "http://apple.com/ns/ical/" - }, - - serviceproviders: { - "fruux" : {icon: "fruux", caldav: "https://dav.fruux.com", carddav: "https://dav.fruux.com"}, - "icloud" : {icon: "icloud", caldav: "https://caldav.icloud.com", carddav: "https://contacts.icloud.com"}, - "yahoo" : {icon: "yahoo", caldav: "https://caldav.calendar.yahoo.com", carddav: "https://carddav.address.yahoo.com"}, - "gmx.net" : {icon: "gmx", caldav: "https://caldav.gmx.net", carddav: "https://carddav.gmx.net/.well-known/carddav"}, - "gmx.com" : {icon: "gmx", caldav: "https://caldav.gmx.com", carddav: "https://carddav.gmx.com/.well-known/carddav"}, - }, - - //https://bugzilla.mozilla.org/show_bug.cgi?id=669675 - //non permanent cache - problematicHosts: [], - - calendarManagerObserver : { - onCalendarRegistered : function (aCalendar) { - //this observer can go stale, if something bad happens during load and the unload is never called - if (tbSync) { - //identify a calendar which has been deleted and is now being recreated by lightning (not TbSync) - which is probably due to changing the offline support option - let folders = tbSync.db.findFoldersWithSetting(["status"], ["aborted"]); //if it is pending status, we are creating it, not someone else - for (let f=0; f < folders.length; f++) { - let provider = tbSync.db.getAccountSetting(folders[f].account, "provider"); - - //only act on dav calendars which have the same uri - if (provider == "dav" && folders[f].selected == "1" && folders[f].url == aCalendar.uri.spec) { - tbSync.db.setFolderSetting(folders[f].account, folders[f].folderID, "status", "OK"); - //add target to re-take control - tbSync.db.setFolderSetting(folders[f].account, folders[f].folderID, "target", aCalendar.id); - //update settings window, if open - Services.obs.notifyObservers(null, "tbsync.updateSyncstate", folders[f].account); - } - } - } - }, - onCalendarUnregistering : function (aCalendar) {}, - onCalendarDeleting : function (aCalendar) {}, - }, - - calendarObserver : { - onStartBatch : function () {}, - onEndBatch : function () {}, - onLoad : function (aCalendar) {}, - onAddItem : function (aItem) {}, - onModifyItem : function (aNewItem, aOldItem) {}, - onDeleteItem : function (aDeletedItem) {}, - onError : function (aCalendar, aErrNo, aMessage) {}, - onPropertyDeleting : function (aCalendar, aName) {}, - - //Properties of the calendar itself (name, color etc.) - onPropertyChanged : function (aCalendar, aName, aValue, aOldValue) { - //this observer can go stale, if something bad happens during load and the unload is never called - if (tbSync) { - let folders = tbSync.db.findFoldersWithSetting(["target"], [aCalendar.id]); - if (folders.length == 1) { - switch (aName) { - case "color": - //prepare connection data - let connection = {}; - connection.account = folders[0].account; - connection.folderID = folders[0].folderID; - connection.type = "cal"; - connection.fqdn = folders[0].fqdn; - dav.tools.addAccountDataToConnectionData(connection); - - //update stored color to recover after disable - dav.tools.sendRequest(""+(aValue + "FFFFFFFF").slice(0,9)+"", folders[0].folderID, "PROPPATCH", connection); - break; - } - } - } - }, - }, - - - onSettingsGUILoad: function (window, accountID) { - let serviceprovider = tbSync.db.getAccountSetting(accountID, "serviceprovider"); - let isServiceProvider = tbSync.dav.serviceproviders.hasOwnProperty(serviceprovider); - - // special treatment for configuration label, which is a permanent setting and will not change by switching modes - let configlabel = window.document.getElementById("tbsync.accountsettings.label.config"); - if (configlabel) { - let extra = ""; - if (isServiceProvider) { - extra = " [" + tbSync.getLocalizedMessage("add.serverprofile." + serviceprovider, "dav") + "]"; - } - configlabel.setAttribute("value", tbSync.getLocalizedMessage("config.custom", "dav") + extra); - } - - //set certain elements as "alwaysDisable", if locked by service provider (alwaysDisabled is honored by main SettingsUpdate, so we do not have to do that in our own onSettingsGUIUpdate - if (isServiceProvider) { - let items = window.document.getElementsByClassName("lockIfServiceProvider"); - for (let i=0; i < items.length; i++) { - items[i].setAttribute("alwaysDisabled", "true"); - } - } - }, - - stripHost: function (document, account, field) { - let host = document.getElementById('tbsync.accountsettings.pref.' + field).value; - if (host.indexOf("https://") == 0) { - host = host.replace("https://",""); - document.getElementById('tbsync.accountsettings.pref.https').checked = true; - tbSync.db.setAccountSetting(account, "https", "1"); - } else if (host.indexOf("http://") == 0) { - host = host.replace("http://",""); - document.getElementById('tbsync.accountsettings.pref.https').checked = false; - tbSync.db.setAccountSetting(account, "https", "0"); - } - - while (host.endsWith("/")) { host = host.slice(0,-1); } - document.getElementById('tbsync.accountsettings.pref.' + field).value = host - tbSync.db.setAccountSetting(account, field, host); - }, - - - - /** API **/ - - /** - * Called during load of external provider extension to init provider. - * - * @param lightningIsAvail [in] indicate wheter lightning is installed/enabled - */ - load: Task.async (function* (lightningIsAvail) { - //load overlays or do other init stuff, use lightningIsAvail to init stuff if lightning is installed - dav.overlayManager = new OverlayManager({verbose: 0}); - yield dav.overlayManager.registerOverlay("chrome://messenger/content/addressbook/abNewCardDialog.xul", "chrome://dav4tbsync/content/overlays/abNewCardWindow.xul"); - yield dav.overlayManager.registerOverlay("chrome://messenger/content/addressbook/abNewCardDialog.xul", "chrome://dav4tbsync/content/overlays/abCardWindow.xul"); - yield dav.overlayManager.registerOverlay("chrome://messenger/content/addressbook/abEditCardDialog.xul", "chrome://dav4tbsync/content/overlays/abCardWindow.xul"); - yield dav.overlayManager.registerOverlay("chrome://messenger/content/addressbook/addressbook.xul", "chrome://dav4tbsync/content/overlays/addressbookoverlay.xul"); - yield dav.overlayManager.registerOverlay("chrome://messenger/content/addressbook/addressbook.xul", "chrome://dav4tbsync/content/overlays/addressbookdetailsoverlay.xul"); - dav.overlayManager.startObserving(); - - if (lightningIsAvail) { - cal.getCalendarManager().addObserver(tbSync.dav.calendarManagerObserver); - cal.getCalendarManager().addCalendarObserver(tbSync.dav.calendarObserver); - } - - //Migration - accounts without a serviceprovider setting only have a value in host - //is it a discovery setting (only fqdn) or a custom value? - let accounts = tbSync.db.getAccounts(); - for (let i=0; i i != ""); - let fqdn = hostparts.splice(0,1).toString(); - if (hostparts.length == 0) { - tbSync.db.setAccountSetting(accountID, "host", fqdn + "/.well-known/caldav"); - tbSync.db.setAccountSetting(accountID, "host2", fqdn + "/.well-known/carddav"); - tbSync.db.setAccountSetting(accountID, "serviceprovider", "discovery"); - } else { - tbSync.db.setAccountSetting(accountID, "host", fqdn + "/" + hostparts.join("/")); - tbSync.db.setAccountSetting(accountID, "host2", fqdn + "/" + hostparts.join("/")); - tbSync.db.setAccountSetting(accountID, "serviceprovider", "custom"); - } - } - - } - } - }), - - - - /** - * Called during unload of external provider extension to unload provider. - * - * @param lightningIsAvail [in] indicate wheter lightning is installed/enabled - */ - unload: function (lightningIsAvail) { - if (lightningIsAvail) { - cal.getCalendarManager().removeObserver(tbSync.dav.calendarManagerObserver); - cal.getCalendarManager().removeCalendarObserver(tbSync.dav.calendarObserver); - } - dav.overlayManager.stopObserving(); - }, - - - - /** - * Called to get passwords of accounts of this provider - * - * @param accountdata [in] account data structure - */ - getPassword: function (accountdata) { - let hostField = (accountdata.host !== "") ? "host" : "host2"; - let host4PasswordManager = tbSync.getHost4PasswordManager(accountdata.provider, accountdata[hostField]); - return tbSync.getLoginInfo(host4PasswordManager, "TbSync", accountdata.user); - }, - - - - /** - * Called to set passwords of accounts of this provider - * - * @param accountdata [in] account data structure - * @param newPassword [in] new password - */ - setPassword: function (accountdata, newPassword) { - let hostField = (accountdata.host !== "") ? "host" : "host2"; - let host4PasswordManager = tbSync.getHost4PasswordManager(accountdata.provider, accountdata[hostField]); - tbSync.setLoginInfo(host4PasswordManager, "TbSync", accountdata.user, newPassword); - }, - - - - /** - * Returns location of a provider icon. - * - * @param size [in] size of requested icon - * @param accountId [in] optional ID of the account related to this request - * - */ - getProviderIcon: function (size, accountId = null) { - let base = "sabredav"; - if (accountId !== null) { - let serviceprovider = tbSync.db.getAccountSetting(accountId, "serviceprovider"); - if (tbSync.dav.serviceproviders.hasOwnProperty(serviceprovider)) { - base = tbSync.dav.serviceproviders[serviceprovider].icon; - } - } - - switch (size) { - case 16: - return "chrome://dav4tbsync/skin/"+base+"16.png"; - case 32: - return "chrome://dav4tbsync/skin/"+base+"32.png"; - default : - return "chrome://dav4tbsync/skin/"+base+"48.png"; - } - }, - - - - /** - * Returns a list of sponsors, they will be sorted by the index - */ - getSponsors: function () { - return { - "Thoben, Marc" : {name: "Marc Thoben", description: "Zimbra", icon: "", link: "" }, - "Biebl, Michael" : {name: "Michael Biebl", description: "Nextcloud", icon: "", link: "" }, - "László, Kovács" : {name: "Kovács László", description : "Radicale", icon: "", link: "" }, - "Lütticke, David" : {name: "David Lütticke", description : "Posteo", icon: "", link: "" }, - }; - }, - - - - /** - * Returns the email address of the maintainer (used for bug reports). - */ - getMaintainerEmail: function () { - return "john.bieling@gmx.de"; - }, - - - - /** - * Returns XUL URL of the new account dialog. - */ - getCreateAccountXulUrl: function () { - return "chrome://dav4tbsync/content/manager/createAccount.xul"; - }, - - - - /** - * Returns overlay XUL URL of the edit account dialog (chrome://tbsync/content/manager/editAccount.xul) - */ - getEditAccountOverlayUrl: function () { - return "chrome://dav4tbsync/content/manager/editAccountOverlay.xul"; - }, - - - - /** - * Returns nice string for name of provider (is used in the add account menu). - */ - getNiceProviderName: function () { - return dav.bundle.GetStringFromName("menu.name") - }, - - - - /** - * Return object which contains all possible fields of a row in the accounts database with the default value if not yet stored in the database. - */ - getDefaultAccountEntries: function () { - let row = { - "account" : "", - "accountname": "", - "provider": "dav", - "lastsynctime" : "0", - "status" : "disabled", //global status: disabled, OK, syncing, notsyncronized, nolightning, ... - "host" : "", - "host2" : "", - "serviceprovider" : "", - "user" : "", - "https" : "1", - "autosync" : "0", - "createdWithProviderVersion" : "0", - - "syncGroups" : "0", - "useCache" : "1", - "useCardBook" : "0", - }; - return row; - }, - - - /** - * Return object which contains all possible fields of a row in the folder database with the default value if not yet stored in the database. - */ - getDefaultFolderEntries: function (account) { - let folder = { - "account" : account, - "folderID" : "", - - //different folders can be stored on different servers (yahoo, icloud, gmx, ...), - //so we need to store the fqdn information per folders - "fqdn" : "", - - "name" : "", - "type" : "", //cladav, carddav or ics - "shared": "", //identify shared resources - "acl": "", //acl send from server - "target" : "", - "targetName" : "", - "targetColor" : "", - "selected" : "", - "lastsynctime" : "", - "status" : "", - "parentID" : "", - "useChangeLog" : "1", //log changes into changelog - "ctag" : "", - "token" : "", - "downloadonly" : "0", - "createdWithProviderVersion" : "0", - }; - return folder; - }, - - - - /** - * Returns an array of folder settings, that should survive unsubscribe/subscribe and disable/re-enable (caching) - */ - getPersistentFolderSettings: function () { - return ["targetName", "targetColor","downloadonly"]; - }, - - - - /** - * Return the thunderbird type (tb-contact, tb-event, tb-todo) for a given folder type of this provider. A provider could have multiple - * type definitions for a single thunderbird type (default calendar, shared address book, etc), this maps all possible provider types to - * one of the three thunderbird types. - * - * @param type [in] provider folder type - */ - getThunderbirdFolderType: function(type) { - switch (type) { - case "carddav": - return "tb-contact"; - case "caldav": - case "ics": - return "tb-event"; - default: - return "unknown ("+type + ")"; - }; - }, - - - - /** - * Is called everytime an account of this provider is enabled in the manager UI, set/reset database fields as needed. - * - * @param account [in] account which is being enabled - */ - onEnableAccount: function (account) { - db.resetAccountSetting(account, "lastsynctime"); - }, - - - - /** - * Is called everytime an account of this provider is disabled in the manager UI, set/reset database fields as needed and - * remove/backup all sync targets of this account. - * - * @param account [in] account which is being disabled - */ - onDisableAccount: function (account) { - }, - - - - /** - * Is called everytime an new target is created, intended to set a clean sync status. - * - * @param account [in] account the new target belongs to - * @param folderID [in] folder the new target belongs to - */ - onResetTarget: function (account, folderID) { - tbSync.db.resetFolderSetting(account, folderID, "ctag"); - tbSync.db.resetFolderSetting(account, folderID, "token"); - tbSync.db.setFolderSetting(account, folderID, "createdWithProviderVersion", tbSync.loadedProviders.dav.version); - }, - - - - /** - * Is called if TbSync needs to create a new thunderbird address book associated with an account of this provider. - * - * @param newname [in] name of the new address book - * @param account [in] id of the account this address book belongs to - * @param folderID [in] id of the folder this address book belongs to (sync target) - * - * return the id of the newAddressBook - */ - createAddressBook: function (newname, account, folderID) { - //This example implementation is using the standard address book, but you may use another one - let abManager = Components.classes["@mozilla.org/abmanager;1"].getService(Components.interfaces.nsIAbManager); - - let dirPrefId = abManager.newAddressBook(newname, "", 2); - let data = abManager.getDirectoryFromId(dirPrefId); - if (data instanceof Components.interfaces.nsIAbDirectory && data.dirPrefId == dirPrefId) { - let serviceprovider = tbSync.db.getAccountSetting(account, "serviceprovider"); - let icon = "custom"; - if (dav.serviceproviders.hasOwnProperty(serviceprovider)) { - icon = dav.serviceproviders[serviceprovider].icon; - } - data.setStringValue("tbSyncIcon", "dav" + icon); - } - return dirPrefId; //change this to data on next big change - }, - - - - /** - * Is called if TbSync needs to create a new UID for an address book card - * - * @param aItem [in] card that needs new ID - * - * returns the new id - */ - getNewCardID: function (aItem, folder) { - //actually use the full href of this vcard as id - the actual UID is not used by TbSync (only for mailinglist members) - return folder.folderID + dav.tools.generateUUID() + ".vcf"; - }, - - - - /** - * Is called if TbSync needs to create a new lightning calendar associated with an account of this provider. - * - * @param newname [in] name of the new calendar - * @param account [in] id of the account this calendar belongs to - * @param folderID [in] id of the folder this calendar belongs to (sync target) - */ - createCalendar: function(newname, account, folderID) { - let calManager = cal.getCalendarManager(); - let accountdata = tbSync.db.getAccount(account); - let password = tbSync.dav.getPassword(accountdata); - let user = accountdata.user; - let caltype = tbSync.db.getFolderSetting(account, folderID, "type"); - let downloadonly = (tbSync.db.getFolderSetting(account, folderID, "downloadonly") == "1"); - - let baseUrl = ""; - if (caltype != "ics") { - baseUrl = "http" + (accountdata.https == "1" ? "s" : "") + "://" + (tbSync.dav.prefSettings.getBoolPref("addCredentialsToUrl") ? encodeURIComponent(user) + ":" + encodeURIComponent(password) + "@" : "") + tbSync.db.getFolderSetting(account, folderID, "fqdn"); - } - - let url = dav.tools.parseUri(baseUrl + folderID); - tbSync.db.setFolderSetting(account, folderID, "url", url.spec); - - let newCalendar = calManager.createCalendar(caltype, url); //caldav or ics - newCalendar.id = cal.getUUID(); - newCalendar.name = newname; - - newCalendar.setProperty("color", tbSync.db.getFolderSetting(account, folderID, "targetColor")); - newCalendar.setProperty("calendar-main-in-composite", true); - newCalendar.setProperty("cache.enabled", (tbSync.db.getAccountSetting(account, "useCache") == "1")); - if (downloadonly) newCalendar.setProperty("readOnly", true); - - //only add credentials to password manager if they are not added to the URL directly - only for caldav calendars, not for plain ics files - if (!tbSync.dav.prefSettings.getBoolPref("addCredentialsToUrl") && caltype != "ics") { - tbSync.dump("Searching CalDAV authRealm for", url.host); - let realm = (dav.listOfRealms.hasOwnProperty(url.host)) ? dav.listOfRealms[url.host] : ""; - if (realm !== "") { - tbSync.dump("Found CalDAV authRealm", realm); - tbSync.setLoginInfo(url.prePath, realm, user, password); - } - } - - //do not monitor CalDAV calendars (managed by lightning) - tbSync.db.setFolderSetting(account, folderID, "useChangeLog", "0"); - - calManager.registerCalendar(newCalendar); - return newCalendar; - }, - - - - /** - * Is called if TbSync needs to find contacts in the global address list (GAL / directory) of an account associated with this provider. - * It is used for autocompletion while typing something into the address field of the message composer and for the address book search, - * if something is typed into the search field of the Thunderbird address book. - * - * DO NOT IMPLEMENT AT ALL, IF NOT SUPPORTED - * - * TbSync will execute this only for queries longer than 3 chars. - * - * @param account [in] id of the account which should be searched - * @param currentQuery [in] search query - * @param caller [in] "autocomplete" or "search" - - */ - //abServerSearch: Task.async (function* (account, currentQuery, caller) { - // return null; - //}), - - - - /** - * Is called if TbSync needs to synchronize an account. - * - * @param syncdata [in] object that contains the account and maybe the folder which needs to worked on - * you are free to add more fields to this object which you need (persistent) during sync - * @param job [in] identifier about what is to be done, the standard job is "sync", you are free to add - * custom jobs like "deletefolder" via your own accountSettings.xul - */ - start: Task.async (function* (syncdata, job) { - try { - switch (job) { - case "sync": - //update folders avail on server and handle added, removed, renamed folders - yield dav.sync.folderList(syncdata); - - //set all selected folders to "pending", so they are marked for syncing - //this also removes all leftover cached folders and sets all other folders to a well defined cached = "0" - //which will set this account as connected (if at least one folder with cached == "0" is present) - tbSync.prepareFoldersForSync(syncdata.account); - - //check if any folder was found - if (!tbSync.isConnected(syncdata.account)) { - throw dav.sync.failed("no-folders-found-on-server"); - } - - //update folder list in GUI - Services.obs.notifyObservers(null, "tbsync.updateFolderList", syncdata.account); - - //process all pending folders - yield dav.sync.allPendingFolders(syncdata); - break; - - default: - throw dav.sync.failed("unknown::"+job); - break; - } - } catch (e) { - if (e.type == "dav4tbsync") { - tbSync.finishAccountSync(syncdata, e); - } else { - //some other error - e.type = "JavaScriptError"; - tbSync.finishAccountSync(syncdata, e); - Components.utils.reportError(e); - } - } - }), - - - - /** - * Functions used by the folderlist in the main account settings tab - */ - folderList: { - - /** - * Is called before the context menu of the folderlist is shown, allows to - * show/hide custom menu options based on selected folder - * - * @param document [in] document object of the account settings window - * @param folder [in] folder databasse object of the selected folder - */ - onContextMenuShowing: function (document, folder) { - }, - - - - /** - * Returns an array of folderRowData objects, containing all information needed - * to fill the folderlist. The content of the folderRowData object is free to choose, - * it will be passed back to getRow() and updateRow() - * - * @param account [in] account id for which the folder data should be returned - */ - getSortedData: function (account) { - let folders = tbSync.db.getFolders(account); - let folderIDs = Object.keys(folders); - - //we can only sort arrays, so we need to create an array of objects and those objects - //must contain the sort key and the associated folderId - let toBeSorted = []; - for (let i=0; i < folderIDs.length; i++) { - let t = 100; - switch (folders[folderIDs[i]].type) { - case "carddav": - t+=0; - break; - case "caldav": - t+=1; - break; - case "ics": - t+=2; - break; - default: - t+=9; - break; - } - - if (folders[folderIDs[i]].shared == "1") { - t+=100; - } - - toBeSorted.push({"key": t.toString() + folders[folderIDs[i]].name, "id": folderIDs[i]}); - } - - //sort - toBeSorted.sort(function(a,b) { - return a.key > b.key; - }); - - let folderData = []; - for (let sorted of toBeSorted) { - folderData.push(tbSync.dav.folderList.getRowData(folders[sorted.id])); - } - return folderData; - }, - - - - /** - * Returns a folderRowData object, containing all information needed to fill one row - * in the folderlist. The content of the folderRowData object is free to choose, it - * will be passed back to getRow() and updateRow() - * - * Use tbSync.getSyncStatusMsg(folder, syncdata, provider) to get a nice looking - * status message, including sync progress (if folder is synced) - * - * @param folder [in] folder databasse object of requested folder - * @param syncdata [in] optional syncdata obj send by updateRow(), - * needed to check if the folder is currently synced - */ - getRowData: function (folder, syncdata = null) { - let rowData = {}; - rowData.account = folder.account; - rowData.folderID = folder.folderID; - rowData.selected = (folder.selected == "1"); - rowData.type = folder.type; - rowData.shared = folder.shared; - rowData.downloadonly = folder.downloadonly; - rowData.acl = folder.acl; - rowData.name = folder.name; - rowData.statusCode = folder.status; - rowData.statusMsg = tbSync.getSyncStatusMsg(folder, syncdata, "dav"); - - return rowData; - }, - - - - /** - * Returns an array of attribute objects, which define the number of columns - * and the look of the header - */ - getHeader: function () { - return [ - {style: "font-weight:bold;", label: "", width: "93"}, - {style: "font-weight:bold;", label: tbSync.getLocalizedMessage("manager.resource"), width:"150"}, - {style: "font-weight:bold;", label: tbSync.getLocalizedMessage("manager.status"), flex :"1"}, - ] - }, - - - //not part of API - updateReadOnly: function (event) { - let p = event.target.parentNode.parentNode; - let account = p.getAttribute('account'); - let folderID = p.getAttribute('folderID'); - let value = event.target.value; - let type = tbSync.db.getFolderSetting(account, folderID, "type"); - - //update value - tbSync.db.setFolderSetting(account, folderID, "downloadonly", value); - - //update icon - if (value == "0") { - p.setAttribute('image','chrome://tbsync/skin/acl_rw.png'); - } else { - p.setAttribute('image','chrome://tbsync/skin/acl_ro.png'); - } - - //update ro flag if calendar - switch (type) { - case "carddav": - break; - case "caldav": - case "ics": - { - let target = tbSync.db.getFolderSetting(account, folderID, "target"); - if (target != "") { - let calManager = cal.getCalendarManager(); - let targetCal = calManager.getCalendarById(target); - targetCal.setProperty("readOnly", value == '1'); - } - } - break; - } - }, - - /** - * Is called to add a row to the folderlist. After this call, updateRow is called as well. - * - * @param document [in] document object of the account settings window - * @param newListItem [in] the listitem of the row, where row items should be added to - * @param rowData [in] rowData object with all information needed to add the row - * @param itemSelCheckbox [in] a checkbox object which can be used to allow the user to select/deselect this resource - */ - getRow: function (document, rowData, itemSelCheckbox) { - //checkbox - itemSelCheckbox.setAttribute("style", "margin: 0px 0px 0px 3px;"); - - //icon - let itemType = document.createElement("image"); - itemType.setAttribute("src", tbSync.dav.folderList.getTypeImage(rowData)); - itemType.setAttribute("style", "margin: 0px 9px 0px 3px;"); - - //ACL - let itemACL = document.createElement("button"); - itemACL.setAttribute("image", "chrome://tbsync/skin/acl_" + (rowData.downloadonly == "1" ? "ro" : "rw") + ".png"); - itemACL.setAttribute("class", "plain"); - itemACL.setAttribute("style", "width: 35px; min-width: 35px; margin: 0; height:26px"); - itemACL.setAttribute("account", rowData.account); - itemACL.setAttribute("folderID", rowData.folderID); - itemACL.setAttribute("type", "menu"); - let menupopup = document.createElement("menupopup"); - let menuitem1 = document.createElement("menuitem"); - menuitem1.setAttribute("value", "1"); - menuitem1.setAttribute("class", "menuitem-iconic"); - menuitem1.setAttribute("label", tbSync.getLocalizedMessage("acl.readonly", "dav")); - menuitem1.setAttribute("image", "chrome://tbsync/skin/acl_ro2.png"); - menuitem1.addEventListener("command", tbSync.dav.folderList.updateReadOnly); - - let acl = parseInt(rowData.acl); - let acls = []; - if (acl & 0x2) acls.push(tbSync.getLocalizedMessage("acl.modify", "dav")); - if (acl & 0x4) acls.push(tbSync.getLocalizedMessage("acl.add", "dav")); - if (acl & 0x8) acls.push(tbSync.getLocalizedMessage("acl.delete", "dav")); - if (acls.length == 0) acls.push(tbSync.getLocalizedMessage("acl.none", "dav")); - - let menuitem2 = document.createElement("menuitem"); - menuitem2.setAttribute("value", "0"); - menuitem2.setAttribute("class", "menuitem-iconic"); - menuitem2.setAttribute("label", tbSync.getLocalizedMessage("acl.readwrite::"+acls.join(", "), "dav")); - menuitem2.setAttribute("image", "chrome://tbsync/skin/acl_rw2.png"); - menuitem2.setAttribute("disabled", (acl & 0x7) != 0x7); - menuitem2.addEventListener("command", tbSync.dav.folderList.updateReadOnly); - - menupopup.appendChild(menuitem2); - menupopup.appendChild(menuitem1); - itemACL.appendChild(menupopup); - - //folder name - let itemLabel = document.createElement("description"); - itemLabel.setAttribute("disabled", !rowData.selected); - - //status - let itemStatus = document.createElement("description"); - itemStatus.setAttribute("disabled", !rowData.selected); - - //group1 - let itemHGroup1 = document.createElement("hbox"); - itemHGroup1.setAttribute("align", "center"); - itemHGroup1.appendChild(itemSelCheckbox); - itemHGroup1.appendChild(itemType); - itemHGroup1.appendChild(itemACL); - - let itemVGroup1 = document.createElement("vbox"); - itemVGroup1.setAttribute("width", "93"); - itemVGroup1.appendChild(itemHGroup1); - - //group2 - let itemHGroup2 = document.createElement("hbox"); - itemHGroup2.setAttribute("align", "center"); - itemHGroup2.setAttribute("width", "146"); - itemHGroup2.appendChild(itemLabel); - - let itemVGroup2 = document.createElement("vbox"); - itemVGroup2.setAttribute("style", "padding: 3px"); - itemVGroup2.appendChild(itemHGroup2); - - //group3 - let itemHGroup3 = document.createElement("hbox"); - itemHGroup3.setAttribute("align", "center"); - itemHGroup3.setAttribute("width", "200"); - itemHGroup3.appendChild(itemStatus); - - let itemVGroup3 = document.createElement("vbox"); - itemVGroup3.setAttribute("style", "padding: 3px"); - itemVGroup3.appendChild(itemHGroup3); - - //final row - let row = document.createElement("hbox"); - row.setAttribute("style", "min-height: 24px;"); - row.appendChild(itemVGroup1); - row.appendChild(itemVGroup2); - row.appendChild(itemVGroup3); - return row; - }, - - - - /** - * Is called to update a row of the folderlist (the first cell is a select checkbox inserted by TbSync) - * - * @param document [in] document object of the account settings window - * @param listItem [in] the listitem of the row, which needs to be updated - * @param rowData [in] rowData object with all information needed to add the row - */ - updateRow: function (document, item, rowData) { - //acl image - item.childNodes[0].childNodes[0].childNodes[0].childNodes[2].setAttribute("image", "chrome://tbsync/skin/acl_" + (rowData.downloadonly == "1" ? "ro" : "rw") + ".png"); - - //select checkbox - if (rowData.selected) { - item.childNodes[0].childNodes[0].childNodes[0].childNodes[0].setAttribute("checked", true); - } else { - item.childNodes[0].childNodes[0].childNodes[0].childNodes[0].removeAttribute("checked"); - } - - if (item.childNodes[0].childNodes[1].childNodes[0].textContent != rowData.name) item.childNodes[0].childNodes[1].childNodes[0].textContent = rowData.name; - if (item.childNodes[0].childNodes[2].childNodes[0].textContent != rowData.statusMsg) item.childNodes[0].childNodes[2].childNodes[0].textContent = rowData.statusMsg; - item.childNodes[0].childNodes[1].childNodes[0].setAttribute("disabled", !rowData.selected); - item.childNodes[0].childNodes[1].childNodes[0].setAttribute("style", rowData.selected ? "" : "font-style:italic"); - item.childNodes[0].childNodes[2].childNodes[0].setAttribute("style", rowData.selected ? "" : "font-style:italic"); - }, - - - - /** - * Return the icon used in the folderlist to represent the different folder types - * Not part of API, only called by getRow - * - * @param rowData [in] rowData object - */ - getTypeImage: function (rowData) { - let src = ""; - switch (rowData.type) { - case "carddav": - if (rowData.shared == "1") { - return "chrome://tbsync/skin/contacts16_shared.png"; - } else { - return "chrome://tbsync/skin/contacts16.png"; - } - case "caldav": - if (rowData.shared == "1") { - return "chrome://tbsync/skin/calendar16_shared.png"; - } else { - return "chrome://tbsync/skin/calendar16.png"; - } - case "ics": - return "chrome://dav4tbsync/skin/ics16.png"; - } - } - } -}; - -tbSync.includeJS("chrome://dav4tbsync/content/sync.js"); -tbSync.includeJS("chrome://dav4tbsync/content/tools.js"); -tbSync.includeJS("chrome://dav4tbsync/content/vcard/vcard.js"); diff -Nru dav4tbsync-0.15/content/includes/abUI.js dav4tbsync-1.9/content/includes/abUI.js --- dav4tbsync-0.15/content/includes/abUI.js 1970-01-01 00:00:00.000000000 +0000 +++ dav4tbsync-1.9/content/includes/abUI.js 2020-02-20 12:39:38.000000000 +0000 @@ -0,0 +1,478 @@ +/* + * This file is part of DAV-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 ui = { + + 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 + let ownerId = aCard.directoryId; + return dav.ui.getUriFromDirectoryId(ownerId); + } 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; + } + }, + + + + //* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + //* Functions to handle advanced UI elements of AB + //* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + updatePref: function(aDocument, icon, toggle = false) { + if (toggle) { + if (icon.parentNode.meta.includes("PREF")) icon.parentNode.meta = icon.parentNode.meta.filter(e => e != "PREF"); + else icon.parentNode.meta.push("PREF"); + + icon.parentNode.updateFunction (aDocument); + } + + if (icon.parentNode.meta.includes("PREF")) { + icon.setAttribute("src", "chrome://dav4tbsync/skin/type.pref.png"); + } else { + icon.setAttribute("src", "chrome://dav4tbsync/skin/type.nopref.png"); + } + }, + + updateType: function(aDocument, button, newvalue = null) { + if (newvalue) { + //we declare allowedValues to be non-overlapping -> remove all allowed values and just add the newvalue + button.parentNode.meta = button.parentNode.meta.filter(value => -1 == button.allowedValues.indexOf(value)); + if (button.allowedValues.includes(newvalue)) { + //hardcoded sort order: HOME/WORK always before other types + if (["HOME","WORK"].includes(newvalue)) button.parentNode.meta.unshift(newvalue); + else button.parentNode.meta.push(newvalue); + } + + button.parentNode.updateFunction (aDocument); + } + + let intersection = button.parentNode.meta.filter(value => -1 !== button.allowedValues.indexOf(value)); + let buttonType = (intersection.length > 0) ? intersection[0].toLowerCase() : button.otherIcon; + button.setAttribute("image","chrome://dav4tbsync/skin/type."+buttonType+"10.png"); + }, + + dragdrop: { + handleEvent(event) { + //only allow to drag the elements which are valid drag targets + if (event.target.getAttribute("dragtarget") != "true") { + event.stopPropagation(); + return; + } + + let outerbox = event.currentTarget; + let richlistitem = outerbox.parentNode; + + switch (event.type) { + case "dragenter": + case "dragover": + let dropIndex = richlistitem.parentNode.getIndexOfItem(richlistitem); + let dragIndex = richlistitem.parentNode.getIndexOfItem(richlistitem.ownerDocument.getElementById(event.dataTransfer.getData("id"))); + + let centerY = event.currentTarget.clientHeight / 2; + let insertBefore = (event.offsetY < centerY); + let moveNeeded = !(dropIndex == dragIndex || (dropIndex+1 == dragIndex && !insertBefore) || (dropIndex-1 == dragIndex && insertBefore)); + + if (moveNeeded) { + if (insertBefore) { + richlistitem.parentNode.insertBefore(richlistitem.parentNode.getItemAtIndex(dragIndex), richlistitem); + } else { + richlistitem.parentNode.insertBefore(richlistitem.parentNode.getItemAtIndex(dragIndex), richlistitem.nextSibling); + } + } + + event.preventDefault(); + break; + + case "drop": + event.preventDefault(); + case "dragleave": + break; + + case "dragstart": + event.currentTarget.style["background-color"] = "#eeeeee"; + event.dataTransfer.setData("id", richlistitem.id); + break; + + case "dragend": + event.currentTarget.style["background-color"] = "transparent"; + outerbox.updateFunction(outerbox.ownerDocument); + break; + + default: + return undefined; + } + }, + }, + + //* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + //* Functions to handle multiple email addresses in AB (UI) + //* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + getNewEmailDetailsRow: function (aWindow, aItemData) { + let emailType = "other"; + if (aItemData.meta.includes("HOME")) emailType = "home"; + else if (aItemData.meta.includes("WORK")) emailType = "work"; + + //first column + let vbox = aWindow.document.createXULElement("vbox"); + vbox.setAttribute("class","CardViewText"); + vbox.setAttribute("style","margin-right:1ex; margin-bottom:2px;"); + let image = aWindow.document.createXULElement("image"); + image.setAttribute("width","10"); + image.setAttribute("height","10"); + image.setAttribute("src", "chrome://dav4tbsync/skin/type."+emailType+"10.png"); + vbox.appendChild(image); + + //second column + let description = aWindow.document.createXULElement("description"); + description.setAttribute("class","plain"); + let namespace = aWindow.document.lookupNamespaceURI("html"); + let a = aWindow.document.createElementNS(namespace, "a"); + a.setAttribute("href", "mailto:" + aItemData.value); + a.textContent = aItemData.value; + description.appendChild(a); + + if (aItemData.meta.includes("PREF")) { + let pref = aWindow.document.createXULElement("image"); + pref.setAttribute("style", "margin-left:1ex;"); + pref.setAttribute("width", "11"); + pref.setAttribute("height", "10"); + pref.setAttribute("src", "chrome://dav4tbsync/skin/type.nopref.png"); + description.appendChild(pref); + } + + //row + let row = aWindow.document.createXULElement("row"); + row.setAttribute("align","end"); + row.appendChild(vbox); + row.appendChild(description); + return row; + }, + + getNewEmailListItem: function (aDocument, aItemData) { + //hbox + let outerhbox = aDocument.createXULElement("hbox"); + outerhbox.setAttribute("dragtarget", "true"); + outerhbox.setAttribute("flex", "1"); + outerhbox.setAttribute("align", "center"); + outerhbox.updateFunction = dav.ui.updateEmails; + outerhbox.meta = aItemData.meta; + + outerhbox.addEventListener("dragenter", dav.ui.dragdrop); + outerhbox.addEventListener("dragover", dav.ui.dragdrop); + outerhbox.addEventListener("dragleave", dav.ui.dragdrop); + outerhbox.addEventListener("dragstart", dav.ui.dragdrop); + outerhbox.addEventListener("dragend", dav.ui.dragdrop); + outerhbox.addEventListener("drop", dav.ui.dragdrop); + + outerhbox.style["background-image"] = "url('chrome://dav4tbsync/skin/dragdrop.png')"; + outerhbox.style["background-position"] = "right"; + outerhbox.style["background-repeat"] = "no-repeat"; + + //button + let button = aDocument.createXULElement("button"); + button.allowedValues = ["HOME", "WORK"]; + button.otherIcon = "other"; + button.setAttribute("type", "menu"); + button.setAttribute("class", "plain"); + button.setAttribute("style", "width: 35px; min-width: 35px; margin: 0;"); + button.appendChild(aDocument.getElementById("DavEmailSpacer").children[0].cloneNode(true)); + outerhbox.appendChild(button); + + //email box + let emailbox = aDocument.createXULElement("hbox"); + emailbox.setAttribute("flex", "1"); + emailbox.setAttribute("style", "padding-bottom:1px"); + let email = aDocument.createXULElement("textbox"); + email.setAttribute("flex", "1"); + email.setAttribute("class", "plain"); + email.setAttribute("value", aItemData.value); + email.addEventListener("change", function(e) {dav.ui.updateEmails(aDocument)}); + email.addEventListener("keydown", function(e) {if (e.key == "Enter") {e.stopPropagation(); e.preventDefault(); if (e.target.value != "") { dav.ui.addEmailEntry(e.target.ownerDocument); }}}); + emailbox.appendChild(email); + outerhbox.appendChild(emailbox); + + //image + let image = aDocument.createXULElement("image"); + image.setAttribute("width", "11"); + image.setAttribute("height", "10"); + image.setAttribute("style", "margin:2px 20px 2px 1ex"); + image.addEventListener("click", function(e) { dav.ui.updatePref(aDocument, e.target, true); }); + outerhbox.appendChild(image); + + //richlistitem + let richlistitem = aDocument.createXULElement("richlistitem"); + richlistitem.setAttribute("id", "entry_" + TbSync.generateUUID()); + richlistitem.appendChild(outerhbox); + + return richlistitem; + }, + + getEmailListItemElement: function(item, element) { + switch (element) { + case "dataContainer": + return item.children[0]; + case "button": + return item.children[0].children[0]; + case "email": + return item.children[0].children[1].children[0]; + case "pref": + return item.children[0].children[2]; + default: + return null; + } + }, + + addEmailEntry: function(aDocument) { + let list = aDocument.getElementById("X-DAV-EmailAddressList"); + let data = {value: "", meta: ["HOME"]}; + let item = list.appendChild(dav.ui.getNewEmailListItem(aDocument, data)); + list.ensureElementIsVisible(item); + + dav.ui.updateType(aDocument, dav.ui.getEmailListItemElement(item, "button")); + dav.ui.updatePref(aDocument, dav.ui.getEmailListItemElement(item, "pref")); + + dav.ui.getEmailListItemElement(item, "email").focus(); + }, + + + //if any setting changed, we need to update Primary and Secondary Email Fields + updateEmails: function(aDocument) { + let list = aDocument.getElementById("X-DAV-EmailAddressList"); + + let emails = []; + for (let i=0; i < list.children.length; i++) { + let item = list.children[i]; + let email = dav.ui.getEmailListItemElement(item, "email").value.trim(); + if (email != "") { + let json = {}; + json.meta = dav.ui.getEmailListItemElement(item, "dataContainer").meta; + json.value = email; + emails.push(json); + } + } + aDocument.getElementById("X-DAV-JSON-Emails").value = JSON.stringify(emails); + + //now update all other TB email fields based on the new JSON data + let emailData = dav.tools.getEmailsFromJSON(aDocument.getElementById("X-DAV-JSON-Emails").value); + for (let field in emailData) { + if (emailData.hasOwnProperty(field)) { + aDocument.getElementById(field).value = emailData[field].join(", "); + } + } + }, + + + + + //* * * * * * * * * * * * * * * * * * * * * * * * * * * * * + //* Functions to handle multiple phone numbers in AB (UI) + //* * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + getNewPhoneDetailsRow: function (aWindow, aItemData) { + let phoneType1 = ""; + if (aItemData.meta.includes("HOME")) phoneType1 = "home"; + else if (aItemData.meta.includes("WORK")) phoneType1 = "work"; + + let phoneType2 = ""; + if (aItemData.meta.includes("CELL")) phoneType2 = "cell"; + else if (aItemData.meta.includes("FAX")) phoneType2 = "fax"; + else if (aItemData.meta.includes("PAGER")) phoneType2 = "pager"; + else if (aItemData.meta.includes("CAR")) phoneType2 = "car"; + else if (aItemData.meta.includes("VIDEO")) phoneType2 = "video"; + else if (aItemData.meta.includes("VOICE")) phoneType2 = "voice"; + + //first column + let vbox = aWindow.document.createXULElement("hbox"); + vbox.setAttribute("pack","end"); + vbox.setAttribute("class","CardViewText"); + vbox.setAttribute("style","margin-bottom:3px;"); + if (phoneType1) { + let image = aWindow.document.createXULElement("image"); + image.setAttribute("style","margin-right:1ex;"); + image.setAttribute("width","10"); + image.setAttribute("height","10"); + image.setAttribute("src", "chrome://dav4tbsync/skin/type."+phoneType1+"10.png"); + vbox.appendChild(image); + } + if (phoneType2) { + let image = aWindow.document.createXULElement("image"); + image.setAttribute("style","margin-right:1ex;"); + image.setAttribute("width","10"); + image.setAttribute("height","10"); + image.setAttribute("src", "chrome://dav4tbsync/skin/type."+phoneType2+"10.png"); + vbox.appendChild(image); + } + + //second column + let description = aWindow.document.createXULElement("description"); + description.setAttribute("class","plain"); + description.setAttribute("style","-moz-user-select: text;"); + description.textContent = aItemData.value; + + if (aItemData.meta.includes("PREF")) { + let pref = aWindow.document.createXULElement("image"); + pref.setAttribute("style", "margin-left:1ex;"); + pref.setAttribute("width", "11"); + pref.setAttribute("height", "10"); + pref.setAttribute("src", "chrome://dav4tbsync/skin/type.nopref.png"); + description.appendChild(pref); + } + + //row + let row = aWindow.document.createXULElement("row"); + row.setAttribute("align","end"); + row.appendChild(vbox); + row.appendChild(description); + return row; + }, + + getNewPhoneListItem: function (aDocument, aItemData) { + //hbox + let outerhbox = aDocument.createXULElement("hbox"); + outerhbox.setAttribute("dragtarget", "true"); + outerhbox.setAttribute("flex", "1"); + outerhbox.setAttribute("align", "center"); + outerhbox.updateFunction = dav.ui.updatePhoneNumbers; + outerhbox.meta = aItemData.meta; + + outerhbox.addEventListener("dragenter", dav.ui.dragdrop); + outerhbox.addEventListener("dragover", dav.ui.dragdrop); + outerhbox.addEventListener("dragleave", dav.ui.dragdrop); + outerhbox.addEventListener("dragstart", dav.ui.dragdrop); + outerhbox.addEventListener("dragend", dav.ui.dragdrop); + outerhbox.addEventListener("drop", dav.ui.dragdrop); + + outerhbox.style["background-image"] = "url('chrome://dav4tbsync/skin/dragdrop.png')"; + outerhbox.style["background-position"] = "right"; + outerhbox.style["background-repeat"] = "no-repeat"; + + //button1 + let button1 = aDocument.createXULElement("button"); + button1.allowedValues = ["HOME", "WORK"]; + button1.otherIcon = "none"; + button1.setAttribute("type", "menu"); + button1.setAttribute("class", "plain"); + button1.setAttribute("style", "width: 35px; min-width: 35px; margin: 0;"); + button1.appendChild(aDocument.getElementById("DavEmailSpacer").children[1].cloneNode(true)); + outerhbox.appendChild(button1); + + //button2 + let button2 = aDocument.createXULElement("button"); + button2.allowedValues = ["CELL", "FAX", "PAGER", "CAR", "VIDEO", "VOICE"] ; //same order as in getNewPhoneDetailsRow + button2.otherIcon = "none"; + button2.setAttribute("type", "menu"); + button2.setAttribute("class", "plain"); + button2.setAttribute("style", "width: 35px; min-width: 35px; margin: 0;"); + button2.appendChild(aDocument.getElementById("DavEmailSpacer").children[2].cloneNode(true)); + outerhbox.appendChild(button2); + + //phone box + let phonebox = aDocument.createXULElement("hbox"); + phonebox.setAttribute("flex", "1"); + phonebox.setAttribute("style", "padding-bottom:1px"); + let phone = aDocument.createXULElement("textbox"); + phone.setAttribute("flex", "1"); + phone.setAttribute("class", "plain"); + phone.setAttribute("value", aItemData.value); + phone.addEventListener("change", function(e) {dav.ui.updatePhoneNumbers(aDocument)}); + phone.addEventListener("keydown", function(e) {if (e.key == "Enter") {e.stopPropagation(); e.preventDefault(); if (e.target.value != "") { dav.ui.addPhoneEntry(e.target.ownerDocument); }}}); + phonebox.appendChild(phone); + outerhbox.appendChild(phonebox); + + //image + let image = aDocument.createXULElement("image"); + image.setAttribute("width", "11"); + image.setAttribute("height", "10"); + image.setAttribute("style", "margin:2px 20px 2px 1ex"); + image.addEventListener("click", function(e) { dav.ui.updatePref(aDocument, e.target, true); }); + outerhbox.appendChild(image); + + //richlistitem + let richlistitem = aDocument.createXULElement("richlistitem"); + richlistitem.setAttribute("id", "entry_" + TbSync.generateUUID()); + richlistitem.appendChild(outerhbox); + + return richlistitem; + }, + + updatePhoneNumbers: function(aDocument) { + let list = aDocument.getElementById("X-DAV-PhoneNumberList"); + + let phones = []; + for (let i=0; i < list.children.length; i++) { + let item = list.children[i]; + let phone = dav.ui.getPhoneListItemElement(item, "phone").value.trim(); + if (phone != "") { + let json = {}; + json.meta = dav.ui.getPhoneListItemElement(item, "dataContainer").meta; + json.value = phone; + phones.push(json); + } + } + aDocument.getElementById("X-DAV-JSON-Phones").value = JSON.stringify(phones); + + //now update all other TB number fields based on the new JSON data + let phoneData = dav.tools.getPhoneNumbersFromJSON(aDocument.getElementById("X-DAV-JSON-Phones").value); + for (let field in phoneData) { + if (phoneData.hasOwnProperty(field)) { + aDocument.getElementById(field).value = phoneData[field].join(", "); + } + } + }, + + addPhoneEntry: function(aDocument) { + let list = aDocument.getElementById("X-DAV-PhoneNumberList"); + let data = {value: "", meta: ["VOICE"]}; + let item = list.appendChild(dav.ui.getNewPhoneListItem(aDocument, data)); + list.ensureElementIsVisible(item); + + dav.ui.updateType(aDocument, dav.ui.getPhoneListItemElement(item, "button1")); + dav.ui.updateType(aDocument, dav.ui.getPhoneListItemElement(item, "button2")); + dav.ui.updatePref(aDocument, dav.ui.getPhoneListItemElement(item, "pref")); + + dav.ui.getPhoneListItemElement(item, "phone").focus(); + }, + + getPhoneListItemElement: function(item, element) { + switch (element) { + case "dataContainer": + return item.children[0]; + case "button1": + return item.children[0].children[0]; + case "button2": + return item.children[0].children[1]; + case "phone": + return item.children[0].children[2].children[0]; + case "pref": + return item.children[0].children[3]; + default: + return null; + } + }, + +} diff -Nru dav4tbsync-0.15/content/includes/network.js dav4tbsync-1.9/content/includes/network.js --- dav4tbsync-0.15/content/includes/network.js 1970-01-01 00:00:00.000000000 +0000 +++ dav4tbsync-1.9/content/includes/network.js 2020-02-20 12:39:38.000000000 +0000 @@ -0,0 +1,649 @@ +/* + * This file is part of DAV-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 { HttpRequest } = ChromeUtils.import("chrome://tbsync/content/HttpRequest.jsm"); +var { OAuth2 } = ChromeUtils.import("resource:///modules/OAuth2.jsm"); +const { DNS } = ChromeUtils.import("resource:///modules/DNS.jsm"); + +var network = { + + getAuthData: function(accountData) { + let connection = { + get host() { + return "TbSync#" + accountData.accountID; + }, + + get username() { + return accountData.getAccountProperty("user"); + }, + + get password() { + // try new host first + let pw = TbSync.passwordManager.getLoginInfo(this.host, "TbSync/DAV", this.username); + if (pw) { + return pw; + } + + // try old host as fallback + let oldHost = accountData.getAccountProperty("calDavHost") ? accountData.getAccountProperty("calDavHost") : accountData.getAccountProperty("cardDavHost"); + if (oldHost.startsWith("http://")) oldHost = oldHost.substr(7); + if (oldHost.startsWith("https://")) oldHost = oldHost.substr(8); + pw = TbSync.passwordManager.getLoginInfo(oldHost, "TbSync/DAV", this.username); + if (pw) { + //migrate + this.updateLoginData(this.username, pw); + } + return pw; + }, + + updateLoginData: function(newUsername, newPassword) { + let oldUsername = this.username; + TbSync.passwordManager.updateLoginInfo(this.host, "TbSync/DAV", oldUsername, newUsername, newPassword); + // Also update the username of this account. + accountData.setAccountProperty("user", newUsername); + }, + + removeLoginData: function() { + TbSync.passwordManager.removeLoginInfos(this.host, "TbSync/DAV"); + } + }; + return connection; + }, + + // prepare and patch OAuth2 object + getOAuthObj: function(_uri, configObject = null) { + let uri = _uri; + + // if _uri input is not yet an uri, try to get one + try { + if (!_uri.spec) + uri = Services.io.newURI(_uri); + } catch (e) { + Components.utils.reportError(e); + return null; + } + + let config = {}; + switch (uri.host) { + case "apidata.googleusercontent.com": + case "www.googleapis.com": + config = { + base_uri : "https://accounts.google.com/o/", + //redirect_uri : "urn:ietf:wg:oauth:2.0:oob:auto", + scope : "https://www.googleapis.com/auth/carddav https://www.googleapis.com/auth/calendar", + client_id : "689460414096-e4nddn8tss5c59glidp4bc0qpeu3oper.apps.googleusercontent.com", + client_secret : "LeTdF3UEpCvP1V3EBygjP-kl", + } + break; + + default: + return null; + } + + let oauth = new OAuth2(config.base_uri, config.scope, config.client_id, config.client_secret); + oauth.requestWindowFeatures = "chrome,private,centerscreen,width=500,height=750"; + + //the v2 endpoints are different and would need manual override + //oauth.authURI = + //oauth.tokenURI = + oauth.extraAuthParams = [ + ["access_type", "offline"], + ["prompt", "select_account"], + // Does not work with "legacy" clients like Thunderbird, do not know why, + // also the OAuth UI looks different from Firefox. + //["login_hint", "test@gmail.com"], + ]; + + // Storing the accountID as part of the URI has multiple benefits: + // - it does not get lost during offline support disable/enable + // - we can connect multiple google accounts without running into same-url-issue of shared calendars + // - if called from lightning, we do not need to do an expensive url search to get the accountID + let accountID = uri.username || ((configObject && configObject.hasOwnProperty("accountID")) ? configObject.accountID : null); + + let accountData = null; + try { + accountData = new TbSync.AccountData(accountID); + } catch (e) {}; + + if (configObject && configObject.hasOwnProperty("accountname")) { + oauth.requestWindowTitle = "TbSync account <" + configObject.accountname + "> requests authorization."; + } else if (accountData) { + oauth.requestWindowTitle = "TbSync account <" + accountData.getAccountProperty("accountname") + "> requests authorization."; + } else { + oauth.requestWindowTitle = "A TbSync account requests authorization."; + } + + + + + /* Adding custom methods to the oauth object */ + + // Similar to tbSyncDavCalendar.oauthConnect(), but true async. + 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 = 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; + } + 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.dav.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.username, 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 ""; + }, + + ConnectionData: class { + constructor(data) { + this._password = ""; + this._username = ""; + this._https = ""; + this._type = ""; + this._fqdn = ""; + this._timeout = dav.Base.getConnectionTimeout(); + + //for error logging + this._eventLogInfo = null; + + //typof syncdata? + let folderData = null; + let accountData = null; + + if (data instanceof TbSync.SyncData) { + folderData = data.currentFolderData; + accountData = data.accountData; + this._eventLogInfo = data.eventLogInfo; + } else if (data instanceof TbSync.FolderData) { + folderData = data; + accountData = data.accountData; + this._eventLogInfo = new TbSync.EventLogInfo( + accountData.getAccountProperty("provider"), + accountData.getAccountProperty("accountname"), + accountData.accountID, + folderData.getFolderProperty("foldername")); + } else if (data instanceof TbSync.AccountData) { + accountData = data; + this._eventLogInfo = new TbSync.EventLogInfo( + accountData.getAccountProperty("provider"), + accountData.getAccountProperty("accountname"), + accountData.accountID, + ""); + } + + if (accountData) { + let authData = dav.network.getAuthData(accountData); + this._password = authData.password; + this._username = authData.username; + + this._accountname = accountData.getAccountProperty("accountname"); + if (folderData) { + this._fqdn = folderData.getFolderProperty("fqdn"); + this._https = folderData.getFolderProperty("https"); + } + this.accountData = accountData; + } + } + + + set password(v) {this._password = v;} + set username(v) {this._username = v;} + set timeout(v) {this._timeout = v;} + set https(v) {this._https = v;} + set fqdn(v) {this._fqdn = v;} + set eventLogInfo(v) {this._eventLogInfo = v;} + + get password() {return this._password;} + get username() {return this._username;} + get timeout() {return this._timeout;} + get https() {return this._https;} + get fqdn() {return this._fqdn;} + get eventLogInfo() {return this._eventLogInfo;} + }, + + + checkForRFC6764Request: async function (path, eventLogInfo) { + function checkDefaultSecPort (sec) { + return sec ? "443" : "80"; + } + + if (!this.isRFC6764Request(path)) { + return path; + } + + let parts = path.toLowerCase().split("6764://"); + let type = parts[0].endsWith("caldav") ? "caldav" : "carddav"; + + // obey preselected security level for DNS lookup + // and only use insecure option if specified + let scheme = parts[0].startsWith("httpca") ? "http" : "https"; //httpcaldav or httpcarddav = httpca = http + let sec = (scheme == "https"); + + let hostPath = parts[1]; + while (hostPath.endsWith("/")) { hostPath = hostPath.slice(0,-1); } + let host = hostPath.split("/")[0]; + + let result = {}; + + //only perform dns lookup, if the provided path does not contain any path information + if (host == hostPath) { + let request = "_" + type + (sec ? "s" : "") + "._tcp." + host; + + // get host from SRV record + let rv = await DNS.srv(request); + if (rv && Array.isArray(rv) && rv.length>0 && rv[0].host) { + result.secure = sec; + result.host = rv[0].host + ((checkDefaultSecPort(sec) == rv[0].port) ? "" : ":" + rv[0].port); + TbSync.eventlog.add("info", eventLogInfo, "RFC6764 DNS request succeeded", "SRV record @ " + request + "\n" + JSON.stringify(rv[0])); + + // Now try to get path from TXT + rv = await DNS.txt(request); + if (rv && Array.isArray(rv) && rv.length>0 && rv[0].data && rv[0].data.toLowerCase().startsWith("path=")) { + result.path = rv[0].data.substring(5); + TbSync.eventlog.add("info", eventLogInfo, "RFC6764 DNS request succeeded", "TXT record @ " + request + "\n" + JSON.stringify(rv[0])); + } else { + result.path = "/.well-known/" + type; + } + + result.url = "http" + (result.secure ? "s" : "") + "://" + result.host + result.path; + return result.url; + } else { + TbSync.eventlog.add("warning", eventLogInfo, "RFC6764 DNS request failed", "SRV record @ " + request); + } + } + + // use the provided hostPath and build standard well-known url + return scheme + "://" + hostPath + "/.well-known/" + type; + }, + + startsWithScheme: function (url) { + return (url.toLowerCase().startsWith("http://") || url.toLowerCase().startsWith("https://") || this.isRFC6764Request(url)); + }, + + isRFC6764Request: function (url) { + let parts = url.split("6764://"); + return (parts.length == 2 && parts[0].endsWith("dav")); + }, + + sendRequest: async function (requestData, path, method, connectionData, headers = {}, options = {}) { + let url = await this.checkForRFC6764Request(path, connectionData.eventLogInfo); + let enforcedPermanentlyRedirectedUrl = (url != path) ? url : null; + + // path could be absolute or relative, we may need to rebuild the full url. + if (url.startsWith("http://") || url.startsWith("https://")) { + // extract segments from url + let uri = Services.io.newURI(url); + connectionData.https = (uri.scheme == "https"); + connectionData.fqdn = uri.hostPort; + } else { + url = "http" + (connectionData.https ? "s" : "") + "://" + connectionData.fqdn + url; + } + + let currentSyncState = connectionData.accountData ? connectionData.accountData.syncData.getSyncState().state : ""; + let accountID = connectionData.accountData ? connectionData.accountData.accountID : ""; + + // Loop: Prompt user for password and retry + const MAX_RETRIES = options.hasOwnProperty("passwordRetries") ? options.passwordRetries+1 : 5; + for (let i=1; i <= MAX_RETRIES; i++) { + TbSync.dump("URL Request #" + i, url); + + connectionData.url = url; + + connectionData.oauthObj = dav.network.getOAuthObj(connectionData.url, { username: connectionData.username, accountID }); + // Check OAUTH situation before connecting. + if (connectionData.oauthObj && (!connectionData.oauthObj.accessToken || connectionData.oauthObj.isExpired())) { + let rv = {} + if (connectionData.accountData) { + connectionData.accountData.syncData.setSyncState("oauthprompt"); + } + + if (await connectionData.oauthObj.asyncConnect(rv)) { + connectionData.password = rv.tokens; + } else { + throw dav.sync.finish("error", rv.error); + } + } + + // Restore original syncstate before open the connection + if (connectionData.accountData && currentSyncState != connectionData.accountData.syncData.getSyncState().state) { + connectionData.accountData.syncData.setSyncState(currentSyncState); + } + + let r = await dav.network.promisifiedHttpRequest(requestData, method, connectionData, headers, options); + if (r && enforcedPermanentlyRedirectedUrl && !r.permanentlyRedirectedUrl) { + r.permanentlyRedirectedUrl = enforcedPermanentlyRedirectedUrl; + } + + if (r && r.passwordPrompt && r.passwordPrompt === true) { + if (i == MAX_RETRIES) { + // If this is the final retry, abort with error. + throw r.passwordError; + } else { + let credentials = null; + let retry = false; + + // Prompt, if connection belongs to an account (and not from the create wizard) + if (connectionData.accountData) { + if (connectionData.oauthObj) { + connectionData.oauthObj.accessToken = ""; + retry = true; + } else { + let promptData = { + windowID: "auth:" + connectionData.accountData.accountID, + accountname: connectionData.accountData.getAccountProperty("accountname"), + usernameLocked: connectionData.accountData.isConnected(), + username: connectionData.username, + } + connectionData.accountData.syncData.setSyncState("passwordprompt"); + + credentials = await TbSync.passwordManager.asyncPasswordPrompt(promptData, dav.openWindows); + if (credentials) { + // update login data + dav.network.getAuthData(connectionData.accountData).updateLoginData(credentials.username, credentials.password); + // update connection data + connectionData.username = credentials.username; + connectionData.password = credentials.password; + retry = true; + } + } + } + + if (!retry) { + throw r.passwordError; + } + + } + } else { + return r; + } + } + }, + + // Promisified implementation of TbSync's HttpRequest (with XHR interface) + promisifiedHttpRequest: function (requestData, method, connectionData, headers, options) { + let responseData = ""; + + //do not log HEADERS, as it could contain an Authorization header + //TbSync.dump("HEADERS", JSON.stringify(headers)); + if (TbSync.prefs.getIntPref("log.userdatalevel") > 1) TbSync.dump("REQUEST", method + " : " + requestData); + + if (!options.hasOwnProperty("softfail")) { + options.softfail = []; + } + + if (!options.hasOwnProperty("responseType")) { + options.responseType = "xml"; + } + + return new Promise(function(resolve, reject) { + let req = new HttpRequest(); + + req.timeout = connectionData.timeout; + req.mozBackgroundRequest = true; + + req.open(method, connectionData.url, true, connectionData.username, connectionData.password); + + if (options.hasOwnProperty("containerRealm")) req.setContainerRealm(options.containerRealm); + if (options.hasOwnProperty("containerReset") && options.containerReset == true) req.clearContainerCache(); + + if (headers) { + for (let header in headers) { + req.setRequestHeader(header, headers[header]); + } + } + + if (options.responseType == "base64") { + req.responseAsBase64 = true; + } + + req.setRequestHeader("User-Agent", dav.sync.prefSettings.getCharPref("clientID.useragent")); + + // If this is one of the servers which we use OAuth for, add the bearer token. + if (connectionData.oauthObj) { + req.setRequestHeader("Authorization", "Bearer " + dav.network.getOAuthValue(connectionData.password, "access")); + } + + req.realmCallback = function(username, realm, host) { + // Store realm, needed later to setup lightning passwords. + TbSync.dump("Found CalDAV authRealm for <"+host+">", realm); + connectionData.realm = realm; + }; + + req.onerror = function () { + let error = TbSync.network.createTCPErrorFromFailedXHR(req); + if (!error) { + return reject(dav.sync.finish("error", "networkerror", "URL:\n" + connectionData.url + " ("+method+")")); //reject/resolve do not terminate control flow + } else { + return reject(dav.sync.finish("error", error, "URL:\n" + connectionData.url + " ("+method+")")); + } + }; + + req.ontimeout = req.onerror; + + req.onredirect = function(flags, uri) { + console.log("Redirect ("+ flags.toString(2) +"): " + uri.spec); + // Update connection settings from current URL + let newHttps = (uri.scheme == "https"); + if (connectionData.https != newHttps) { + TbSync.dump("Updating HTTPS", connectionData.https + " -> " + newHttps); + connectionData.https = newHttps; + } + if (connectionData.fqdn !=uri.hostPort) { + TbSync.dump("Updating FQDN", connectionData.fqdn + " -> " + uri.hostPort); + connectionData.fqdn = uri.hostPort; + } + }; + + req.onload = function() { + if (TbSync.prefs.getIntPref("log.userdatalevel") > 1) TbSync.dump("RESPONSE", req.status + " ("+req.statusText+")" + " : " + req.responseText); + responseData = req.responseText.split("><").join(">\n<"); + + let commLog = "URL:\n" + connectionData.url + " ("+method+")" + "\n\nRequest:\n" + requestData + "\n\nResponse:\n" + responseData; + let aResult = req.responseText; + let responseStatus = req.status; + + switch(responseStatus) { + case 401: //AuthError + { + let response = {}; + response.passwordPrompt = true; + response.passwordError = dav.sync.finish("error", responseStatus, commLog); + return resolve(response); + } + break; + + case 207: //preprocess multiresponse + { + let xml = dav.tools.convertToXML(aResult); + if (xml === null) return reject(dav.sync.finish("warning", "malformed-xml", commLog)); + + let response = {}; + response.davOptions = req.getResponseHeader("dav"); + response.responseURL = req.responseURL; + response.permanentlyRedirectedUrl = req.permanentlyRedirectedUrl; + response.commLog = commLog; + response.node = xml.documentElement; + + let multi = xml.documentElement.getElementsByTagNameNS(dav.sync.ns.d, "response"); + response.multi = []; + for (let i=0; i < multi.length; i++) { + let hrefNode = dav.tools.evaluateNode(multi[i], [["d","href"]]); + let responseStatusNode = dav.tools.evaluateNode(multi[i], [["d", "status"]]); + let propstats = multi[i].getElementsByTagNameNS(dav.sync.ns.d, "propstat"); + if (propstats.length > 0) { + //response contains propstats, push each as single entry + for (let p=0; p < propstats.length; p++) { + let statusNode = dav.tools.evaluateNode(propstats[p], [["d", "status"]]); + + let resp = {}; + resp.node = propstats[p]; + resp.status = statusNode === null ? null : statusNode.textContent.split(" ")[1]; + resp.responsestatus = responseStatusNode === null ? null : responseStatusNode.textContent.split(" ")[1]; + resp.href = hrefNode === null ? null : hrefNode.textContent; + response.multi.push(resp); + } + } else { + //response does not contain any propstats, push raw response + let resp = {}; + resp.node = multi[i]; + resp.status = responseStatusNode === null ? null : responseStatusNode.textContent.split(" ")[1]; + resp.responsestatus = responseStatusNode === null ? null : responseStatusNode.textContent.split(" ")[1]; + resp.href = hrefNode === null ? null : hrefNode.textContent; + response.multi.push(resp); + } + } + + return resolve(response); + } + + + case 200: //returned by DELETE by radicale - watch this !!! + return resolve(aResult); + + case 204: //is returned by DELETE - no data + case 201: //is returned by CREATE - no data + return resolve(null); + break; + + default: + if (options.softfail.includes(responseStatus)) { + let noresponse = {}; + noresponse.softerror = responseStatus; + let xml = dav.tools.convertToXML(aResult); + if (xml !== null) { + let exceptionNode = dav.tools.evaluateNode(xml.documentElement, [["s","exception"]]); + if (exceptionNode !== null) { + noresponse.exception = exceptionNode.textContent; + } + } + //manually log this non-fatal error + TbSync.eventlog.add("info", connectionData.eventLogInfo, "softerror::"+responseStatus, commLog); + return resolve(noresponse); + } else { + return reject(dav.sync.finish("warning", responseStatus, commLog)); + } + break; + + } + }; + + req.send(requestData); + }); + } +} diff -Nru dav4tbsync-0.15/content/includes/sync.js dav4tbsync-1.9/content/includes/sync.js --- dav4tbsync-0.15/content/includes/sync.js 1970-01-01 00:00:00.000000000 +0000 +++ dav4tbsync-1.9/content/includes/sync.js 2020-02-20 12:39:38.000000000 +0000 @@ -0,0 +1,921 @@ +/* +/* + * This file is part of DAV-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 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/DAV: Unknown status <"+aStatus+">"); + status = TbSync.StatusData.ERROR; + break; + } + + let e = new Error(); + e.name = "dav4tbsync"; + e.message = status.toUpperCase() + ": " + msg.toString() + " (" + details.toString() + ")"; + e.statusData = new TbSync.StatusData(status, msg.toString(), details.toString()); + return e; + }, + + prefSettings: Services.prefs.getBranch("extensions.dav4tbsync."), + + ns: { + d: "DAV:", + cal: "urn:ietf:params:xml:ns:caldav" , + card: "urn:ietf:params:xml:ns:carddav" , + cs: "http://calendarserver.org/ns/", + s: "http://sabredav.org/ns", + apple: "http://apple.com/ns/ical/" + }, + + serviceproviders: { + "fruux" : {revision: 1, icon: "fruux", caldav: "https://dav.fruux.com", carddav: "https://dav.fruux.com"}, + "mbo" : {revision: 1, icon: "mbo", caldav: "caldav6764://mailbox.org", carddav: "carddav6764://mailbox.org"}, + "icloud" : {revision: 1, icon: "icloud", caldav: "https://caldav.icloud.com", carddav: "https://contacts.icloud.com"}, + "google" : {revision: 1, icon: "google", caldav: "https://apidata.googleusercontent.com/caldav/v2/", carddav: "https://www.googleapis.com/.well-known/carddav"}, + "gmx.net" : {revision: 1, icon: "gmx", caldav: "caldav6764://gmx.net", carddav: "carddav6764://gmx.net"}, + "gmx.com" : {revision: 1, icon: "gmx", caldav: "caldav6764://gmx.com", carddav: "carddav6764://gmx.com"}, + "posteo" : {revision: 1, icon: "posteo", caldav: "https://posteo.de:8443", carddav: "posteo.de:8843"}, + "web.de" : {revision: 1, icon: "web", caldav: "caldav6764://web.de", carddav: "carddav6764://web.de"}, + "yahoo" : {revision: 1, icon: "yahoo", caldav: "caldav6764://yahoo.com", carddav: "carddav6764://yahoo.com"}, + }, + + onChange(abItem) { + if (!this._syncOnChangeTimers) + this._syncOnChangeTimers = {}; + + this._syncOnChangeTimers[abItem.abDirectory.UID] = {}; + this._syncOnChangeTimers[abItem.abDirectory.UID].timer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer); + this._syncOnChangeTimers[abItem.abDirectory.UID].event = { + notify: function(timer) { + // if account is syncing, re-schedule + // if folder got synced after the start time (due to re-scheduling) abort + console.log("DONE: "+ abItem.abDirectory.UID); + } + } + + this._syncOnChangeTimers[abItem.abDirectory.UID].timer.initWithCallback( + this._syncOnChangeTimers[abItem.abDirectory.UID].event, + 2000, + Components.interfaces.nsITimer.TYPE_ONE_SHOT); + }, + + resetFolderSyncInfo : function (folderData) { + folderData.resetFolderProperty("ctag"); + folderData.resetFolderProperty("token"); + folderData.setFolderProperty("createdWithProviderVersion", folderData.accountData.providerData.getVersion()); + }, + + folderList: async function (syncData) { + //Method description: http://sabre.io/dav/building-a-caldav-client/ + //get all folders currently known + let folderTypes = ["caldav", "carddav", "ics"]; + let unhandledFolders = {}; + for (let type of folderTypes) { + unhandledFolders[type] = []; + } + + + let folders = syncData.accountData.getAllFolders(); + for (let folder of folders) { + //just in case + if (!unhandledFolders.hasOwnProperty(folder.getFolderProperty("type"))) { + unhandledFolders[folder.getFolderProperty("type")] = []; + } + unhandledFolders[folder.getFolderProperty("type")].push(folder); + } + + // refresh urls of service provider, if they have been updated + let serviceprovider = syncData.accountData.getAccountProperty("serviceprovider"); + let serviceproviderRevision = syncData.accountData.getAccountProperty("serviceproviderRevision"); + if (dav.sync.serviceproviders.hasOwnProperty(serviceprovider) && serviceproviderRevision != dav.sync.serviceproviders[serviceprovider].revision) { + TbSync.eventlog.add("info", syncData.eventLogInfo, "updatingServiceProvider", serviceprovider); + syncData.accountData.setAccountProperty("serviceproviderRevision", dav.sync.serviceproviders[serviceprovider].revision); + syncData.accountData.resetAccountProperty("calDavPrincipal"); + syncData.accountData.resetAccountProperty("cardDavPrincipal"); + syncData.accountData.setAccountProperty("calDavHost", dav.sync.serviceproviders[serviceprovider].caldav); + syncData.accountData.setAccountProperty("cardDavHost", dav.sync.serviceproviders[serviceprovider].carddav); + } + + let davjobs = { + cal : {server: syncData.accountData.getAccountProperty("calDavHost")}, + card : {server: syncData.accountData.getAccountProperty("cardDavHost")}, + }; + + for (let job in davjobs) { + if (!davjobs[job].server) continue; + + // SOGo needs some special handling for shared addressbooks. We detect it by having SOGo/dav in the url. + let isSogo = davjobs[job].server.includes("/SOGo/dav"); + + //sync states are only printed while the account state is "syncing" to inform user about sync process (it is not stored in DB, just in syncData) + //example state "getfolders" to get folder information from server + //if you send a request to a server and thus have to wait for answer, use a "send." syncstate, which will give visual feedback to the user, + //that we are waiting for an answer with timeout countdown + + let home = []; + let own = []; + + // migration code for http setting, we might keep it as a fallback, if user removed the http:// scheme from the url in the settings + if (!dav.network.startsWithScheme(davjobs[job].server)) { + davjobs[job].server = "http" + (syncData.accountData.getAccountProperty("https") ? "s" : "") + "://" + davjobs[job].server; + syncData.accountData.setAccountProperty(job + "DavHost", davjobs[job].server); + } + + //add connection to syncData + syncData.connectionData = new dav.network.ConnectionData(syncData); + + //only do that, if a new calendar has been enabled + TbSync.network.resetContainerForUser(syncData.connectionData.username); + + syncData.setSyncState("send.getfolders"); + let principal = syncData.accountData.getAccountProperty(job + "DavPrincipal"); // defaults to null + if (principal === null) { + + let response = await dav.network.sendRequest("", davjobs[job].server , "PROPFIND", syncData.connectionData, {"Depth": "0", "Prefer": "return=minimal"}); + syncData.setSyncState("eval.folders"); + + // keep track of permanent redirects for the server URL + if (response && response.permanentlyRedirectedUrl) { + syncData.accountData.setAccountProperty(job + "DavHost", response.permanentlyRedirectedUrl) + } + + // store dav options send by server + if (response && response.davOptions) { + syncData.accountData.setAccountProperty(job + "DavOptions", response.davOptions.split(",").map(e => e.trim())); + } + + // allow 404 because iCloud sends it on valid answer (yeah!) + if (response && response.multi) { + principal = dav.tools.getNodeTextContentFromMultiResponse(response, [["d","prop"], ["d","current-user-principal"], ["d","href"]], null, ["200","404"]); + } + } + + //principal now contains something like "/remote.php/carddav/principals/john.bieling/" + //principal can also be an absolute url + // -> get home/root of storage + if (principal !== null) { + syncData.setSyncState("send.getfolders"); + + let options = syncData.accountData.getAccountProperty(job + "DavOptions"); + + let homeset = (job == "cal") + ? "calendar-home-set" + : "addressbook-home-set"; + + let request = "<"+job+":" + homeset + " />" + + (job == "cal" && options.includes("calendar-proxy") ? "" : "") + + "" + + ""; + + let response = await dav.network.sendRequest(request, principal, "PROPFIND", syncData.connectionData, {"Depth": "0", "Prefer": "return=minimal"}); + syncData.setSyncState("eval.folders"); + + // keep track of permanent redirects for the principal URL + if (response && response.permanentlyRedirectedUrl) { + principal = response.permanentlyRedirectedUrl; + } + + own = dav.tools.getNodesTextContentFromMultiResponse(response, [["d","prop"], [job, homeset ], ["d","href"]], principal); + home = own.concat(dav.tools.getNodesTextContentFromMultiResponse(response, [["d","prop"], ["cs", "calendar-proxy-read-for" ], ["d","href"]], principal)); + home = home.concat(dav.tools.getNodesTextContentFromMultiResponse(response, [["d","prop"], ["cs", "calendar-proxy-write-for" ], ["d","href"]], principal)); + + //Any groups we need to find? Only diving one level at the moment, + let g = dav.tools.getNodesTextContentFromMultiResponse(response, [["d","prop"], ["d", "group-membership" ], ["d","href"]], principal); + for (let gc=0; gc < g.length; gc++) { + //SOGo reports a 403 if I request the provided resource, also since we do not dive, remove the request for group-membership + response = await dav.network.sendRequest(request.replace("",""), g[gc], "PROPFIND", syncData.connectionData, {"Depth": "0", "Prefer": "return=minimal"}, {softfail: [403, 404]}); + if (response && response.softerror) { + continue; + } + home = home.concat(dav.tools.getNodesTextContentFromMultiResponse(response, [["d","prop"], [job, homeset ], ["d","href"]], g[gc])); + } + + //calendar-proxy and group-membership could have returned the same values, make the homeset unique + home = home.filter((v,i,a) => a.indexOf(v) == i); + } else { + // do not throw here, but log the error and skip this server + TbSync.eventlog.add("error", syncData.eventLogInfo, job+"davservernotfound", davjobs[job].server); + } + + //home now contains something like /remote.php/caldav/calendars/john.bieling/ + // -> get all resources + if (home.length > 0) { + // the used principal returned valid resources, store/update it + // as the principal is being used as a starting point, it must be stored as absolute url + syncData.accountData.setAccountProperty(job + "DavPrincipal", dav.network.startsWithScheme(principal) + ? principal + : "http" + (syncData.connectionData.https ? "s" : "") + "://" + syncData.connectionData.fqdn + principal); + + for (let h=0; h < home.length; h++) { + syncData.setSyncState("send.getfolders"); + let request = (job == "cal") + ? "" + : ""; + + //some servers report to have calendar-proxy-read but return a 404 when that gets actually queried + let response = await dav.network.sendRequest(request, home[h], "PROPFIND", syncData.connectionData, {"Depth": "1", "Prefer": "return=minimal"}, {softfail: [403, 404]}); + if (response && response.softerror) { + continue; + } + + for (let r=0; r < response.multi.length; r++) { + if (response.multi[r].status != "200") continue; + + let resourcetype = null; + //is this a result with a valid recourcetype? (the node must be present) + switch (job) { + case "card": + if (dav.tools.evaluateNode(response.multi[r].node, [["d","prop"], ["d","resourcetype"], ["card", "addressbook"]]) !== null) resourcetype = "carddav"; + break; + + case "cal": + if (dav.tools.evaluateNode(response.multi[r].node, [["d","prop"], ["d","resourcetype"], ["cal", "calendar"]]) !== null) resourcetype = "caldav"; + else if (dav.tools.evaluateNode(response.multi[r].node, [["d","prop"], ["d","resourcetype"], ["cs", "subscribed"]]) !== null) resourcetype = "ics"; + break; + } + if (resourcetype === null) continue; + + //get ACL (grant read rights per default, if it is SOGo, as they do not send that permission) + let acl = isSogo ? 0x1 : 0; + + let privilegNode = dav.tools.evaluateNode(response.multi[r].node, [["d","prop"], ["d","current-user-privilege-set"]]); + if (privilegNode) { + if (privilegNode.getElementsByTagNameNS(dav.sync.ns.d, "all").length > 0) { + acl = 0xF; //read=1, mod=2, create=4, delete=8 + } else { + // check for individual write permissions + if (privilegNode.getElementsByTagNameNS(dav.sync.ns.d, "write").length > 0) { + acl = 0xF; + } else { + if (privilegNode.getElementsByTagNameNS(dav.sync.ns.d, "write-content").length > 0) acl |= 0x2; + if (privilegNode.getElementsByTagNameNS(dav.sync.ns.d, "bind").length > 0) acl |= 0x4; + if (privilegNode.getElementsByTagNameNS(dav.sync.ns.d, "unbind").length > 0) acl |= 0x8; + } + + // check for read permission (implying read if any write is given) + if (privilegNode.getElementsByTagNameNS(dav.sync.ns.d, "read").length > 0 || acl != 0) acl |= 0x1; + } + } + + //ignore this resource, if no read access + if ((acl & 0x1) == 0) continue; + + let href = response.multi[r].href; + if (resourcetype == "ics") href = dav.tools.evaluateNode(response.multi[r].node, [["d","prop"], ["cs","source"], ["d","href"]]).textContent; + + let name_node = dav.tools.evaluateNode(response.multi[r].node, [["d","prop"], ["d","displayname"]]); + let name = TbSync.getString("defaultname." + ((job == "cal") ? "calendar" : "contacts") , "dav"); + if (name_node != null) { + name = name_node.textContent; + } + let color = dav.tools.evaluateNode(response.multi[r].node, [["d","prop"], ["apple","calendar-color"]]); + + //remove found folder from list of unhandled folders + unhandledFolders[resourcetype] = unhandledFolders[resourcetype].filter(item => item.getFolderProperty("href") !== href); + + + // interaction with TbSync + // do we have a folder for that href? + let folderData = syncData.accountData.getFolder("href", href); + if (!folderData) { + // create a new folder entry + folderData = syncData.accountData.createNewFolder(); + // this MUST be set to either "addressbook" or "calendar" to use the standard target support, or any other value, which + // requires a corresponding targets implementation by this provider + folderData.setFolderProperty("targetType", (job == "card") ? "addressbook" : "calendar"); + + folderData.setFolderProperty("href", href); + folderData.setFolderProperty("foldername", name); + folderData.setFolderProperty("type", resourcetype); + folderData.setFolderProperty("shared", !own.includes(home[h])); + folderData.setFolderProperty("acl", acl.toString()); + folderData.setFolderProperty("downloadonly", (acl == 0x1)); //if any write access is granted, setup as writeable + + //we assume the folder has the same fqdn as the homeset, otherwise href must contain the full URL and the fqdn is ignored + folderData.setFolderProperty("fqdn", syncData.connectionData.fqdn); + folderData.setFolderProperty("https", syncData.connectionData.https); + + //do we have a cached folder? + let cachedFolderData = syncData.accountData.getFolderFromCache("href", href); + if (cachedFolderData) { + // copy fields from cache which we want to re-use + folderData.setFolderProperty("targetColor", cachedFolderData.getFolderProperty("targetColor")); + folderData.setFolderProperty("targetName", cachedFolderData.getFolderProperty("targetName")); + //if we have only READ access, do not restore cached value for downloadonly + if (acl > 0x1) folderData.setFolderProperty("downloadonly", cachedFolderData.getFolderProperty("downloadonly")); + } + } else { + //Update name & color + folderData.setFolderProperty("foldername", name); + folderData.setFolderProperty("fqdn", syncData.connectionData.fqdn); + folderData.setFolderProperty("https", syncData.connectionData.https); + folderData.setFolderProperty("acl", acl); + //if the acl changed from RW to RO we need to update the downloadonly setting + if (acl == 0x1) { + folderData.setFolderProperty("downloadonly", true); + } + } + + // Update color from server (skip if nolightning, no + // need to run into error when hasTarget() throws). + if (color && job == "cal" && TbSync.lightning.isAvailable()) { + color = color.textContent.substring(0,7); + folderData.setFolderProperty("targetColor", color); + + // Do we have to update the calendar? + if (folderData.targetData && folderData.targetData.hasTarget()) { + try { + let targetCal = await folderData.targetData.getTarget(); + targetCal.calendar.setProperty("color", color); + } catch (e) { + Components.utils.reportError(e) + } + } + } + } + } + } else { + //home was not found - connection error? - do not delete unhandled folders + switch (job) { + case "card": + unhandledFolders.carddav = []; + break; + + case "cal": + unhandledFolders.caldav = []; + unhandledFolders.ics = []; + break; + } + //reset stored principal + syncData.accountData.resetAccountProperty(job + "DavPrincipal"); + } + } + + // Remove unhandled old folders, (because they no longer exist on the server). + // Do not delete the targets, but keep them as stale/unconnected elements. + for (let type of folderTypes) { + for (let folder of unhandledFolders[type]) { + folder.remove("[deleted on server]"); + } + } + }, + + + + + + + folder: async function (syncData) { + // add connection data to syncData + syncData.connectionData = new dav.network.ConnectionData(syncData); + + // add target to syncData (getTarget() will throw "nolightning" if lightning missing) + let hadTarget; + try { + // accessing the target for the first time will check if it is avail and if not will create it (if possible) + hadTarget = syncData.currentFolderData.targetData.hasTarget(); + syncData.target = await syncData.currentFolderData.targetData.getTarget(); + } catch (e) { + Components.utils.reportError(e); + throw dav.sync.finish("warning", e.message); + } + + switch (syncData.currentFolderData.getFolderProperty("type")) { + case "carddav": + { + await dav.sync.singleFolder(syncData); + } + break; + + case "caldav": + case "ics": + { + // update downloadonly - we do not use TbCalendar (syncData.target) but the underlying lightning calendar obj + if (syncData.currentFolderData.getFolderProperty("downloadonly")) syncData.target.calendar.setProperty("readOnly", true); + + // update username of calendar + syncData.target.calendar.setProperty("username", syncData.connectionData.username); + + //init sync via lightning + if (hadTarget) syncData.target.calendar.refresh(); + + throw dav.sync.finish("ok", "managed-by-lightning"); + } + break; + + default: + { + throw dav.sync.finish("warning", "notsupported"); + } + break; + } + }, + + + singleFolder: async function (syncData) { + let downloadonly = syncData.currentFolderData.getFolderProperty("downloadonly"); + + // we have to abort sync of this folder, if it is contact, has groupSync enabled and gContactSync is enabled + let syncGroups = syncData.accountData.getAccountProperty("syncGroups"); + let gContactSync = await AddonManager.getAddonByID("gContactSync@pirules.net") ; + let contactSync = (syncData.currentFolderData.getFolderProperty("type") == "carddav"); + if (syncGroups && contactSync && gContactSync && gContactSync.isActive) { + throw dav.sync.finish("warning", "gContactSync"); + } + + await dav.sync.remoteChanges(syncData); + let numOfLocalChanges = await dav.sync.localChanges(syncData); + + //revert all local changes on permission error by doing a clean sync + if (numOfLocalChanges < 0) { + dav.sync.resetFolderSyncInfo(syncData.currentFolderData); + await dav.sync.remoteChanges(syncData); + + if (!downloadonly) throw dav.sync.finish("info", "info.restored"); + } else if (numOfLocalChanges > 0){ + //we will get back our own changes and can store etags and vcards and also get a clean ctag/token + await dav.sync.remoteChanges(syncData); + } + }, + + + + + + + + + + + remoteChanges: async function (syncData) { + //Do we have a sync token? No? -> Initial Sync (or WebDAV sync not supported) / Yes? -> Get updates only (token only present if WebDAV sync is suported) + let token = syncData.currentFolderData.getFolderProperty("token"); + let isGoogle = (syncData.accountData.getAccountProperty("serviceprovider") == "google"); + if (token && !isGoogle) { + //update via token sync + let tokenSyncSucceeded = await dav.sync.remoteChangesByTOKEN(syncData); + if (tokenSyncSucceeded) return; + + //token sync failed, reset ctag and token and do a full sync + dav.sync.resetFolderSyncInfo(syncData.currentFolderData); + } + + //Either token sync did not work or there is no token (initial sync) + //loop until ctag is the same before and after polling data (sane start condition) + let maxloops = 20; + for (let i=0; i <= maxloops; i++) { + if (i == maxloops) + throw dav.sync.finish("warning", "could-not-get-stable-ctag"); + + let ctagChanged = await dav.sync.remoteChangesByCTAG(syncData); + if (!ctagChanged) break; + } + }, + + remoteChangesByTOKEN: async function (syncData) { + syncData.progressData.reset(); + + let token = syncData.currentFolderData.getFolderProperty("token"); + syncData.setSyncState("send.request.remotechanges"); + let cards = await dav.network.sendRequest(""+token+"1", syncData.currentFolderData.getFolderProperty("href"), "REPORT", syncData.connectionData, {}, {softfail: [415,403,409]}); + + //EteSync throws 409 because it does not support sync-token + //Sabre\DAV\Exception\ReportNotSupported - Unsupported media type - returned by fruux if synctoken is 0 (empty book), 415 & 403 + //https://github.com/sabre-io/dav/issues/1075 + //Sabre\DAV\Exception\InvalidSyncToken (403) + if (cards && cards.softerror) { + //token sync failed, reset ctag and do a full sync + return false; + } + + let tokenNode = dav.tools.evaluateNode(cards.node, [["d", "sync-token"]]); + if (tokenNode === null) { + //token sync failed, reset ctag and do a full sync + return false; + } + + let vCardsDeletedOnServer = []; + let vCardsChangedOnServer = {}; + + let localDeletes = syncData.target.getDeletedItemsFromChangeLog(); + + let cardsFound = 0; + for (let c=0; c < cards.multi.length; c++) { + let id = cards.multi[c].href; + if (id !==null) { + //valid + let card = syncData.target.getItemFromProperty("X-DAV-HREF", id); + if (cards.multi[c].status == "200") { + //MOD or ADD + let etag = dav.tools.evaluateNode(cards.multi[c].node, [["d","prop"], ["d","getetag"]]); + if (!card) { + //if the user deleted this card (not yet send to server), do not add it again + if (!localDeletes.includes(id)) { + cardsFound++; + vCardsChangedOnServer[id] = "ADD"; + } + } else if (etag.textContent != card.getProperty("X-DAV-ETAG")) { + cardsFound++; + vCardsChangedOnServer[id] = "MOD"; + } + } else if (cards.multi[c].responsestatus == "404" && card) { + //DEL + cardsFound++; + vCardsDeletedOnServer.push(card); + } else { + //We received something, that is not a DEL, MOD or ADD + TbSync.eventlog.add("warning", syncData.eventLogInfo, "Unknown XML", JSON.stringify(cards.multi[c])); + } + } + } + + // reset sync process + syncData.progressData.reset(0, cardsFound); + + //download all cards added to vCardsChangedOnServer and process changes + await dav.sync.multiget(syncData, vCardsChangedOnServer); + + //delete all contacts added to vCardsDeletedOnServer + await dav.sync.deleteContacts (syncData, vCardsDeletedOnServer); + + //update token + syncData.currentFolderData.setFolderProperty("token", tokenNode.textContent); + + return true; + }, + + remoteChangesByCTAG: async function (syncData) { + syncData.progressData.reset(); + + //Request ctag and token + syncData.setSyncState("send.request.remotechanges"); + let response = await dav.network.sendRequest("", syncData.currentFolderData.getFolderProperty("href"), "PROPFIND", syncData.connectionData, {"Depth": "0"}); + + syncData.setSyncState("eval.response.remotechanges"); + let ctag = dav.tools.getNodeTextContentFromMultiResponse(response, [["d","prop"], ["cs", "getctag"]], syncData.currentFolderData.getFolderProperty("href")); + let token = dav.tools.getNodeTextContentFromMultiResponse(response, [["d","prop"], ["d", "sync-token"]], syncData.currentFolderData.getFolderProperty("href")); + + let localDeletes = syncData.target.getDeletedItemsFromChangeLog(); + + //if CTAG changed, we need to sync everything and compare + if (ctag === null || ctag != syncData.currentFolderData.getFolderProperty("ctag")) { + let vCardsFoundOnServer = []; + let vCardsChangedOnServer = {}; + + //get etags of all cards on server and find the changed cards + syncData.setSyncState("send.request.remotechanges"); + let cards = await dav.network.sendRequest("", syncData.currentFolderData.getFolderProperty("href"), "PROPFIND", syncData.connectionData, {"Depth": "1", "Prefer": "return=minimal"}); + + //to test other impl + //let cards = await dav.network.sendRequest("", syncData.currentFolderData.getFolderProperty("href"), "PROPFIND", syncData.connectionData, {"Depth": "1", "Prefer": "return=minimal"}, {softfail: []}, false); + + //this is the same request, but includes getcontenttype, do we need it? icloud send contacts without + //let cards = await dav.network.sendRequest("", syncData.currentFolderData.getFolderProperty("href"), "PROPFIND", syncData.connectionData, {"Depth": "1", "Prefer": "return=minimal"}); + + //play with filters and limits for synology + /* + let additional = "10"; + additional += ""; + additional += ""; + additional += "bogusxy"; + additional += ""; + additional += "";*/ + + //addressbook-query does not work on older servers (zimbra) + //let cards = await dav.network.sendRequest("", syncData.currentFolderData.getFolderProperty("href"), "REPORT", syncData.connectionData, {"Depth": "1", "Prefer": "return=minimal"}); + + syncData.setSyncState("eval.response.remotechanges"); + let cardsFound = 0; + for (let c=0; cards.multi && c < cards.multi.length; c++) { + let id = cards.multi[c].href; + if (id == syncData.currentFolderData.getFolderProperty("href")) { + //some servers (Radicale) report the folder itself and a querry to that would return everything again + continue; + } + let etag = dav.tools.evaluateNode(cards.multi[c].node, [["d","prop"], ["d","getetag"]]); + + //ctype is currently not used, because iCloud does not send one and sabre/dav documentation is not checking ctype + //let ctype = dav.tools.evaluateNode(cards.multi[c].node, [["d","prop"], ["d","getcontenttype"]]); + + if (cards.multi[c].status == "200" && etag !== null && id !== null /* && ctype !== null */) { //we do not actually check the content of ctype - but why do we request it? iCloud seems to send cards without ctype + vCardsFoundOnServer.push(id); + let card = syncData.target.getItemFromProperty("X-DAV-HREF", id); + if (!card) { + //if the user deleted this card (not yet send to server), do not add it again + if (!localDeletes.includes(id)) { + cardsFound++; + vCardsChangedOnServer[id] = "ADD"; + } + } else if (etag.textContent != card.getProperty("X-DAV-ETAG")) { + cardsFound++; + vCardsChangedOnServer[id] = "MOD"; + } + } + } + + //FIND DELETES: loop over current addressbook and check each local card if it still exists on the server + let vCardsDeletedOnServer = []; + let localAdditions = syncData.target.getAddedItemsFromChangeLog(); + let allItems = syncData.target.getAllItems() + for (let card of allItems) { + let id = card.getProperty("X-DAV-HREF"); + if (id && !vCardsFoundOnServer.includes(id) && !localAdditions.includes(id)) { + //delete request from server + cardsFound++; + vCardsDeletedOnServer.push(card); + } + } + + // reset sync process + syncData.progressData.reset(0, cardsFound); + + //download all cards added to vCardsChangedOnServer and process changes + await dav.sync.multiget(syncData, vCardsChangedOnServer); + + //delete all contacts added to vCardsDeletedOnServer + await dav.sync.deleteContacts (syncData, vCardsDeletedOnServer); + + //update ctag and token (if there is one) + if (ctag === null) return false; //if server does not support ctag, "it did not change" + syncData.currentFolderData.setFolderProperty("ctag", ctag); + if (token) syncData.currentFolderData.setFolderProperty("token", token); + + //ctag did change + return true; + } else { + + //ctag did not change + return false; + } + + }, + + + + multiget: async function (syncData, vCardsChangedOnServer) { + //keep track of found mailing lists and its members + syncData.foundMailingListsDuringDownSync = {}; + + //download all changed cards and process changes + let cards2catch = Object.keys(vCardsChangedOnServer); + let maxitems = dav.sync.prefSettings.getIntPref("maxitems"); + + for (let i=0; i < cards2catch.length; i+=maxitems) { + let request = dav.tools.getMultiGetRequest(cards2catch.slice(i, i+maxitems)); + if (request) { + syncData.setSyncState("send.request.remotechanges"); + let cards = await dav.network.sendRequest(request, syncData.currentFolderData.getFolderProperty("href"), "REPORT", syncData.connectionData, {"Depth": "1"}); + + syncData.setSyncState("eval.response.remotechanges"); + for (let c=0; c < cards.multi.length; c++) { + syncData.progressData.inc(); + let id = cards.multi[c].href; + let etag = dav.tools.evaluateNode(cards.multi[c].node, [["d","prop"], ["d","getetag"]]); + let data = dav.tools.evaluateNode(cards.multi[c].node, [["d","prop"], ["card","address-data"]]); + + if (cards.multi[c].status == "200" && etag !== null && data !== null && id !== null && vCardsChangedOnServer.hasOwnProperty(id)) { + switch (vCardsChangedOnServer[id]) { + case "ADD": + await dav.tools.addContact (syncData, id, data, etag); + break; + + case "MOD": + await dav.tools.modifyContact (syncData, id, data, etag); + break; + } + //Feedback from users: They want to see the individual count + syncData.setSyncState("eval.response.remotechanges"); + await TbSync.tools.sleep(100); + } else { + TbSync.dump("Skipped Card", [id, cards.multi[c].status == "200", etag !== null, data !== null, id !== null, vCardsChangedOnServer.hasOwnProperty(id)].join(", ")); + } + } + } + } + // Feedback from users: They want to see the final count. + syncData.setSyncState("eval.response.remotechanges"); + await TbSync.tools.sleep(200); + + // On down sync, mailinglists need to be done at the very end so all member data is avail. + if (syncData.accountData.getAccountProperty("syncGroups")) { + let l=0; + for (let listID in syncData.foundMailingListsDuringDownSync) { + if (syncData.foundMailingListsDuringDownSync.hasOwnProperty(listID)) { + l++; + + let list = syncData.target.getItemFromProperty("X-DAV-HREF", listID); + if (!list.isMailList) + continue; + + let currentMembers = list.getMembersPropertyList("X-DAV-UID"); + + //CardInfo contains the name and the X-DAV-UID list of the members + let vCardInfo = dav.tools.getGroupInfoFromCardData(syncData.foundMailingListsDuringDownSync[listID].vCardData, syncData.target); + let oCardInfo = dav.tools.getGroupInfoFromCardData(syncData.foundMailingListsDuringDownSync[listID].oCardData, syncData.target); + + // Smart merge: oCardInfo contains the state during last sync, vCardInfo is the current state. + // By comparing we can learn, which member was deleted by the server (in old but not in new). + let removedMembers = oCardInfo.members.filter(e => !vCardInfo.members.includes(e)); + + // The new list from the server is taken. + let newMembers = vCardInfo.members; + + // Any member in current but not in new is added. + for (let member of currentMembers) { + if (!newMembers.includes(member) && !removedMembers.includes(member)) + newMembers.push(member); + } + + // Remove local deletes. + for (let member of oCardInfo.members) { + if (!currentMembers.includes(member)) + newMembers = newMembers.filter(e => e != member); + } + + // Check that all new members have an email address (fix for bug 1522453) + let m=0; + for (let member of newMembers) { + let card = syncData.target.getItemFromProperty("X-DAV-UID", member); + if (card) { + let email = card.getProperty("PrimaryEmail"); + if (!email) { + let email = Date.now() + "." + l + "." + m + "@bug1522453"; + card.setProperty("PrimaryEmail", email); + syncData.target.modifyItem(card); + } + } else { + TbSync.dump("Member not found: " + member); + } + m++; + } + list.setMembersByPropertyList("X-DAV-UID", newMembers); + } + } + } + }, + + deleteContacts: async function (syncData, cards2delete) { + let maxitems = dav.sync.prefSettings.getIntPref("maxitems"); + + // try to show a progress based on maxitens during delete and not delete all at once + for (let i=0; i < cards2delete.length; i+=maxitems) { + //get size of next block + let remain = (cards2delete.length - i); + let chunk = Math.min(remain, maxitems); + + syncData.progressData.inc(chunk); + syncData.setSyncState("eval.response.remotechanges"); + await TbSync.tools.sleep(200); //we want the user to see, that deletes are happening + + for (let j=0; j < chunk; j++) { + syncData.target.deleteItem(cards2delete[i+j]); + } + } + }, + + + + + localChanges: async function (syncData) { + //define how many entries can be send in one request + let maxitems = dav.sync.prefSettings.getIntPref("maxitems"); + + let downloadonly = syncData.currentFolderData.getFolderProperty("downloadonly"); + + let permissionErrors = 0; + let permissionError = { //keep track of permission errors - preset with downloadonly status to skip sync in that case + "added_by_user": downloadonly, + "modified_by_user": downloadonly, + "deleted_by_user": downloadonly + }; + + let syncGroups = syncData.accountData.getAccountProperty("syncGroups"); + + //access changelog to get local modifications (done and todo are used for UI to display progress) + syncData.progressData.reset(0, syncData.target.getItemsFromChangeLog().length); + + do { + syncData.setSyncState("prepare.request.localchanges"); + + //get changed items from ChangeLog + let changes = syncData.target.getItemsFromChangeLog(maxitems); + if (changes.length == 0) + break; + + for (let i=0; i < changes.length; i++) { + switch (changes[i].status) { + case "added_by_user": + case "modified_by_user": + { + let isAdding = (changes[i].status == "added_by_user"); + if (!permissionError[changes[i].status]) { //if this operation failed already, do not retry + + let card = syncData.target.getItem(changes[i].itemId); + if (card) { + if (card.isMailList && !syncGroups) { + // Conditionally break out of the switch early, but do + // execute the cleanup code below the switch. A continue would + // miss that. + break; + } + + let vcard = card.isMailList + ? dav.tools.getVCardFromThunderbirdListCard(syncData, card, isAdding) + : dav.tools.getVCardFromThunderbirdContactCard(syncData, card, isAdding); + let headers = {"Content-Type": "text/vcard; charset=utf-8"}; + //if (!isAdding) headers["If-Match"] = vcard.etag; + + syncData.setSyncState("send.request.localchanges"); + if (isAdding || vcard.modified) { + let response = await dav.network.sendRequest(vcard.data, card.getProperty("X-DAV-HREF"), "PUT", syncData.connectionData, headers, {softfail: [403,405]}); + + syncData.setSyncState("eval.response.localchanges"); + if (response && response.softerror) { + permissionError[changes[i].status] = true; + TbSync.eventlog.add("warning", syncData.eventLogInfo, "missing-permission::" + TbSync.getString(isAdding ? "acl.add" : "acl.modify", "dav")); + } + } + } else { + TbSync.eventlog.add("warning", syncData.eventLogInfo, "cardnotfoundbutinchangelog::" + changes[i].itemId + "/" + changes[i].status); + } + } + + if (permissionError[changes[i].status]) { + //we where not allowed to add or modify that card, remove it, we will get a fresh copy on the following revert + let card = syncData.target.getItem(changes[i].itemId); + if (card) syncData.target.deleteItem(card); + permissionErrors++; + } + } + break; + + case "deleted_by_user": + { + if (!permissionError[changes[i].status]) { //if this operation failed already, do not retry + syncData.setSyncState("send.request.localchanges"); + let response = await dav.network.sendRequest("", changes[i].itemId , "DELETE", syncData.connectionData, {}, {softfail: [403, 404, 405]}); + + syncData.setSyncState("eval.response.localchanges"); + if (response && response.softerror) { + if (response.softerror != 404) { //we cannot do anything about a 404 on delete, the card has been deleted here and is not avail on server + permissionError[changes[i].status] = true; + TbSync.eventlog.add("warning", syncData.eventLogInfo, "missing-permission::" + TbSync.getString("acl.delete", "dav")); + } + } + } + + if (permissionError[changes[i].status]) { + permissionErrors++; + } + } + break; + } + + syncData.target.removeItemFromChangeLog(changes[i].itemId); + syncData.progressData.inc(); //UI feedback + } + + + } while (true); + + //return number of modified cards or the number of permission errors (negativ) + return (permissionErrors > 0 ? 0 - permissionErrors : syncData.progressData.done); + }, +} diff -Nru dav4tbsync-0.15/content/includes/tbSyncDavCalendar.js dav4tbsync-1.9/content/includes/tbSyncDavCalendar.js --- dav4tbsync-0.15/content/includes/tbSyncDavCalendar.js 1970-01-01 00:00:00.000000000 +0000 +++ dav4tbsync-1.9/content/includes/tbSyncDavCalendar.js 2020-02-20 12:39:38.000000000 +0000 @@ -0,0 +1,228 @@ +/* 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/. */ + +var { AddonManager } = ChromeUtils.import("resource://gre/modules/AddonManager.jsm"); +var { XPCOMUtils } = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm"); +var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); +var { cal } = ChromeUtils.import("resource://calendar/modules/calUtils.jsm"); + +Cu.importGlobalProperties(["TextDecoder"]); +Services.scriptloader.loadSubScript("resource://calendar/components/calDavCalendar.js", this); +Services.scriptloader.loadSubScript("resource://calendar/calendar-js/calDavRequestHandlers.js", this); + +// +// tbSyncDavCalendar.js +// + +function tbSyncDavCalendar() { + calDavCalendar.call(this); + this.tbSyncLoaded = false; +} + +tbSyncDavCalendar.prototype = { + __proto__: calDavCalendar.prototype, + classID: Components.ID('7eb8f992-3956-4607-95ac-b860ebd51f5a}'), + classDescription: 'tbSyncCalDav', + contractID: '@mozilla.org/calendar/calendar;1?type=tbSyncCalDav', + + + sleep: function(delay) { + let timer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer); + return new Promise(function(resolve, reject) { + let event = { + notify: function(timer) { + resolve(); + } + } + timer.initWithCallback(event, delay, Components.interfaces.nsITimer.TYPE_ONE_SHOT); + }); + }, + + + /** The following functions are almost copied 1-to-1 but needed little changes to work with tbSyncCalDav **/ + + getProperty: function(aName) { + if (aName in this.mACLProperties && this.mACLProperties[aName]) { + return this.mACLProperties[aName]; + } + + switch (aName) { + case "organizerId": + if (this.calendarUserAddress) { + return this.calendarUserAddress; + } // else use configured email identity + break; + case "organizerCN": + return null; // xxx todo + case "itip.transport": + if (this.hasAutoScheduling || this.hasScheduling) { + return this.QueryInterface(Ci.calIItipTransport); + } // else use outbound email-based iTIP (from cal.provider.BaseClass) + break; + case "capabilities.tasks.supported": + return this.supportedItemTypes.includes("VTODO"); + case "capabilities.events.supported": + return this.supportedItemTypes.includes("VEVENT"); + case "capabilities.autoschedule.supported": + return this.hasAutoScheduling; + case "capabilities.username.supported": + return true; + } + // We needed to add one more __proto__. + return this.__proto__.__proto__.__proto__.getProperty.apply(this, arguments); + }, + + get type() { + return "tbSyncCalDav"; + }, + + firstInRealm: function() { + let calendars = cal.getCalendarManager().getCalendars({}); + for (let i = 0; i < calendars.length; i++) { + if (calendars[i].type != "tbSyncCalDav" || calendars[i].getProperty("disabled")) { + continue; + } + // XXX We should probably expose the inner calendar via an + // interface, but for now use wrappedJSObject. + let calendar = calendars[i].wrappedJSObject; + if (calendar.mUncachedCalendar) { + calendar = calendar.mUncachedCalendar; + } + if (calendar.uri.prePath == this.uri.prePath && calendar.authRealm == this.mAuthRealm) { + if (calendar.id == this.id) { + return true; + } + break; + } + } + return false; + }, + + + /** Overriding lightning oauth **/ + + oauthConnect: function(authSuccessCb, authFailureCb, aRefresh = false) { + let self = this; + // 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. + // The value of aRefresh is being ignored. + if (self.oauth.accessToken) self.oauth.accessToken = ""; + + // Use the async prompter to avoid multiple master password prompts + let promptlistener = { + onPromptStartAsync: function(callback) { + this.onPromptAuthAvailable(callback); + }, + onPromptAuthAvailable: function(callback) { + // 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.oauth.connect( + () => { + authSuccessCb(); + if (callback) { + callback.onAuthResult(true); + } + }, + () => { + authFailureCb(); + if (callback) { + callback.onAuthResult(false); + } + }, + true, // with UI + false // refresh + ); + }, + onPromptCanceled: authFailureCb, + onPromptStart: function() {}, + }; + let asyncprompter = Cc["@mozilla.org/messenger/msgAsyncPrompter;1"].getService( + Ci.nsIMsgAsyncPrompter + ); + asyncprompter.queueAsyncAuthPrompt(self.uri.spec, false, promptlistener); + }, + + setupAuthentication: async function(aChangeLogListener) { + let self = this; + function authSuccess() { + self.checkDavResourceType(aChangeLogListener); + } + function authFailed() { + self.setProperty("disabled", "true"); + self.setProperty("auto-enabled", "true"); + self.completeCheckServerInfo(aChangeLogListener, Cr.NS_ERROR_FAILURE); + } + + // If TbSync is not installed, disable all calendars. + let tbSyncAddon = await AddonManager.getAddonByID("tbsync@jobisoft.de"); + if (!tbSyncAddon || !tbSyncAddon.isActive) { + console.log("Failed to load TbSync, GoogleDav calendar will be disabled."); + authFailed(); + return; + } + + // Wait until TbSync has been loaded + for (let waitCycles=0; waitCycles < 120 && !this.tbSyncLoaded; waitCycles++) { + await this.sleep(1000); + try { + var { TbSync } = ChromeUtils.import("chrome://tbsync/content/tbsync.jsm"); + this.tbSyncLoaded = TbSync.enabled; + } catch (e) { + // If this fails, TbSync is not loaded yet. + } + } + if (!this.tbSyncLoaded) { + console.log("Failed to load TbSync, GoogleDav calendar will be disabled."); + authFailed(); + return; + } + + // Wait until master password has been entered (if needed) + while (!Services.logins.isLoggedIn) { + await this.sleep(1000); + } + + + if (!this.oauth) { + let oauth = TbSync.providers.dav.network.getOAuthObj(this.mUri); + if (oauth) { + // This Server req OAUTH + this.oauth = oauth; + } else { + // This Server does not req OAUTH Server + authSuccess(); + return; + } + } + + if (this.oauth.accessToken) { + authSuccess(); + } else { + // bug 901329: If the calendar window isn't loaded yet the + // master password prompt will show just the buttons and + // possibly hang. If we postpone until the window is loaded, + // all is well. + setTimeout(function postpone() { + // eslint-disable-line func-names + let win = cal.window.getCalendarWindow(); + if (!win || win.document.readyState != "complete") { + setTimeout(postpone, 0); + } else { + self.oauthConnect(authSuccess, authFailed); + } + }, 0); + } + }, +} + + +/** Module Registration */ +this.NSGetFactory = cid => { + this.NSGetFactory = XPCOMUtils.generateNSGetFactory([tbSyncDavCalendar]); + return this.NSGetFactory(cid); +}; diff -Nru dav4tbsync-0.15/content/includes/tools.js dav4tbsync-1.9/content/includes/tools.js --- dav4tbsync-0.15/content/includes/tools.js 1970-01-01 00:00:00.000000000 +0000 +++ dav4tbsync-1.9/content/includes/tools.js 2020-02-20 12:39:38.000000000 +0000 @@ -0,0 +1,1295 @@ +/* + * This file is part of DAV-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 = { + + getEmailsFromCard: function (aCard) { //return array of objects {meta, value} + let emailData = []; + try { + emailData = JSON.parse(aCard.getProperty("X-DAV-JSON-Emails","[]").trim()); + } catch (e) { + //Components.utils.reportError(e); + } + // always use the core email field values, they could have been mod outside by the user, + // not knowing that we store our stuff in X-DAV-JSON-Emails + let emailFields = ["PrimaryEmail", "SecondEmail"]; + for (let i = 0; i < emailFields.length; i++) { + let email = aCard.getProperty(emailFields[i],"").trim(); + if (email) { + if (emailData.length > i) emailData[i].value = email; + else emailData.push({value: email, meta: []}); + } + } + + return emailData; + }, + + getPhoneNumbersFromCard: function (aCard) { //return array of objects {meta, value} + let phones = aCard.getProperty("X-DAV-JSON-Phones","").trim(); + try { + if (phones) { + return JSON.parse(phones); + } + } catch (e) { + //Components.utils.reportError(e); + } + + phones = []; + + + //So this card is not a "DAV" card: Get the phone numbers from current numbers stored in + //CellularNumber, FaxNumber, PagerNumber, WorkPhone, HomePhone"}, + let todo = [ + {field: "CellularNumber", meta: ["CELL"]}, + {field: "FaxNumber", meta: ["FAX"]}, + {field: "PagerNumber", meta: ["PAGER"]}, + {field: "WorkPhone", meta: ["WORK"]}, + {field: "HomePhone", meta: ["HOME"]} + ]; + + for (let data of todo) { + let phone = aCard.getProperty(data.field,"").trim(); + if (phone) { + phones.push({value: phone, meta: data.meta}); + } + } + return phones; + }, + + + + + + //* * * * * * * * * * * * * + //* UTILS + //* * * * * * * * * * * * * + + /** + * Convert a byte array to a string - copied from lightning + * + * @param {octet[]} aResult The bytes to convert + * @param {String} aCharset The character set of the bytes, defaults to utf-8 + * @param {Boolean} aThrow If true, the function will raise an exception on error + * @returns {?String} The string result, or null on error + */ + convertByteArray: function(aResult, aCharset="utf-8", aThrow) { + try { + return new TextDecoder(aCharset).decode(Uint8Array.from(aResult)); + } catch (e) { + if (aThrow) { + throw e; + } + } + return null; + }, + + /** + * Removes XML-invalid characters from a string. + * @param {string} string - a string potentially containing XML-invalid characters, such as non-UTF8 characters, STX, EOX and so on. + * @param {boolean} removeDiscouragedChars - a string potentially containing XML-invalid characters, such as non-UTF8 characters, STX, EOX and so on. + * @returns : a sanitized string without all the XML-invalid characters. + * + * Source: https://www.ryadel.com/en/javascript-remove-xml-invalid-chars-characters-string-utf8-unicode-regex/ + */ + removeXMLInvalidChars: function (string, removeDiscouragedChars = true) + { + // remove everything forbidden by XML 1.0 specifications, plus the unicode replacement character U+FFFD + var regex = /((?:[\0-\x08\x0B\f\x0E-\x1F\uFFFD\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]))/g; + string = string.replace(regex, ""); + + if (removeDiscouragedChars) { + // remove everything not suggested by XML 1.0 specifications + regex = new RegExp( + "([\\x7F-\\x84]|[\\x86-\\x9F]|[\\uFDD0-\\uFDEF]|(?:\\uD83F[\\uDFFE\\uDFFF])|(?:\\uD87F[\\uDF"+ + "FE\\uDFFF])|(?:\\uD8BF[\\uDFFE\\uDFFF])|(?:\\uD8FF[\\uDFFE\\uDFFF])|(?:\\uD93F[\\uDFFE\\uD"+ + "FFF])|(?:\\uD97F[\\uDFFE\\uDFFF])|(?:\\uD9BF[\\uDFFE\\uDFFF])|(?:\\uD9FF[\\uDFFE\\uDFFF])"+ + "|(?:\\uDA3F[\\uDFFE\\uDFFF])|(?:\\uDA7F[\\uDFFE\\uDFFF])|(?:\\uDABF[\\uDFFE\\uDFFF])|(?:\\"+ + "uDAFF[\\uDFFE\\uDFFF])|(?:\\uDB3F[\\uDFFE\\uDFFF])|(?:\\uDB7F[\\uDFFE\\uDFFF])|(?:\\uDBBF"+ + "[\\uDFFE\\uDFFF])|(?:\\uDBFF[\\uDFFE\\uDFFF])(?:[\\0-\\t\\x0B\\f\\x0E-\\u2027\\u202A-\\uD7FF\\"+ + "uE000-\\uFFFF]|[\\uD800-\\uDBFF][\\uDC00-\\uDFFF]|[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|"+ + "(?:[^\\uD800-\\uDBFF]|^)[\\uDC00-\\uDFFF]))", "g"); + string = string.replace(regex, ""); + } + + return string; + }, + + xmlns: function (ns) { + let _xmlns = []; + for (let i=0; i < ns.length; i++) { + _xmlns.push('xmlns:'+ns[i]+'="'+dav.sync.ns[ns[i]]+'"'); + } + return _xmlns.join(" "); + }, + + parseUri: function (aUri) { + let uri; + try { + // Test if the entered uri can be parsed. + uri = Services.io.newURI(aUri, null, null); + } catch (ex) { + throw new Error("invalid-calendar-url"); + } + return uri; + }, + + parseVcardDateTime: function ( newServerValue, metadata ) { + if (!newServerValue) { + return false; + } + + /* + ** This accepts RFC2426 BDAY values (with/without hyphens), + ** though TB doesn't handle the time part of date-times, so we discard it. + */ + let bday = newServerValue.match( /^(\d{4})-?(\d{2})-?(\d{2})/ ); + if (!bday) { + return false; + } + + /* + ** Apple Contacts shoehorns date with missing year into vcard3 thus: BDAY;X-APPLE-OMIT-YEAR=1604:1604-03-15 + ** Later in vcard4, it will be represented as BDAY:--0315 + */ + if (metadata + && metadata['x-apple-omit-year'] + && metadata['x-apple-omit-year'] == bday[1]) { + bday[1] = ''; + } + return bday; + }, + + + + + getEmailsFromJSON: function (emailDataJSON) { + let emailFields = {}; + + if (emailDataJSON) { + try { + // We pack the first entry into PrimaryEmail and the second one into SecondEmail. + // For compatibility with the Phones, we return arrays, even though we only return + // one element per array. + let emailData = JSON.parse(emailDataJSON); + emailFields = {PrimaryEmail:[], SecondEmail:[]}; + + for (let d=0; d < emailData.length && d < 2; d++) { + let field = (d==0) ? "PrimaryEmail" : "SecondEmail"; + emailFields[field].push(emailData[d].value); + } + } catch(e) { + //something went wrong + Components.utils.reportError(e); + } + } + + //object with TB field names as keys and array of emails as values + return emailFields; + }, + + + getPhoneNumbersFromJSON: function (phoneDataJSON) { + let phoneFields = {}; + + if (phoneDataJSON) { + try { + //we first search and remove CELL, FAX, PAGER and WORK from the list and put the remains into HOME + let phoneData = JSON.parse(phoneDataJSON); + let phoneMap = [ + {meta: "CELL", field: "CellularNumber"}, + {meta: "FAX", field: "FaxNumber"}, + {meta: "PAGER", field: "PagerNumber"}, + {meta: "WORK", field: "WorkPhone"}, + {meta: "", field: "HomePhone"}, + ]; + + for (let m=0; m < phoneMap.length; m++) { + phoneFields[phoneMap[m].field] = []; + for (let d=phoneData.length-1; d >= 0; d--) { + if (phoneData[d].meta.includes(phoneMap[m].meta) || phoneMap[m].meta == "") { + phoneFields[phoneMap[m].field].unshift(phoneData[d].value); + phoneData.splice(d,1); + } + } + } + } catch(e) { + //something went wrong + Components.utils.reportError(e); + } + } + + //object with TB field names as keys and array of numbers as values + return phoneFields; + }, + + + //* * * * * * * * * * * * * * + //* EVALUATE XML RESPONSES * + //* * * * * * * * * * * * * * + + convertToXML: function(text) { + //try to convert response body to xml + let xml = null; + let oParser = new DOMParser(); + try { + xml = oParser.parseFromString(dav.tools.removeXMLInvalidChars(text), "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 + xml = null; + } + //check if xml is error document + if (xml && xml.documentElement.nodeName == "parsererror") { + xml = null; + } + + return xml; + }, + + evaluateNode: function (_node, path) { + let node = _node; + let valid = false; + + for (let i=0; i < path.length; i++) { + + let children = node.children; + valid = false; + + for (let c=0; c < children.length; c++) { + if (children[c].localName == path[i][1] && children[c].namespaceURI == dav.sync.ns[path[i][0]]) { + node = children[c]; + valid = true; + break; + } + } + + if (!valid) { + //none of the children matched the path abort + return null; + } + } + + if (valid) return node; + return null; + }, + + hrefMatch:function (_requestHref, _responseHref) { + if (_requestHref === null) + return true; + + let requestHref = _requestHref; + let responseHref = _responseHref; + while (requestHref.endsWith("/")) { requestHref = requestHref.slice(0,-1); } + while (responseHref.endsWith("/")) { responseHref = responseHref.slice(0,-1); } + if (requestHref.endsWith(responseHref) || decodeURIComponent(requestHref).endsWith(responseHref) || requestHref.endsWith(decodeURIComponent(responseHref))) + return true; + + return false; + }, + + getNodeTextContentFromMultiResponse: function (response, path, href = null, status = ["200"]) { + for (let i=0; i < response.multi.length; i++) { + let node = dav.tools.evaluateNode(response.multi[i].node, path); + if (node !== null && dav.tools.hrefMatch(href, response.multi[i].href) && status.includes(response.multi[i].status)) { + return node.textContent; + } + } + return null; + }, + + getNodesTextContentFromMultiResponse: function (response, path, href = null, status = "200") { + //remove last element from path + let lastPathElement = path.pop(); + let rv = []; + + for (let i=0; i < response.multi.length; i++) { + let node = dav.tools.evaluateNode(response.multi[i].node, path); + if (node !== null && dav.tools.hrefMatch(href, response.multi[i].href) && response.multi[i].status == status) { + + //get all children + let children = node.getElementsByTagNameNS(dav.sync.ns[lastPathElement[0]], lastPathElement[1]); + for (let c=0; c < children.length; c++) { + if (children[c].textContent) rv.push(children[c].textContent); + } + } + } + return rv; + }, + + getMultiGetRequest: function(hrefs) { + let request = ""; + let counts = 0; + for (let i=0; i < hrefs.length; i++) { + request += ""+hrefs[i]+""; + counts++; + } + request += ""; + + if (counts > 0) return request; + else return null; + }, + + + + + + //* * * * * * * * * * * + //* CARDS OPERATIONS * + //* * * * * * * * * * * + + addContact: async function(syncData, id, data, etag) { + let vCard = data.textContent.trim(); + let vCardData = dav.vCard.parse(vCard); + + //check if contact or mailinglist + if (!dav.tools.vCardIsMailingList (syncData, id, null, vCard, vCardData, etag)) { + //prepare new contact card + let card = syncData.target.createNewCard(); + card.setProperty("X-DAV-HREF", id); + card.setProperty("X-DAV-ETAG", etag.textContent); + card.setProperty("X-DAV-VCARD", vCard); + + await dav.tools.setThunderbirdCardFromVCard(syncData, card, vCardData); + syncData.target.addItem(card); + } + }, + + modifyContact: async function(syncData, id, data, etag) { + let vCard = data.textContent.trim(); + let vCardData = dav.vCard.parse(vCard); + + //get card + let card = syncData.target.getItemFromProperty("X-DAV-HREF", id); + if (card) { + //check if contact or mailinglist to update card + if (!dav.tools.vCardIsMailingList (syncData, id, card, vCard, vCardData, etag)) { + //get original vCard data as stored by last update from server + let oCard = card.getProperty("X-DAV-VCARD"); + let oCardData = oCard ? dav.vCard.parse(oCard) : null; + + card.setProperty("X-DAV-ETAG", etag.textContent); + card.setProperty("X-DAV-VCARD", vCard); + + await dav.tools.setThunderbirdCardFromVCard(syncData, card, vCardData, oCardData); + syncData.target.modifyItem(card); + } + + } else { + //card does not exists, create it? + } + }, + + + + + //check if vCard is a mailinglist and handle it + vCardIsMailingList: function (syncData, id, _list, vCard, vCardData, etag) { + if (vCardData.hasOwnProperty("X-ADDRESSBOOKSERVER-KIND") && vCardData["X-ADDRESSBOOKSERVER-KIND"][0].value == "group") { + if (!syncData.accountData.getAccountProperty("syncGroups")) { + //user did not enable group sync, so do nothing, but return true so this card does not get added as a real card + return true; + } + + let vCardInfo = dav.tools.getGroupInfoFromCardData(vCardData, syncData.target, false); //just get the name, not the members + + //if no card provided, create a new one + let list = _list; + if (!list) { + list = syncData.target.createNewList(); + list.setProperty("X-DAV-HREF", id); + list.setProperty("X-DAV-UID", vCardInfo.uid); + list.setProperty("ListName", vCardInfo.name); + syncData.target.addItem(list); + } else { + list.setProperty("ListName", vCardInfo.name); + syncData.target.modifyItem(list); + } + + //get original vCardData from last server contact, needed for "smart merge" on changes on both sides + let oCardData = dav.vCard.parse(list.getProperty("X-DAV-VCARD")); + //store all old and new vCards for later processing (cannot do it here, because it is not guaranteed, that all members exists already) + syncData.foundMailingListsDuringDownSync[id] = {oCardData, vCardData}; + + //update properties + list.setProperty("X-DAV-ETAG", etag.textContent); + list.setProperty("X-DAV-VCARD", vCard); + + // AbItem implementation: Custom properties of lists are updated instantly, no need to call target.modifyItem(list); + return true; + + } else { + return false; + } + }, + + + + + + + //* * * * * * * * * * * + //* ACTUAL SYNC MAGIC * + //* * * * * * * * * * * + + //helper function: extract the associated meta.type of an entry + getItemMetaType: function (vCardData, item, i, typefield) { + if (vCardData[item][i].meta && vCardData[item][i].meta[typefield] && vCardData[item][i].meta[typefield].length > 0) { + //vCard parser now spilts up meta types into single array values + //TYPE="home,cell" and TYPE=home;Type=cell will be received as ["home", "cell"] + return vCardData[item][i].meta[typefield]; + } + return []; + }, + + //helper function: for each entry for the given item, extract the associated meta.type + getMetaTypeData: function (vCardData, item, typefield) { + let metaTypeData = []; + for (let i=0; i < vCardData[item].length; i++) { + metaTypeData.push( dav.tools.getItemMetaType(vCardData, item, i, typefield) ); + } + return metaTypeData; + }, + + fixArrayValue: function (vCardData, vCardField, index) { + if (!Array.isArray(vCardData[vCardField.item][vCardField.entry].value)) { + let v = vCardData[vCardField.item][vCardField.entry].value; + vCardData[vCardField.item][vCardField.entry].value = [v]; + } + while (vCardData[vCardField.item][vCardField.entry].value.length < index) vCardData[vCardField.item][vCardField.entry].value.push(""); + }, + + getSaveArrayValue: function (vCardValue, index) { + if (Array.isArray(vCardValue)) { + if(vCardValue.length > index) return vCardValue[index]; + else return ""; + } else if (index == 0) return vCardValue; + else return ""; + }, + + supportedProperties: [ + {name: "DisplayName", minversion: "0.4"}, + {name: "FirstName", minversion: "0.4"}, + {name: "X-DAV-PrefixName", minversion: "0.12.13"}, + {name: "X-DAV-MiddleName", minversion: "0.8.8"}, + {name: "X-DAV-SuffixName", minversion: "0.12.13"}, + {name: "X-DAV-UID", minversion: "0.10.36"}, + {name: "X-DAV-JSON-Phones", minversion: "0.4"}, + {name: "X-DAV-JSON-Emails", minversion: "0.4"}, + {name: "LastName", minversion: "0.4"}, + {name: "NickName", minversion: "0.4"}, + {name: "Birthday", minversion: "0.4"}, //fake, will trigger special handling + {name: "Photo", minversion: "0.4"}, //fake, will trigger special handling + {name: "HomeCity", minversion: "0.4"}, + {name: "HomeCountry", minversion: "0.4"}, + {name: "HomeZipCode", minversion: "0.4"}, + {name: "HomeState", minversion: "0.4"}, + {name: "HomeAddress", minversion: "0.4"}, + {name: "HomeAddress2", minversion: "1.4.1"}, + {name: "WorkCity", minversion: "0.4"}, + {name: "WorkCountry", minversion: "0.4"}, + {name: "WorkZipCode", minversion: "0.4"}, + {name: "WorkState", minversion: "0.4"}, + {name: "WorkAddress", minversion: "0.4"}, + {name: "WorkAddress2", minversion: "1.4.1"}, + {name: "Categories", minversion: "0.4"}, + {name: "JobTitle", minversion: "0.4"}, + {name: "Department", minversion: "0.4"}, + {name: "Company", minversion: "0.4"}, + {name: "WebPage1", minversion: "0.4"}, + {name: "WebPage2", minversion: "0.4"}, + {name: "Notes", minversion: "0.4"}, + {name: "PreferMailFormat", minversion: "0.4"}, + {name: "Custom1", minversion: "0.4"}, + {name: "Custom2", minversion: "0.4"}, + {name: "Custom3", minversion: "0.4"}, + {name: "Custom4", minversion: "0.4"}, + {name: "_GoogleTalk", minversion: "0.4"}, + {name: "_JabberId", minversion: "0.4"}, + {name: "_Yahoo", minversion: "0.4"}, + {name: "_QQ", minversion: "0.4"}, + {name: "_AimScreenName", minversion: "0.4"}, + {name: "_MSN", minversion: "0.4"}, + {name: "_Skype", minversion: "0.4"}, + {name: "_ICQ", minversion: "0.4"}, + {name: "_IRC", minversion: "0.4"}, + ], + + //map thunderbird fields to simple vcard fields without additional types + simpleMap : { + "X-DAV-UID" : "uid", + "Birthday" : "bday", //fake + "Photo" : "photo", //fake + "JobTitle" : "title", + "Department" : "org", + "Company" : "org", + "DisplayName" : "fn", + "NickName" : "nickname", + "Categories" : "categories", + "Notes" : "note", + "FirstName" : "n", + "X-DAV-PrefixName" : "n", + "X-DAV-MiddleName" : "n", + "X-DAV-SuffixName" : "n", + "LastName" : "n", + "PreferMailFormat" : "X-MOZILLA-HTML", + "Custom1" : "X-MOZILLA-CUSTOM1", + "Custom2" : "X-MOZILLA-CUSTOM2", + "Custom3" : "X-MOZILLA-CUSTOM3", + "Custom4" : "X-MOZILLA-CUSTOM4", + }, + + //map thunderbird fields to vcard fields with additional types + complexMap : { + "WebPage1" : {item: "url", type: "WORK"}, + "WebPage2" : {item: "url", type: "HOME"}, + + "HomeCity" : {item: "adr", type: "HOME"}, + "HomeCountry" : {item: "adr", type: "HOME"}, + "HomeZipCode" : {item: "adr", type: "HOME"}, + "HomeState" : {item: "adr", type: "HOME"}, + "HomeAddress" : {item: "adr", type: "HOME"}, + "HomeAddress2" : {item: "adr", type: "HOME"}, + + "WorkCity" : {item: "adr", type: "WORK"}, + "WorkCountry" : {item: "adr", type: "WORK"}, + "WorkZipCode" : {item: "adr", type: "WORK"}, + "WorkState" : {item: "adr", type: "WORK"}, + "WorkAddress" : {item: "adr", type: "WORK"}, + "WorkAddress2" : {item: "adr", type: "WORK"}, + }, + + //map thunderbird fields to impp vcard fields with additional x-service-types + imppMap : { + "_GoogleTalk" : {item: "impp" , prefix: "xmpp:", type: "GOOGLETALK"}, //actually x-service-type + "_JabberId" : {item: "impp", prefix: "xmpp:", type: "JABBER"}, + "_Yahoo" : {item: "impp", prefix: "ymsgr:", type: "YAHOO"}, + "_QQ" : {item: "impp", prefix: "x-apple:", type: "QQ"}, + "_AimScreenName" : {item: "impp", prefix: "aim:", type: "AIM"}, + "_MSN" : {item: "impp", prefix: "msnim:", type: "MSN"}, + "_Skype" : {item: "impp", prefix: "skype:", type: "SKYPE"}, + "_ICQ" : {item: "impp", prefix: "aim:", type: "ICQ"}, + "_IRC" : {item: "impp", prefix: "irc:", type: "IRC"}, + }, + + + + + + //For a given Thunderbird property, identify the vCard field + // -> which main item + // -> which array element (based on metatype, if needed) + //https://tools.ietf.org/html/rfc2426#section-3.6.1 + getVCardField: function (syncData, property, vCardData) { + let data = {item: "", metatype: [], metatypefield: "type", entry: -1, prefix: ""}; + + if (vCardData) { + + //handle special cases independently, those from *Map will be handled by default + switch (property) { + case "X-DAV-JSON-Emails": + { + data.metatype.push("OTHER"); //default for new entries + data.item = "email"; + + if (vCardData[data.item] && vCardData[data.item].length > 0) { + //NOOP, just return something, if present + data.entry = 0; + } + } + break; + + case "X-DAV-JSON-Phones": + { + data.metatype.push("VOICE"); //default for new entries + data.item = "tel"; + + if (vCardData[data.item] && vCardData[data.item].length > 0) { + //NOOP, just return something, if present + data.entry = 0; + } + } + break; + + default: + //Check *Maps + if (dav.tools.simpleMap.hasOwnProperty(property)) { + + data.item = dav.tools.simpleMap[property]; + if (vCardData[data.item] && vCardData[data.item].length > 0) data.entry = 0; + + } else if (dav.tools.imppMap.hasOwnProperty(property)) { + + let type = dav.tools.imppMap[property].type; + data.metatype.push(type); + data.item = dav.tools.imppMap[property].item; + data.prefix = dav.tools.imppMap[property].prefix; + data.metatypefield = "x-service-type"; + + if (vCardData[data.item]) { + let metaTypeData = dav.tools.getMetaTypeData(vCardData, data.item, data.metatypefield); + + let valids = []; + for (let i=0; i < metaTypeData.length; i++) { + if (metaTypeData[i].includes(type)) valids.push(i); + } + if (valids.length > 0) data.entry = valids[0]; + } + + } else if (dav.tools.complexMap.hasOwnProperty(property)) { + + let type = dav.tools.complexMap[property].type; + let invalidTypes = (dav.tools.complexMap[property].invalidTypes) ? dav.tools.complexMap[property].invalidTypes : []; + data.metatype.push(type); + data.item = dav.tools.complexMap[property].item; + + if (vCardData[data.item]) { + let metaTypeData = dav.tools.getMetaTypeData(vCardData, data.item, data.metatypefield); + let valids = []; + for (let i=0; i < metaTypeData.length; i++) { + //check if this includes the requested type and also none of the invalid types + if (metaTypeData[i].includes(type) && metaTypeData[i].filter(value => -1 !== invalidTypes.indexOf(value)).length == 0) valids.push(i); + } + if (valids.length > 0) data.entry = valids[0]; + } + + } else throw "Unknown TB property <"+property+">"; + } + } + return data; + }, + + + + + + //turn the given vCardValue into a string to be stored as a Thunderbird property + getThunderbirdPropertyValueFromVCard: function (syncData, property, vCardData, vCardField) { + let vCardValue = (vCardData && + vCardField && + vCardField.entry != -1 && + vCardData[vCardField.item] && + vCardData[vCardField.item][vCardField.entry] && + vCardData[vCardField.item][vCardField.entry].value) ? vCardData[vCardField.item][vCardField.entry].value : null; + + if (vCardValue === null) { + return null; + } + + //handle all special fields, which are not plain strings + switch (property) { + case "HomeCity": + case "HomeCountry": + case "HomeZipCode": + case "HomeState": + case "HomeAddress": + case "HomeAddress2": + case "WorkCity": + case "WorkCountry": + case "WorkZipCode": + case "WorkState": + case "WorkAddress": + case "WorkAddress2": + { + let field = property.substring(4); + let adr = (Services.vc.compare("0.8.11", syncData.currentFolderData.getFolderProperty("createdWithProviderVersion")) > 0) + ? ["OfficeBox","Address2","Address","City","Country","ZipCode", "State"] //WRONG + : ["OfficeBox","Address2","Address","City","State","ZipCode", "Country"]; //RIGHT, fixed in 0.8.11 + + let index = adr.indexOf(field); + return dav.tools.getSaveArrayValue(vCardValue, index); + } + break; + + case "FirstName": + case "LastName": + case "X-DAV-PrefixName": + case "X-DAV-MiddleName": + case "X-DAV-SuffixName": + { + let index = ["LastName","FirstName","X-DAV-MiddleName","X-DAV-PrefixName","X-DAV-SuffixName"].indexOf(property); + return dav.tools.getSaveArrayValue(vCardValue, index); + } + break; + + case "Department": + case "Company": + { + let index = ["Company","Department"].indexOf(property); + return dav.tools.getSaveArrayValue(vCardValue, index); + } + break; + + case "Categories": + return (Array.isArray(vCardValue) ? vCardValue.join("\u001A") : vCardValue); + break; + + case "PreferMailFormat": + if (vCardValue.toLowerCase() == "true") return 2; + if (vCardValue.toLowerCase() == "false") return 1; + return 0; + break; + + case "X-DAV-JSON-Phones": + case "X-DAV-JSON-Emails": + { + //this is special, we need to return the full JSON object + let entries = []; + let metaTypeData = dav.tools.getMetaTypeData(vCardData, vCardField.item, vCardField.metatypefield); + for (let i=0; i < metaTypeData.length; i++) { + let entry = {}; + entry.meta = metaTypeData[i]; + entry.value = vCardData[vCardField.item][i].value; + entries.push(entry); + } + return JSON.stringify(entries); + } + break; + + default: + { + //should be a single string + let v = (Array.isArray(vCardValue)) ? vCardValue.join(" ") : vCardValue; + if (vCardField.prefix.length > 0 && v.startsWith(vCardField.prefix)) return v.substring(vCardField.prefix.length); + else return v; + } + } + }, + + + + + + //add/update the given Thunderbird propeties value in vCardData obj + updateValueOfVCard: function (syncData, property, vCardData, vCardField, value) { + let add = false; + let store = value ? true : false; + let remove = (!store && vCardData[vCardField.item] && vCardField.entry != -1); + + //preperations if this item does not exist + if (store && vCardField.entry == -1) { + //entry does not exists, does the item exists? + if (!vCardData[vCardField.item]) vCardData[vCardField.item] = []; + let newItem = {}; + if (vCardField.metatype.length > 0) { + newItem["meta"] = {}; + newItem["meta"][vCardField.metatypefield] = vCardField.metatype; + } + vCardField.entry = vCardData[vCardField.item].push(newItem) - 1; + add = true; + } + + //handle all special fields, which are not plain strings + switch (property) { + case "HomeCity": + case "HomeCountry": + case "HomeZipCode": + case "HomeState": + case "HomeAddress": + case "HomeAddress2": + case "WorkCity": + case "WorkCountry": + case "WorkZipCode": + case "WorkState": + case "WorkAddress": + case "WorkAddress2": + { + let field = property.substring(4); + let adr = (Services.vc.compare("0.8.11", syncData.currentFolderData.getFolderProperty("createdWithProviderVersion")) > 0) + ? ["OfficeBox","Address2","Address","City","Country","ZipCode", "State"] //WRONG + : ["OfficeBox","Address2","Address","City","State","ZipCode", "Country"]; //RIGHT, fixed in 0.8.11 + + let index = adr.indexOf(field); + if (store) { + if (add) vCardData[vCardField.item][vCardField.entry].value = ["","","","","","",""]; + + dav.tools.fixArrayValue(vCardData, vCardField, index); + vCardData[vCardField.item][vCardField.entry].value[index] = value; + } else if (remove) { + dav.tools.fixArrayValue(vCardData, vCardField, index); + vCardData[vCardField.item][vCardField.entry].value[index] = ""; //Will be completly removed by the parser, if all fields are empty! + } + } + break; + + case "FirstName": + case "X-DAV-PrefixName": + case "X-DAV-MiddleName": + case "X-DAV-SuffixName": + case "LastName": + { + let index = ["LastName","FirstName","X-DAV-MiddleName","X-DAV-PrefixName","X-DAV-SuffixName"].indexOf(property); + if (store) { + if (add) vCardData[vCardField.item][vCardField.entry].value = ["","","","",""]; + + dav.tools.fixArrayValue(vCardData, vCardField, index); + vCardData[vCardField.item][vCardField.entry].value[index] = value; + } else if (remove) { + dav.tools.fixArrayValue(vCardData, vCardField, index); + vCardData[vCardField.item][vCardField.entry].value[index] = ""; //Will be completly removed by the parser, if all fields are empty! + } + } + break; + + case "Department": + case "Company": + { + let index = ["Company","Department"].indexOf(property); + if (store) { + if (add) vCardData[vCardField.item][vCardField.entry].value = ["",""]; + + dav.tools.fixArrayValue(vCardData, vCardField, index); + vCardData[vCardField.item][vCardField.entry].value[index] = value; + } else if (remove && vCardData[vCardField.item][vCardField.entry].value.length > index) { + dav.tools.fixArrayValue(vCardData, vCardField, index); + vCardData[vCardField.item][vCardField.entry].value[index] = ""; //Will be completly removed by the parser, if all fields are empty! + } + } + break; + + case "Categories": + if (store) vCardData[vCardField.item][vCardField.entry].value = value.split("\u001A"); + else if (remove) vCardData[vCardField.item][vCardField.entry].value = []; + break; + + case "PreferMailFormat": + { + if (store) { + let v = (value == 2) ? "TRUE" : (value == 1) ? "FALSE" : ""; + vCardData[vCardField.item][vCardField.entry].value = v; + } else if (remove) vCardData[vCardField.item][vCardField.entry].value = ""; + } + break; + + case "Emails": //also update meta + case "Phones": //also update meta + if (store) { + vCardData[vCardField.item][vCardField.entry].value = vCardField.prefix + value; + if (!vCardData[vCardField.item][vCardField.entry].hasOwnProperty("meta")) { + vCardData[vCardField.item][vCardField.entry].meta = {}; + } + vCardData[vCardField.item][vCardField.entry].meta[vCardField.metatypefield] = vCardField.metatype; + } else if (remove) vCardData[vCardField.item][vCardField.entry].value = ""; + break; + + default: //should be a string + if (store) vCardData[vCardField.item][vCardField.entry].value = vCardField.prefix + value; + else if (remove) vCardData[vCardField.item][vCardField.entry].value = ""; + } + }, + + + + + //MAIN FUNCTIONS FOR UP/DOWN SYNC + + //update send from server to client + setThunderbirdCardFromVCard: async function(syncData, card, vCardData, oCardData = null) { + if (TbSync.prefs.getIntPref("log.userdatalevel") > 2) TbSync.dump("JSON from vCard", JSON.stringify(vCardData)); + //if (oCardData) TbSync.dump("JSON from oCard", JSON.stringify(oCardData)); + + for (let f=0; f < dav.tools.supportedProperties.length; f++) { + //Skip sync fields that have been added after this folder was created (otherwise we would delete them) + if (Services.vc.compare(dav.tools.supportedProperties[f].minversion, syncData.currentFolderData.getFolderProperty("createdWithProviderVersion"))> 0) continue; + + let property = dav.tools.supportedProperties[f].name; + let vCardField = dav.tools.getVCardField(syncData, property, vCardData); + let newServerValue = dav.tools.getThunderbirdPropertyValueFromVCard(syncData, property, vCardData, vCardField); + + let oCardField = dav.tools.getVCardField(syncData, property, oCardData); + let oldServerValue = dav.tools.getThunderbirdPropertyValueFromVCard(syncData, property, oCardData, oCardField); + + //smart merge: only update the property, if it has changed on the server (keep local modifications) + if (newServerValue !== oldServerValue) { + //some "properties" need special handling + switch (property) { + case "Photo": + { + if (newServerValue) { + let type = ""; + try { + type = vCardData[vCardField.item][0].meta.type[0].toLowerCase(); + } catch (e) { + Components.utils.reportError(e); + } + + // check for inline data or linked data + if (vCardData[vCardField.item][0].meta && vCardData[vCardField.item][0].meta.encoding) { + + let ext = type || "jpg"; + let data = vCardData[vCardField.item][0].value; + card.addPhoto(TbSync.generateUUID(), data, ext); + } else if (vCardData[vCardField.item][0].meta && Array.isArray(vCardData[vCardField.item][0].meta.value) && vCardData[vCardField.item][0].meta.value[0].toString().toLowerCase() == "uri") { + let connectionData = new dav.network.ConnectionData(); + connectionData.eventLogInfo = syncData.connectionData.eventLogInfo; + // add credentials, if image is on the account server, go anonymous otherwise + try { + if (vCardData[vCardField.item][0].value.split("://").pop().startsWith(syncData.connectionData.fqdn)) { + connectionData.password = syncData.connectionData.password; + connectionData.username = syncData.connectionData.username; + } + + let ext = type || this.getImageExtension(vCardData[vCardField.item][0].value); + let data = await dav.network.sendRequest("", vCardData[vCardField.item][0].value , "GET", connectionData, {}, {responseType: "base64"}); + card.addPhoto(TbSync.generateUUID(), data, ext, vCardData[vCardField.item][0].value); + } catch(e) { + Components.utils.reportError(e); + TbSync.eventlog.add("warning", syncData.eventLogInfo,"Could not extract externally linked photo from vCard", JSON.stringify(vCardData)); + } + } + } else { + //clear + card.deleteProperty("PhotoName"); + card.deleteProperty("PhotoType"); + card.deleteProperty("PhotoURI"); + } + } + break; + + case "Birthday": + { + if ( newServerValue ) { + let bday = dav.tools.parseVcardDateTime( newServerValue, vCardData[vCardField.item][0].meta ); + card.setProperty("BirthYear", bday[1]); + card.setProperty("BirthMonth", bday[2]); + card.setProperty("BirthDay", bday[3]); + } else { + card.deleteProperty("BirthYear"); + card.deleteProperty("BirthMonth"); + card.deleteProperty("BirthDay"); + } + } + break; + + case "X-DAV-JSON-Emails": + case "X-DAV-JSON-Phones": + { + //This field contains all the JSON encoded values and TbSync has its own UI to display them. + //However, we also want to fill the standard TB fields. + let tbData; + switch (property) { + case "X-DAV-JSON-Emails" : + tbData = dav.tools.getEmailsFromJSON(newServerValue); + break; + case "X-DAV-JSON-Phones" : + tbData = dav.tools.getPhoneNumbersFromJSON(newServerValue); + break; + } + + for (let field in tbData) { + if (tbData.hasOwnProperty(field)) { + //set or delete TB Property + if ( tbData[field].length > 0 ) { + card.setProperty(field, tbData[field].join(", ")); + } else { + card.deleteProperty(field); + } + } + } + } + + default: + { + if (newServerValue) { + //set + card.setProperty(property, newServerValue); + } else { + //clear (del if possible) + card.setProperty(property, ""); + try { + card.deleteProperty(property); + } catch (e) {} + } + } + break; + } + } + } + }, + + + getGroupInfoFromCardData: function (vCardData, addressBook, getMembers = true) { + let members = []; + let name = "Unlabled Group"; try { name = vCardData["fn"][0].value; } catch (e) {} + let uid = ""; try { uid = vCardData["uid"][0].value; } catch (e) {} + + if (getMembers && vCardData.hasOwnProperty("X-ADDRESSBOOKSERVER-MEMBER")) { + for (let i=0; i < vCardData["X-ADDRESSBOOKSERVER-MEMBER"].length; i++) { + let member = vCardData["X-ADDRESSBOOKSERVER-MEMBER"][i].value.replace(/^(urn:uuid:)/,""); + // "member" is the X-DAV-UID property of the member vCard + members.push(member); + } + } + return {members, name, uid}; + }, + + + + //build group card + getVCardFromThunderbirdListCard: function(syncData, list, generateUID = false) { + let currentCard = list.getProperty("X-DAV-VCARD").trim(); + let cCardData = dav.vCard.parse(currentCard); + let vCardData = dav.vCard.parse(currentCard); + let members = list.getMembersPropertyList("X-DAV-UID"); + + if (!vCardData.hasOwnProperty("version")) vCardData["version"] = [{"value": "3.0"}]; + + let listName = list.getProperty("ListName", "Unlabled List"); + vCardData["fn"] = [{"value": listName}]; + vCardData["n"] = [{"value": listName}]; + vCardData["X-ADDRESSBOOKSERVER-KIND"] = [{"value": "group"}]; + + // check UID status + let uidProp = list.getProperty("X-DAV-UID"); + let uidItem = ""; try { uidItem = vCardData["uid"][0].value; } catch (e) {} + if (!uidItem && !uidProp) { + TbSync.eventlog.add("info", syncData.eventLogInfo, "Generated missing UID for list <"+listName+">"); + let uid = TbSync.generateUUID(); + list.setProperty("X-DAV-UID", uid); + vCardData["uid"] = [{"value": uid}]; + } else if (!uidItem && uidProp) { + vCardData["uid"] = [{"value": uidProp}]; + TbSync.eventlog.add("info", syncData.eventLogInfo, "Updating item uid from uid property for list <"+listName+">", JSON.stringify({uidProp, uidItem})); + } else if (uidItem && !uidProp) { + list.setProperty("X-DAV-UID", uidItem); + TbSync.eventlog.add("info", syncData.eventLogInfo, "Updating uid property from item uid of list <"+listName+">", JSON.stringify({uidProp, uidItem})); + } else if (uidItem != uidProp) { + list.setProperty("X-DAV-UID", uidItem); + TbSync.eventlog.add("info", syncData.eventLogInfo, "Updating uid property from item uid of list <"+listName+">", JSON.stringify({uidProp, uidItem})); + } + + //build memberlist from scratch + vCardData["X-ADDRESSBOOKSERVER-MEMBER"]=[]; + for (let member of members) { + // member has the UID (X-DAV-UID) of each member + vCardData["X-ADDRESSBOOKSERVER-MEMBER"].push({"value": "urn:uuid:" + member}); + } + + let newCard = dav.vCard.generate(vCardData).trim(); + let oldCard = dav.vCard.generate(cCardData).trim(); + + let modified = false; + if (oldCard != newCard) { + TbSync.dump("List has been modified!",""); + TbSync.dump("currentCard", oldCard); + TbSync.dump("newCard", newCard); + modified = true; + } + return {data: newCard, etag: list.getProperty("X-DAV-ETAG"), modified: modified}; + }, + + + setDefaultMetaButKeepCaseIfPresent: function(defaults, currentObj) { + const keys = Object.keys(defaults); + for (const key of keys) { + let defaultValue = defaults[key]; + + // we need to set this value, but do not want to cause a "modified" if it was set like this before, but just with different case + // so keep the current case + try { + let c = currentObj.meta[key][0]; + if (c.toLowerCase() == defaultValue.toLowerCase()) defaultValue = c; + } catch(e) { + //Components.utils.reportError(e); + } + + if (!currentObj.hasOwnProperty("meta")) currentObj.meta = {}; + currentObj.meta[key]=[defaultValue]; + } + }, + + getImageExtension: function(filename) { + // get extension from filename + let extension = "jpg"; + try { + let parts = filename.toString().split("/").pop().split("."); + let lastPart = parts.pop(); + if (parts.length > 0 && lastPart) { + extension = lastPart; + } + } catch (e) {} + return extension.toLowerCase(); + }, + + + //return the stored vcard of the card (or empty vcard if none stored) and merge local changes + getVCardFromThunderbirdContactCard: function(syncData, card, generateUID = false) { + let currentCard = card.getProperty("X-DAV-VCARD").trim(); + let cCardData = dav.vCard.parse(currentCard); + let vCardData = dav.vCard.parse(currentCard); + + for (let f=0; f < dav.tools.supportedProperties.length; f++) { + //Skip sync fields that have been added after this folder was created (otherwise we would delete them) + if (Services.vc.compare(dav.tools.supportedProperties[f].minversion, syncData.currentFolderData.getFolderProperty("createdWithProviderVersion"))> 0) continue; + + let property = dav.tools.supportedProperties[f].name; + let vCardField = dav.tools.getVCardField(syncData, property, vCardData); + + //some "properties" need special handling + switch (property) { + case "Photo": + { + let extension = this.getImageExtension(card.getProperty("PhotoURI", "")); + let type = (extension == "jpg") ? "JPEG" : extension.toUpperCase(); + + if (card.getProperty("PhotoType", "") == "file") { + TbSync.eventlog.add("info", syncData.eventLogInfo, "before photo ("+vCardField.item+")", JSON.stringify(vCardData)); + dav.tools.updateValueOfVCard(syncData, property, vCardData, vCardField, card.getPhoto()); + this.setDefaultMetaButKeepCaseIfPresent({encoding : "B", type : type}, vCardData[vCardField.item][0]); + TbSync.eventlog.add("info", syncData.eventLogInfo, "after photo ("+vCardField.item+")", JSON.stringify(vCardData)); + } else if (card.getProperty("PhotoType", "") == "web" && card.getProperty("PhotoURI", "")) { + TbSync.eventlog.add("info", syncData.eventLogInfo, "before photo ("+vCardField.item+")", JSON.stringify(vCardData)); + dav.tools.updateValueOfVCard(syncData, property, vCardData, vCardField, card.getProperty("PhotoURI", "")); + this.setDefaultMetaButKeepCaseIfPresent({value : "uri", type : type}, vCardData[vCardField.item][0]); + TbSync.eventlog.add("info", syncData.eventLogInfo, "after photo ("+vCardField.item+")", JSON.stringify(vCardData)); + } + } + break; + + case "Birthday": + { + // Support missing year in vcard3, as done by Apple Contacts. + const APPLE_MISSING_YEAR_MARK = "1604"; + + let birthYear = parseInt(card.getProperty("BirthYear", 0)); + let birthMonth = parseInt(card.getProperty("BirthMonth", 0)); + let birthDay = parseInt(card.getProperty("BirthDay", 0)); + + if (!birthYear) { + birthYear = APPLE_MISSING_YEAR_MARK; + } + + let value = ""; + if (birthYear && birthMonth && birthDay) { + // TODO: for vcard4, we'll need to get rid of the hyphens and support missing date elements + value = birthYear + "-" + ("00"+birthMonth).slice(-2) + "-" + ("00"+birthDay).slice(-2); + } + dav.tools.updateValueOfVCard(syncData, property, vCardData, vCardField, value); + + if (birthYear == APPLE_MISSING_YEAR_MARK && Array.isArray(vCardData[vCardField.item]) && vCardData[vCardField.item].length > 0) { + vCardData[vCardField.item][0].meta = {"x-apple-omit-year": [APPLE_MISSING_YEAR_MARK]}; + } + } + break; + + case "X-DAV-JSON-Emails": + { + //this gets us all emails + let emails = dav.tools.getEmailsFromCard(card); + let idx = 0; + + //store default meta type + let defaultMeta = vCardField.metatype; + + for (let i=0; i < emails.length || (vCardData.hasOwnProperty(vCardField.item) && idx < vCardData[vCardField.item].length); i++) { + //get value or or empty if entry is to be deleted + let value = (i < emails.length) ? emails[i].value : ""; + + //fix for bug 1522453 - ignore these + if (value.endsWith("@bug1522453")) + continue; + + //do we have a meta type? otherwise stick to default + if (i < emails.length && emails[i].meta.length > 0) { + vCardField.metatype = emails[i].meta; + } else { + vCardField.metatype = defaultMeta; + } + + //remove: value == "" and index != -1 + //add value != "" and index == -1 + vCardField.entry = idx++; + if (!(vCardData.hasOwnProperty(vCardField.item) && vCardField.entry < vCardData[vCardField.item].length)) vCardField.entry = -1; //need to add a new one + + dav.tools.updateValueOfVCard(syncData, "Emails", vCardData, vCardField, value); + } + } + break; + + case "X-DAV-JSON-Phones": + { + //this gets us all phones + let phones = dav.tools.getPhoneNumbersFromCard(card); + let idx = 0; + + //store default meta type + let defaultMeta = vCardField.metatype; + + for (let i=0; i < phones.length || (vCardData.hasOwnProperty(vCardField.item) && idx < vCardData[vCardField.item].length); i++) { + //get value or or empty if entry is to be deleted + let value = (i < phones.length) ? phones[i].value : ""; + + //do we have a meta type? otherwise stick to default + if (i < phones.length && phones[i].meta.length > 0) { + vCardField.metatype = phones[i].meta; + } else { + vCardField.metatype = defaultMeta; + } + + //remove: value == "" and index != -1 + //add value != "" and index == -1 + vCardField.entry = idx++; + if (!(vCardData.hasOwnProperty(vCardField.item) && vCardField.entry < vCardData[vCardField.item].length)) vCardField.entry = -1; //need to add a new one + + dav.tools.updateValueOfVCard(syncData, "Phones", vCardData, vCardField, value); + } + } + break; + + default: + { + let value = card.getProperty(property, ""); + dav.tools.updateValueOfVCard(syncData, property, vCardData, vCardField, value); + } + break; + } + } + + // check UID status + let uidProp = card.getProperty("X-DAV-UID"); + let uidItem = ""; try { uidItem = vCardData["uid"][0].value; } catch (e) {} + if (!uidItem && !uidProp) { + TbSync.eventlog.add("info", syncData.eventLogInfo, "Generated missing UID for card <"+listName+">"); + let uid = TbSync.generateUUID(); + card.setProperty("X-DAV-UID", uid); + vCardData["uid"] = [{"value": uid}]; + syncData.target.modifyItem(card); + } else if (!uidItem && uidProp) { + vCardData["uid"] = [{"value": uidProp}]; + TbSync.eventlog.add("info", syncData.eventLogInfo, "Updating item uid from uid property for card <"+listName+">", JSON.stringify({uidProp, uidItem})); + } else if (uidItem && !uidProp) { + card.setProperty("X-DAV-UID", uidItem); + TbSync.eventlog.add("info", syncData.eventLogInfo, "Updating uid property from item uid of card <"+listName+">", JSON.stringify({uidProp, uidItem})); + syncData.target.modifyItem(card); + } else if (uidItem != uidProp) { + card.setProperty("X-DAV-UID", uidItem); + TbSync.eventlog.add("info", syncData.eventLogInfo, "Updating uid property from item uid of card <"+listName+">", JSON.stringify({uidProp, uidItem})); + syncData.target.modifyItem(card); + } + + //add required fields + if (!vCardData.hasOwnProperty("version")) vCardData["version"] = [{"value": "3.0"}]; + if (!vCardData.hasOwnProperty("fn")) vCardData["fn"] = [{"value": " "}]; + if (!vCardData.hasOwnProperty("n")) vCardData["n"] = [{"value": [" ","","","",""]}]; + + //build vCards + let newCard = dav.vCard.generate(vCardData).trim(); + let oldCard = dav.vCard.generate(cCardData).trim(); + + let modified = false; + if (oldCard != newCard) { + TbSync.dump("Card has been modified!",""); + TbSync.dump("currentCard", oldCard); + TbSync.dump("newCard", newCard); + modified = true; + } + return {data: newCard, etag: card.getProperty("X-DAV-ETAG"), modified: modified}; + }, + +} diff -Nru dav4tbsync-0.15/content/includes/vcard/LICENSE dav4tbsync-1.9/content/includes/vcard/LICENSE --- dav4tbsync-0.15/content/includes/vcard/LICENSE 1970-01-01 00:00:00.000000000 +0000 +++ dav4tbsync-1.9/content/includes/vcard/LICENSE 2020-02-20 12:39:38.000000000 +0000 @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Aleksandr Kitov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff -Nru dav4tbsync-0.15/content/includes/vcard/SOURCE dav4tbsync-1.9/content/includes/vcard/SOURCE --- dav4tbsync-0.15/content/includes/vcard/SOURCE 1970-01-01 00:00:00.000000000 +0000 +++ dav4tbsync-1.9/content/includes/vcard/SOURCE 2020-02-20 12:39:38.000000000 +0000 @@ -0,0 +1 @@ +https://github.com/Heymdall/vcard/releases/tag/v0.2.7 \ No newline at end of file diff -Nru dav4tbsync-0.15/content/includes/vcard/vcard.js dav4tbsync-1.9/content/includes/vcard/vcard.js --- dav4tbsync-0.15/content/includes/vcard/vcard.js 1970-01-01 00:00:00.000000000 +0000 +++ dav4tbsync-1.9/content/includes/vcard/vcard.js 2020-02-20 12:39:38.000000000 +0000 @@ -0,0 +1,305 @@ +(function (namespace) { + var PREFIX = 'BEGIN:VCARD', + POSTFIX = 'END:VCARD'; + + /** + * Return json representation of vCard + * @param {string} string raw vCard + * @returns {*} + */ + function parse(string) { + var result = {}, + lines = string.split(/\r\n|\r|\n/), + count = lines.length, + pieces, + key, + value, + meta, + namespace; + + for (var i = 0; i < count; i++) { + if (lines[i] == '') { + continue; + } + if (lines[i].toUpperCase() == PREFIX || lines[i].toUpperCase() == POSTFIX) { + continue; + } + var data = lines[i]; + + /** + * Check that next line continues current + * @param {number} i + * @returns {boolean} + */ + var isValueContinued = function (i) { + return i + 1 < count && (lines[i + 1][0] == ' ' || lines[i + 1][0] == '\t'); + }; + // handle multiline properties (i.e. photo). + // next line should start with space or tab character + if (isValueContinued(i)) { + while (isValueContinued(i)) { + data += lines[i + 1].trim(); + i++; + } + } + + pieces = data.split(':'); + key = pieces.shift(); + value = pieces.join(':'); + namespace = false; + meta = {}; + + // meta fields in property + if (key.match(/;/)) { + key = key + .replace(/\\;/g, 'ΩΩΩ') + .replace(/\\,/, ','); + var metaArr = key.split(';').map(function (item) { + return item.replace(/ΩΩΩ/g, ';'); + }); + key = metaArr.shift(); + metaArr.forEach(function (item) { + var arr = item.split('='); + arr[0] = arr[0].toLowerCase(); + if (arr[0].length === 0) { + return; + } + if (arr.length>1) { + //removing boundary quotes and splitting up values, if send as list - upperCase for hitory reasons + let metavalue = arr[1].replace (/(^")|("$)/g, '').toUpperCase().split(","); + if (meta[arr[0]]) { + meta[arr[0]].push(...metavalue); + } else { + meta[arr[0]] = metavalue; + } + } + }); + } + + // values with \n + value = value + .replace(/\\r/g, '') + .replace(/\\n/g, '\n'); + + value = tryToSplit(value); + + // Grouped properties + if (key.match(/\./)) { + var arr = key.split('.'); + key = arr[1]; + namespace = arr[0]; + } + + var newValue = { + value: value + }; + if (Object.keys(meta).length) { + newValue.meta = meta; + } + if (namespace) { + newValue.namespace = namespace; + } + + if (key.indexOf('X-') !== 0) { + key = key.toLowerCase(); + } + + if (typeof result[key] === 'undefined') { + result[key] = [newValue]; + } else { + result[key].push(newValue); + } + + } + + return result; + } + + var HAS_SEMICOLON_SEPARATOR = /[^\\];|^;/, + HAS_COMMA_SEPARATOR = /[^\\],|^,/; + /** + * Split value by "," or ";" and remove escape sequences for this separators + * @param {string} value + * @returns {string|string[] + */ + function tryToSplit(value) { + if (value.match(HAS_SEMICOLON_SEPARATOR)) { + value = value.replace(/\\,/g, ','); + return splitValue(value, ';'); + } else if (value.match(HAS_COMMA_SEPARATOR)) { + value = value.replace(/\\;/g, ';'); + return splitValue(value, ','); + } else { + return value + .replace(/\\,/g, ',') + .replace(/\\;/g, ';'); + } + } + /** + * Split vcard field value by separator + * @param {string|string[]} value + * @param {string} separator + * @returns {string|string[]} + */ + function splitValue(value, separator) { + var separatorRegexp = new RegExp(separator); + var escapedSeparatorRegexp = new RegExp('\\\\' + separator, 'g'); + // easiest way, replace it with really rare character sequence + value = value.replace(escapedSeparatorRegexp, 'ΩΩΩ'); + if (value.match(separatorRegexp)) { + value = value.split(separator); + + value = value.map(function (item) { + return item.replace(/ΩΩΩ/g, separator); + }); + } else { + value = value.replace(/ΩΩΩ/g, separator); + } + return value; + } + + var guid = (function() { + function s4() { + return Math.floor((1 + Math.random()) * 0x10000) + .toString(16) + .substring(1); + } + return function() { + return s4() + s4() + '-' + s4() + '-' + s4() + '-' + + s4() + '-' + s4() + s4() + s4(); + }; + })(); + + var COMMA_SEPARATED_FIELDS = ['nickname', 'related', 'categories', 'pid']; + + /** + * Generate vCard representation af object + * @param {*} data + * @param {obj} + * member "simpleType" returns all types joined by , instead of multiple TYPE= entries + * member "addRequired" determine if generator should add required properties (version and uid) + * @returns {string} + */ + function generate(data, options = {simpleType: true}) { + var lines = [PREFIX], + line = ''; + + if (options.addRequired && !data.version) { + data.version = [{value: '3.0'}]; + } + if (options.addRequired && !data.uid) { + data.uid = [{value: guid()}]; + } + + var escapeCharacters = function (v) { + if (typeof v === 'undefined') { + return ''; + } + return v + .replace(/\r\n|\r|\n/g, '\\n') + .replace(/;/g, '\\;') + .replace(/,/g, '\\,') + }; + + var escapeTypeCharacters = function(v) { + if (typeof v === 'undefined') { + return ''; + } + return v + .replace(/\r\n|\r|\n/g, '\\n') + .replace(/;/g, '\\;') + }; + + Object.keys(data).forEach(function (key) { + if (!data[key] || typeof data[key].forEach !== 'function') { + return; + } + data[key].forEach(function (value) { + // ignore empty values + if (typeof value.value === 'undefined' || value.value == '') { + return; + } + // ignore empty array values + if (value.value instanceof Array) { + var empty = true; + for (var i = 0; i < value.value.length; i++) { + if (typeof value.value[i] !== 'undefined' && value.value[i] != '') { + empty = false; + break; + } + } + if (empty) { + return; + } + } + line = ''; + + // add namespace if exists + if (value.namespace) { + line += value.namespace + '.'; + } + line += key.indexOf('X-') === 0 ? key : key.toUpperCase(); + + // add meta properties + if (typeof value.meta === 'object') { + Object.keys(value.meta).forEach(function (metaKey) { + // values of meta tags must be an array + if (typeof value.meta[metaKey].forEach !== 'function') { + return; + } + //join meta types so we get TYPE=a,b,c instead of TYPE=a;TYPE=b;TYPE=c + let metaArr = (options.simpleType && metaKey.toUpperCase() === 'TYPE') ? [value.meta[metaKey].join(",")] : value.meta[metaKey]; + metaArr.forEach(function (metaValue) { + if (metaKey.length > 0) { + if (metaKey.toUpperCase() === 'TYPE') { + // Do not escape the comma when it is the type property. This breaks a lot. + line += ';' + escapeCharacters(metaKey.toUpperCase()) + '=' + escapeTypeCharacters(metaValue); + } else { + line += ';' + escapeCharacters(metaKey.toUpperCase()) + '=' + escapeCharacters(metaValue); + } + } + }); + }); + } + + line += ':'; + + + + if (typeof value.value === 'string') { + line += escapeCharacters(value.value); + } else { + // list-values + var separator = COMMA_SEPARATED_FIELDS.indexOf(key) !== -1 + ? ',' + : ';'; + line += value.value.map(function (item) { + return escapeCharacters(item); + }).join(separator); + } + + // line-length limit. Content lines + // SHOULD be folded to a maximum width of 75 octets, excluding the line break. + if (line.length > 75) { + var firstChunk = line.substr(0, 75), + least = line.substr(75); + var splitted = least.match(/.{1,74}/g); + lines.push(firstChunk); + splitted.forEach(function (chunk) { + lines.push(' ' + chunk); + }); + } else { + lines.push(line); + } + }); + }); + + lines.push(POSTFIX); + return lines.join('\r\n'); + } + + namespace.vCard = { + parse: parse, + generate: generate + }; +})(this); diff -Nru dav4tbsync-0.15/content/manager/createAccount.js dav4tbsync-1.9/content/manager/createAccount.js --- dav4tbsync-0.15/content/manager/createAccount.js 2019-02-26 16:26:01.000000000 +0000 +++ dav4tbsync-1.9/content/manager/createAccount.js 2020-02-20 12:39:38.000000000 +0000 @@ -8,106 +8,368 @@ "use strict"; -Components.utils.import("resource://gre/modules/Services.jsm"); -Components.utils.import("resource://gre/modules/Task.jsm"); -Components.utils.import("chrome://tbsync/content/tbsync.jsm"); +var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); +var { TbSync } = ChromeUtils.import("chrome://tbsync/content/tbsync.jsm"); + +const dav = TbSync.providers.dav; var tbSyncDavNewAccount = { + + // standard data fields + get elementName() { return document.getElementById('tbsync.newaccount.name'); }, + get elementUser() { return document.getElementById('tbsync.newaccount.user'); }, + get elementPass() { return document.getElementById('tbsync.newaccount.password'); }, + get elementServer() { return document.getElementById('tbsync.newaccount.server'); }, + get elementCalDavServer() { return document.getElementById('tbsync.newaccount.caldavserver'); }, + get elementCardDavServer() { return document.getElementById('tbsync.newaccount.carddavserver'); }, + get serviceproviderlist() { return document.getElementById('tbsync.newaccount.serviceproviderlist'); }, + + get accountname() { return this.elementName.value.trim(); }, + get username() { return this.elementUser.value.trim(); }, + get password() { return this.elementPass.value.trim(); }, + get server() { return this.elementServer.value.trim(); }, + get calDavServer() { return this.elementCalDavServer.value.trim(); }, + get cardDavServer() { return this.elementCardDavServer.value.trim(); }, + get serviceprovider() { return this.serviceproviderlist.value; }, + get userdomain() { + let parts = this.username.split("@"); + if (parts.length == 2) { + let subparts = parts[1].split("."); + if (subparts.length > 1 && subparts[subparts.length-1].length > 1) return parts[1]; + } + return null; + }, + + set accountname(v) { this.elementName.value = v; }, + set username(v) { this.elementUser.value = v; }, + set password(v) { this.elementPass.value = v; }, + set server(v) { this.elementServer.value = v; }, + set calDavServer(v) { this.elementCalDavServer.value = v; }, + set cardDavServer(v) { this.elementCardDavServer.value = v; }, + + + + // final data fields on final page + get elementFinalName() { return document.getElementById('tbsync.finalaccount.name'); }, + get elementFinalUser() { return document.getElementById('tbsync.finalaccount.user'); }, + get elementFinalCalDavServer() { return document.getElementById('tbsync.finalaccount.caldavserver'); }, + get elementFinalCardDavServer() { return document.getElementById('tbsync.finalaccount.carddavserver'); }, + + get finalAccountname() { return this.elementFinalName.value.trim(); }, + get finalUsername() { return this.elementFinalUser.value.trim(); }, + get finalCalDavServer() { return this.elementFinalCalDavServer.value.trim(); }, + get finalCardDavServer() { return this.elementFinalCardDavServer.value.trim(); }, + + set finalAccountname(v) { this.elementFinalName.value = v;}, + set finalUsername(v) { + this.elementFinalUser.value = v; + this.elementFinalUser.setAttribute("tooltiptext", v); + }, + set finalCalDavServer(v) { + this.elementFinalCalDavServer.value = v; + this.elementFinalCalDavServer.setAttribute("tooltiptext", v); + document.getElementById("tbsyncfinalaccount.caldavserver.row").hidden = (v.trim() == ""); + }, + set finalCardDavServer(v) { + this.elementFinalCardDavServer.value = v; + this.elementFinalCardDavServer.setAttribute("tooltiptext", v); + document.getElementById("tbsyncfinalaccount.carddavserver.row").hidden = (v.trim() == ""); + }, + + get validated() { return this._validated || false; }, + set validated(v) { + this._validated = v; + if (v) { + this.finalAccountname = this.accountname; + } else { + this.finalAccountname = ""; + this.finalUsername = ""; + this.finalCalDavServer = ""; + this.finalCardDavServer = ""; + } + }, + + + showSpinner: function(spinnerText) { + document.getElementById("tbsync.spinner").hidden = false; + document.getElementById("tbsync.spinner.label").value = TbSync.getString("add.spinner." + spinnerText, "dav"); + }, + + hideSpinner: function() { + document.getElementById("tbsync.spinner").hidden = true; + }, + + onLoad: function () { + this.providerData = new TbSync.ProviderData("dav"); + + //init list + this.serviceproviderlist.appendChild(this.addProviderEntry("sabredav32.png", "discovery")); + this.serviceproviderlist.appendChild(this.addProviderEntry("sabredav32.png", "custom")); + for (let p in dav.sync.serviceproviders) { + this.serviceproviderlist.appendChild(this.addProviderEntry(dav.sync.serviceproviders[p].icon +"32.png", p)); + } + + document.addEventListener("wizardfinish", tbSyncDavNewAccount.onFinish.bind(this)); + document.addEventListener("wizardnext", tbSyncDavNewAccount.onAdvance.bind(this)); + document.addEventListener("wizardcancel", tbSyncDavNewAccount.onCancel.bind(this)); + document.getElementById("firstPage").addEventListener("pageshow", tbSyncDavNewAccount.resetFirstPage.bind(this)); + document.getElementById("secondPage").addEventListener("pageshow", tbSyncDavNewAccount.resetSecondPage.bind(this)); + document.getElementById("thirdPage").addEventListener("pageshow", tbSyncDavNewAccount.resetThirdPage.bind(this)); + + this.serviceproviderlist.selectedIndex = 0; + tbSyncDavNewAccount.resetFirstPage(); + }, + + onUnload: function () { + }, + + onClose: function () { + //disallow closing of wizard while isLocked + return !this.isLocked; + }, + + onCancel: function (event) { + //disallow closing of wizard while isLocked + if (this.isLocked) { + event.preventDefault(); + } + }, + + onFinish () { + let newAccountEntry = this.providerData.getDefaultAccountEntries(); + newAccountEntry.createdWithProviderVersion = this.providerData.getVersion(); + newAccountEntry.serviceprovider = this.serviceprovider == "discovery" ? "custom" : this.serviceprovider; + newAccountEntry.serviceproviderRevision = dav.sync.serviceproviders.hasOwnProperty(this.serviceprovider) ? dav.sync.serviceproviders[this.serviceprovider].revision : 0 + newAccountEntry.calDavHost = this.finalCalDavServer; + newAccountEntry.cardDavHost = this.finalCardDavServer; + + // Add the new account. + let newAccountData = this.providerData.addAccount(this.finalAccountname, newAccountEntry); + dav.network.getAuthData(newAccountData).updateLoginData(this.finalUsername, this.password); + }, - startTime: 0, - maxTimeout: 30, + + + + // HELPER FUNCTIONS addProviderEntry: function (icon, serviceprovider) { - let name = tbSync.getLocalizedMessage("add.serverprofile."+serviceprovider, "dav"); - let description = tbSync.getLocalizedMessage("add.serverprofile."+serviceprovider+".description", "dav"); + let name = TbSync.getString("add.serverprofile."+serviceprovider, "dav"); + let description = TbSync.getString("add.serverprofile."+serviceprovider+".description", "dav"); //left column - let image = document.createElement("image"); + let image = document.createXULElement("image"); image.setAttribute("src", "chrome://dav4tbsync/skin/" + icon); image.setAttribute("style", "margin:1ex;"); - let leftColumn = document.createElement("vbox"); + let leftColumn = document.createXULElement("vbox"); leftColumn.appendChild(image); //right column - let label = document.createElement("label"); + let label = document.createXULElement("label"); label.setAttribute("class", "header"); label.setAttribute("value", name); - let desc = document.createElement("description"); + let desc = document.createXULElement("description"); desc.setAttribute("style", "width: 300px"); desc.textContent = description; - let rightColumn = document.createElement("vbox"); + let rightColumn = document.createXULElement("vbox"); rightColumn.appendChild(label); rightColumn.appendChild(desc); //columns - let columns = document.createElement("hbox"); + let columns = document.createXULElement("hbox"); columns.appendChild(leftColumn); columns.appendChild(rightColumn); //richlistitem - let richlistitem = document.createElement("richlistitem"); + let richlistitem = document.createXULElement("richlistitem"); richlistitem.setAttribute("style", "padding:4px"); richlistitem.setAttribute("value", serviceprovider); richlistitem.appendChild(columns); return richlistitem; }, - - onLoad: function () { - this.elementName = document.getElementById('tbsync.newaccount.name'); - this.elementUser = document.getElementById('tbsync.newaccount.user'); - this.elementPass = document.getElementById('tbsync.newaccount.password'); - this.elementServer = document.getElementById('tbsync.newaccount.server'); - this.elementCalDavServer = document.getElementById('tbsync.newaccount.caldavserver'); - this.elementCardDavServer = document.getElementById('tbsync.newaccount.carddavserver'); - this.serviceproviderlist = document.getElementById('tbsync.newaccount.serviceproviderlist'); - - //init list - this.serviceproviderlist.appendChild(this.addProviderEntry("sabredav32.png", "discovery")); - this.serviceproviderlist.appendChild(this.addProviderEntry("sabredav32.png", "custom")); - for (let p in tbSync.dav.serviceproviders) { - this.serviceproviderlist.appendChild(this.addProviderEntry(tbSync.dav.serviceproviders[p].icon +"32.png", p)); + + checkUrlForPrincipal: async function (job) { + // according to RFC6764, we must also try the username with cut-off domain part + // Note: This is never called for OAUTH serves (see onAdvance) + let users = []; + users.push(this.username); + if (this.userdomain) users.push(this.username.split("@")[0]); + + for (let user of users) { + let connectionData = new dav.network.ConnectionData(); + connectionData.password = this.password; + connectionData.username = user; + connectionData.timeout = 5000; + + //only needed for proper error reporting - that dav needs this is beyond API - connectionData is not used by TbSync + //connectionData is a structure which contains all the information needed to establish and evaluate a network connection + connectionData.eventLogInfo = new TbSync.EventLogInfo("dav", this.accountname); + + job.valid = false; + job.error = ""; + + try { + let response = await dav.network.sendRequest("", job.server , "PROPFIND", connectionData, {"Depth": "0", "Prefer": "return=minimal"}, {containerRealm: "setup", containerReset: true, passwordRetries: 0}); + // allow 404 because iCloud sends it on valid answer (yeah!) + let principal = (response && response.multi) ? dav.tools.getNodeTextContentFromMultiResponse(response, [["d","prop"], ["d","current-user-principal"], ["d","href"]], null, ["200","404"]) : null; + job.valid = (principal !== null); + if (!job.valid) { + job.error = job.type + "servernotfound"; + TbSync.eventlog.add("warning", connectionData.eventLogInfo, job.error, response ? response.commLog : ""); + } else { + job.validUser = user; + job.validUrl = (response ? response.permanentlyRedirectedUrl : null) || job.server; + return; + } + } catch (e) { + job.valid = false; + job.error = (e.statusData ? e.statusData.message : e.message); + + if (e.name == "dav4tbsync") { + TbSync.eventlog.add("warning", connectionData.eventLogInfo, e.statusData.message, e.statusData.details); + } else { + Components.utils.reportError(e); + } + } + + // only retry with other user, if 401 + if (!job.error.startsWith("401")) { + break; + } } - this.serviceproviderlist.selectedIndex = 0; - this.validating = false; + + return; }, - + + advance: function () { + document.getElementById("tbsync.newaccount.wizard").advance(null); + }, + + + + + + // RESET AND INIT FUNCTIONS clearValues: function () { //clear fields - this.elementUser.value = ""; - this.elementPass.value = ""; - this.elementServer.value = ""; - this.elementCalDavServer.value = ""; - this.elementCardDavServer.value = ""; - - let serviceprovider = this.serviceproviderlist.value; - if (serviceprovider == "discovery" || serviceprovider == "custom") { - this.elementName.value = ""; + this.username = ""; + this.password = ""; + this.server = ""; + this.calDavServer = ""; + this.cardDavServer = ""; + + if (this.serviceprovider == "discovery" || this.serviceprovider == "custom") { + this.accountname = ""; } else { - this.elementName.value = tbSync.getLocalizedMessage("add.serverprofile."+serviceprovider, "dav"); + this.accountname = TbSync.getString("add.serverprofile." + this.serviceprovider, "dav"); } }, - - showFirstPage: function () { + + resetFirstPage: function () { + // RESET / INIT first page + document.getElementById("tbsync.newaccount.wizard").canRewind = false; document.getElementById("tbsync.newaccount.wizard").canAdvance = true; - this.validating = false; + this.isLocked = false; + this.validated = false; }, - - showSecondPage: function () { - tbSyncDavNewAccount.onUserTextInput(); + + resetSecondPage: function () { + // RESET / INIT second page + this.isLocked = false; + this.validated = false; + + document.getElementById("tbsync.newaccount.wizard").canRewind = true; + document.getElementById("tbsync.newaccount.wizard").canAdvance = false; + this.hideSpinner(); + document.getElementById("tbsync.error").hidden = true; + + this.checkUI(); + }, + + resetThirdPage: function () { + // RESET / INIT third page + document.getElementById("tbsync.newaccount.wizard").canRewind = true; + document.getElementById("tbsync.newaccount.wizard").canAdvance = true; + this.isLocked = false; + }, + + + + + + // UI FUNCTIONS + lockUI: function(spinnerText) { + this.showSpinner(spinnerText); + document.getElementById("tbsync.error").hidden = true; + document.getElementById("tbsync.newaccount.wizard").canAdvance = false; + document.getElementById("tbsync.newaccount.wizard").canRewind = false; + this.isLocked = true; + }, + + unlockUI: function() { + this.hideSpinner(); + document.getElementById("tbsync.newaccount.wizard").canRewind = true; + this.isLocked = false; + this.checkUI(); + }, + + checkUI: function (hideError) { + if (hideError) { + document.getElementById("tbsync.error").hidden = true; + } + + // determine, if we can advance or not + if (this.serviceprovider == "discovery") { + document.getElementById("tbsync.newaccount.wizard").canAdvance = !( + (this.accountname == "") || + (this.server == "" && !this.userdomain) || + (this.server == "" && this.username == "")); + } else if (this.serviceprovider == "custom") { + // custom does not need username or password (allow annonymous access) + document.getElementById("tbsync.newaccount.wizard").canAdvance = !( + (this.accountname == "") || + (this.calDavServer + this.cardDavServer == "")); + } else if (this.serviceprovider == "google") { + // google does not need a password field and also no username + document.getElementById("tbsync.newaccount.wizard").canAdvance = !( + (this.accountname == "")); + } else { + // build in service providers do need a username and password + document.getElementById("tbsync.newaccount.wizard").canAdvance = !( + (this.accountname == "") || + (this.password == "") || + (this.username == "")); + } + + // update placeholder attribute of server + this.elementServer.setAttribute("placeholder", this.userdomain ? TbSync.getString("add.serverprofile.discovery.server-optional", "dav") : ""); + - let serviceprovider = this.serviceproviderlist.value; //show/hide additional descriptions (if avail) let dFound = 0; for (let i=1; i < 4; i++) { let dElement = document.getElementById("tbsync.newaccount.details" + i); - let dLocaleString = "add.serverprofile."+serviceprovider+".details" + i; - let dLocaleValue = tbSync.getLocalizedMessage(dLocaleString, "dav"); + let dLocaleString = "add.serverprofile." + this.serviceprovider + ".details" + i; + let dLocaleValue = TbSync.getString(dLocaleString, "dav"); - if (dLocaleValue == dLocaleString) { + let hide = (dLocaleValue == dLocaleString); + if (this.serviceprovider == "discovery") { + // show them according to UI state + switch (i) { + case 1: + hide = false; + break; + case 2: + hide = !this.userdomain; + break; + } + } + + if (hide) { dElement.textContent = ""; dElement.hidden = true; } else { @@ -120,192 +382,174 @@ //hide Notes header, if no descriptions avail let dLabel = document.getElementById("tbsync.newaccount.details.header"); dLabel.hidden = (dFound == 0); - - //always show the two server URLs, excpet for "discovery" serviceprovider - if (serviceprovider == "discovery") { + + + //which server fields to show? + document.getElementById("tbsync.newaccount.finaluser.row").hidden = (this.serviceprovider == "google"); + document.getElementById("tbsync.newaccount.user.row").hidden = (this.serviceprovider == "google"); + document.getElementById("tbsync.newaccount.password.row").hidden = (this.serviceprovider == "google"); + + if (this.serviceprovider == "discovery") { document.getElementById("tbsync.newaccount.caldavserver.row").hidden = true; document.getElementById("tbsync.newaccount.carddavserver.row").hidden = true; document.getElementById("tbsync.newaccount.server.row").hidden = false; - this.elementCalDavServer.disabled = false; - this.elementCardDavServer.disabled = false; + //this.elementCalDavServer.disabled = false; + //this.elementCardDavServer.disabled = false; + } else if (this.serviceprovider == "custom") { + // custom + document.getElementById("tbsync.newaccount.caldavserver.row").hidden = false; + document.getElementById("tbsync.newaccount.carddavserver.row").hidden = false; + document.getElementById("tbsync.newaccount.server.row").hidden = true; + //this.elementCalDavServer.disabled = false; + //this.elementCardDavServer.disabled = false; } else { - document.getElementById("tbsync.newaccount.server.row").hidden = true; - if (serviceprovider == "custom") { - document.getElementById("tbsync.newaccount.caldavserver.row").hidden = false; - document.getElementById("tbsync.newaccount.carddavserver.row").hidden = false; - this.elementCalDavServer.disabled = false; - this.elementCardDavServer.disabled = false; - } else { - document.getElementById("tbsync.newaccount.caldavserver.row").hidden = true; - document.getElementById("tbsync.newaccount.carddavserver.row").hidden = true; - this.elementCalDavServer.disabled = true; - this.elementCardDavServer.disabled = true; - this.elementCalDavServer.value = tbSync.dav.serviceproviders[serviceprovider].caldav; - this.elementCardDavServer.value = tbSync.dav.serviceproviders[serviceprovider].carddav; - } - } - - this.validating = false; - document.getElementById("tbsync.spinner").hidden = true; - document.getElementById("tbsync.error").hidden = true; - }, - - onUnload: function () { + // build in service provider + document.getElementById("tbsync.newaccount.caldavserver.row").hidden = true; + document.getElementById("tbsync.newaccount.carddavserver.row").hidden = true; + document.getElementById("tbsync.newaccount.server.row").hidden = true; + //this.elementCalDavServer.disabled = true; + //this.elementCardDavServer.disabled = true; + this.calDavServer = dav.sync.serviceproviders[this.serviceprovider].caldav; + this.cardDavServer = dav.sync.serviceproviders[this.serviceprovider].carddav; + } }, - advance: function () { - document.getElementById("tbsync.newaccount.wizard").advance(null); - }, - - onUserTextInput: function () { - document.documentElement.getButton("finish").disabled = (this.elementServer.value.trim() + this.elementCalDavServer.value.trim() + this.elementCardDavServer.value.trim() == "" || this.elementName.value.trim() == "" || this.elementUser.value == "" || this.elementPass.value == ""); - }, - onFinish: function () { - if (document.documentElement.getButton("finish").disabled == false) { - //initiate validation of server connection, - document.getElementById("tbsync.newaccount.wizard").canRewind = false; - document.documentElement.getButton("finish").disabled = true; - this.validating = true; - this.validate(); - } - return false; - }, - validate: Task.async (function* () { - document.getElementById("tbsync.error").hidden = true; - document.getElementById("tbsync.spinner").hidden = false; - this.accountdata = {}; - this.accountdata.accountname = this.elementName.value.trim(); - this.accountdata.user = this.elementUser.value; - this.accountdata.password = this.elementPass.value; - this.accountdata.caldavserver = this.elementCalDavServer.value.trim(); - this.accountdata.carddavserver = this.elementCardDavServer.value.trim(); - - this.accountdata.serviceprovider = this.serviceproviderlist.value; - if (this.accountdata.serviceprovider == "discovery") { - this.accountdata.serviceprovider = "custom"; - let server = this.elementServer.value.trim(); - while (server.endsWith("/")) { server = server.slice(0,-1); } - - this.accountdata.caldavserver = server + "/.well-known/caldav"; - this.accountdata.carddavserver = server + "/.well-known/carddav"; - } else { - while (this.accountdata.caldavserver.endsWith("/")) { this.accountdata.caldavserver = this.accountdata.caldavserver.slice(0,-1); } - while (this.accountdata.carddavserver.endsWith("/")) { this.accountdata.carddavserver = this.accountdata.carddavserver.slice(0,-1); } + + // SETUP LOGIC FUNCTION + onAdvance: function (event) { + // do not prevent advancing if we go from page 1 to page 2, or if validation succeeded + if (document.getElementById("tbsync.newaccount.wizard").currentPage.id == "firstPage" || this.validated) { + return; } + + // if we reach this, we are on page 2 but may not advance but + // go through the setup steps - //HTTP or HTTPS? Default to https, if http is not explicitly specified - this.accountdata.https = (this.accountdata.caldavserver.toLowerCase().substring(0,7) == "http://") ? "0" : "1"; - this.accountdata.caldavserver = this.accountdata.caldavserver.replace("https://","").replace("http://",""); - this.accountdata.carddavserver = this.accountdata.carddavserver.replace("https://","").replace("http://",""); - - let authenticationManager = Components.classes["@mozilla.org/network/http-auth-manager;1"].getService(Components.interfaces.nsIHttpAuthManager); - let davjobs = { - cal : {valid: false, error: "", server: this.accountdata.caldavserver}, - card : {valid: false, error: "", server: this.accountdata.carddavserver}, - }; - - for (let job in davjobs) { - if (!davjobs[job].server) { - davjobs[job].valid = true; - continue; + if (this.serviceprovider == "discovery") { + while (this.server.endsWith("/")) { this.server = this.server.slice(0,-1); } + // the user may either specify a server or he could have entered an email with domain + let parts = (this.server || this.userdomain).split("://"); + let scheme = (parts.length > 1) ? parts[0].toLowerCase() : ""; + let host = parts[parts.length-1]; + + this.calDavServer = scheme + "caldav6764://" + host; + this.cardDavServer = scheme + "carddav6764://" + host; + this.validateDavServers(); + } else if (this.serviceprovider == "google") { + // do not verify, just prompt for permissions + this.promptForOAuth(); + } else { + // custom or service provider + this.validateDavServers(); + } + + event.preventDefault(); + }, + + promptForOAuth: async function() { + this.lockUI("validating"); + let oauthData = dav.network.getOAuthObj(this.calDavServer, { username: this.username, accountname: this.accountname }); + if (oauthData) { + let rv = {}; + if (await oauthData.asyncConnect(rv)) { + this.password = rv.tokens; + this.finalCalDavServer = this.calDavServer; + this.finalCardDavServer = this.cardDavServer; + this.finalUsername = this.username; + this.validated = true; + this.unlockUI(); + this.advance(); + return; + } else { + document.getElementById("tbsync.error.message").textContent = TbSync.getString("status." + rv.error, "dav"); + document.getElementById("tbsync.error").hidden = false; + this.unlockUI(); + return; } - - let connection = {}; - connection.password = this.accountdata.password; - connection.user = this.accountdata.user; - connection.https = this.accountdata.https; - connection.timeout = 15000; - connection.type = job; - connection.fqdn = ""; - //only needed for proper error reporting - connection.provider = "dav"; - connection.accountname = this.accountdata.accountname; - - //build full url, so we do not need fqdn - let url = "http" + (connection.https == "1" ? "s" : "") + "://" + davjobs[job].server; - - //clear credential cache, so the Channel will call nsIAuthPrompt2 and expose the realm (caldav and carddav could be on the same host but use different realms, so we reset for each type) - authenticationManager.clearAll(); + } + document.getElementById("tbsync.error.message").textContent = TbSync.getString("status.OAuthNetworkError", "dav"); + document.getElementById("tbsync.error").hidden = false; + this.unlockUI(); + }, + + validateDavServers: async function() { + this.lockUI("validating"); + + // Do not manipulate input here. + //while (this.calDavServer.endsWith("/")) { this.calDavServer = this.calDavServer.slice(0,-1); } + //while (this.cardDavServer.endsWith("/")) { this.cardDavServer = this.cardDavServer.slice(0,-1); } + + // Default to https, if http is not explicitly specified + if (this.calDavServer && !dav.network.startsWithScheme(this.calDavServer)) { + this.calDavServer = "https://" + this.calDavServer; + } + if (this.cardDavServer && !dav.network.startsWithScheme(this.cardDavServer)) { + this.cardDavServer = "https://" + this.cardDavServer; + } + + let davJobs = [ + {type: "caldav", server: this.calDavServer}, + {type: "carddav", server: this.cardDavServer}, + ]; - try { - let response = yield tbSync.dav.tools.sendRequest("", url , "PROPFIND", connection, {"Depth": "0", "Prefer": "return-minimal"}); - let principal = (response && response.multi) ? tbSync.dav.tools.getNodeTextContentFromMultiResponse(response, [["d","prop"], ["d","current-user-principal"], ["d","href"]]) : null; - davjobs[job].valid = (principal !== null); - if (!davjobs[job].valid) { - davjobs[job].error = job+"davservernotfound"; - } - } catch (e) { - davjobs[job].valid = false; - davjobs[job].error = e.message; - if (e.type == "dav4tbsync") { - tbSync.errorlog("warning", connection, e.message, e.details ? e.details : null); - } else { - Components.utils.reportError(e); - } + let failedDavJobs = []; + let validUserFound = ""; + + for (let job = 0; job < davJobs.length; job++) { + if (!davJobs[job].server) { + continue; + } + await this.checkUrlForPrincipal(davJobs[job]); + if (!davJobs[job].valid) { + failedDavJobs.push(job); + } else if (!validUserFound) { + // set the found user + validUserFound = davJobs[job].validUser; + } else if (validUserFound != davJobs[job].validUser) { + // users do not match + failedDavJobs.push(job); } } - if (davjobs.cal.valid && davjobs.card.valid) { - tbSyncDavNewAccount.addAccount(this.accountdata); - this.validating = false; - document.getElementById("tbsync.newaccount.wizard").cancel(); + if (failedDavJobs.length == 0) { + // boom, setup completed + this.finalCalDavServer = davJobs[0].validUrl || ""; + this.finalCardDavServer = davJobs[1].validUrl || ""; + this.finalUsername = validUserFound; + this.validated = true; + this.unlockUI(); + this.advance(); + return; } else { //only display one error - let badjob = !davjobs.cal.valid ? "cal" : "card"; - switch (davjobs[badjob].error.toString().split("::")[0]) { + let failedJob = failedDavJobs[0]; + console.log("ERROR ("+davJobs[failedJob].type+"): " + davJobs[failedJob].error.toString()); + switch (davJobs[failedJob].error.toString().split("::")[0]) { case "401": case "403": - case "404": - case "500": case "503": - case "network": case "security": - document.getElementById("tbsync.error.message").textContent = tbSync.getLocalizedMessage("info.error") + ": " + tbSync.getLocalizedMessage("status."+davjobs[badjob].error, "dav"); + document.getElementById("tbsync.error.message").textContent = TbSync.getString("status."+davJobs[failedJob].error, "dav"); break; default: - document.getElementById("tbsync.error.message").textContent = tbSync.getLocalizedMessage("info.error") + ": " + tbSync.getLocalizedMessage("status.networkerror", "dav"); + if (this.serviceprovider == "discovery" && this.userdomain && !this.server) { + // the discovery mode has a special error msg, in case a userdomain was used as server, but failed and we need the user to provide the server + document.getElementById("tbsync.error.message").textContent = TbSync.getString("status.rfc6764-lookup-failed::" +this.userdomain, "dav"); + } else if (this.serviceprovider != "discovery" && this.serviceprovider != "custom") { + // error msg, that the serviceprovider setup seems wrong + document.getElementById("tbsync.error.message").textContent = TbSync.getString("status.service-provider-setup-failed", "dav"); + } else if (dav.network.isRFC6764Request(davJobs[failedJob].server)) { + // error msg, that discovery mode failed + document.getElementById("tbsync.error.message").textContent = TbSync.getString("status.service-discovery-failed::" +davJobs[failedJob].server.split("://")[1], "dav"); + } else { + document.getElementById("tbsync.error.message").textContent = TbSync.getString("status." + davJobs[failedJob].type + "servernotfound", "dav"); + } } - - document.getElementById("tbsync.spinner").hidden = true; document.getElementById("tbsync.error").hidden = false; - document.getElementById("tbsync.newaccount.wizard").canRewind = true; - document.documentElement.getButton("finish").disabled = false; - this.validating = false; + this.unlockUI(); } - }), - - onClose: function () { - //disallow closing of wizard while validating - return !this.validating; }, - - onCancel: function () { - //disallow closing of wizard while validating - return !this.validating; - }, - - - addAccount (accountdata) { - let newAccountEntry = tbSync.dav.getDefaultAccountEntries(); - newAccountEntry.accountname = accountdata.accountname; - newAccountEntry.user = accountdata.user; - newAccountEntry.createdWithProviderVersion = tbSync.loadedProviders.dav.version; - - newAccountEntry.https = accountdata.https - newAccountEntry.serviceprovider = accountdata.serviceprovider; - newAccountEntry.host = accountdata.caldavserver; - newAccountEntry.host2 = accountdata.carddavserver; - - //also update password in PasswordManager - tbSync.dav.setPassword (newAccountEntry, accountdata.password); - - //create a new 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)); - - window.close(); - } }; diff -Nru dav4tbsync-0.15/content/manager/createAccount.xul dav4tbsync-1.9/content/manager/createAccount.xul --- dav4tbsync-0.15/content/manager/createAccount.xul 2019-02-26 16:26:01.000000000 +0000 +++ dav4tbsync-1.9/content/manager/createAccount.xul 2020-02-20 12:39:38.000000000 +0000 @@ -6,7 +6,6 @@ %davDTD; ]> -