pax_global_header 0000666 0000000 0000000 00000000064 13623463752 0014525 g ustar 00root root 0000000 0000000 52 comment=4b383b2371020303a564c110f24270567ce7375e
DAV-4-TbSync-1.8/ 0000775 0000000 0000000 00000000000 13623463752 0013350 5 ustar 00root root 0000000 0000000 DAV-4-TbSync-1.8/.github/ 0000775 0000000 0000000 00000000000 13623463752 0014710 5 ustar 00root root 0000000 0000000 DAV-4-TbSync-1.8/.github/issue_template.md 0000664 0000000 0000000 00000001063 13623463752 0020255 0 ustar 00root root 0000000 0000000 ## Your environment
TbSync version:
DAV-4-TbSync version:
Thunderbird version:
[ ] Yes, I have installed the latest available (beta) version from
- https://github.com/jobisoft/TbSync/releases
- https://github.com/jobisoft/DAV-4-TbSync/releases
and my issue is not yet fixed, I can still reproduce it.
## Expected behavior
...
## Actual behavior
...
## Steps to reproduce
...
To help resolving your issue, enable debug logging (TbSync Account Manager -> Help) and send me the debug.log via e-mail (use the title of your issue as subject of the email).
DAV-4-TbSync-1.8/.gitignore 0000664 0000000 0000000 00000000006 13623463752 0015334 0 ustar 00root root 0000000 0000000 *.xpi
DAV-4-TbSync-1.8/CONTRIBUTORS.md 0000664 0000000 0000000 00000000505 13623463752 0015627 0 ustar 00root root 0000000 0000000 ## 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)
DAV-4-TbSync-1.8/LICENSE 0000664 0000000 0000000 00000040525 13623463752 0014363 0 ustar 00root root 0000000 0000000 Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.
DAV-4-TbSync-1.8/Makebeta 0000664 0000000 0000000 00000002614 13623463752 0015007 0 ustar 00root root 0000000 0000000 #!/bin/bash
# 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/.
# $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
git clean -df
git checkout -- .
git pull
version=$(cat manifest.json | jq -r .version)
updatefile=update-dav.json
sed -i "s/%VERSION%/$version/g" "beta-release-channel-update.json"
sed -i "s|%LINK%|$1/$3|g" "beta-release-channel-update.json"
sed -i "s/static getApiVersion() { return \"/static getApiVersion() { return \"Beta /g" "content/provider.js"
echo "Releasing version $version via beta release channel (will include updateURL)"
sed -i "s|\"name\": \"__MSG_extensionName__\",|\"name\": \"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
DAV-4-TbSync-1.8/Makefile.bat 0000664 0000000 0000000 00000000657 13623463752 0015565 0 ustar 00root root 0000000 0000000 @echo off
REM This file is part of DAV-4-TbSync.
REM
REM This Source Code Form is subject to the terms of the Mozilla Public
REM License, v. 2.0. If a copy of the MPL was not distributed with this
REM file, You can obtain one at http://mozilla.org/MPL/2.0/.
del DAV-4-TbSync.xpi
"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
DAV-4-TbSync-1.8/README.md 0000664 0000000 0000000 00000004151 13623463752 0014630 0 ustar 00root root 0000000 0000000 # DAV-4-TbSync
This provider add-on adds CalDAV & CardDAV synchronization capabilities to [TbSync](https://github.com/jobisoft/TbSync/).
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/)
* 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/)
DAV-4-TbSync-1.8/_locales/ 0000775 0000000 0000000 00000000000 13623463752 0015131 5 ustar 00root root 0000000 0000000 DAV-4-TbSync-1.8/_locales/Readme.txt 0000664 0000000 0000000 00000000351 13623463752 0017066 0 ustar 00root root 0000000 0000000 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.
DAV-4-TbSync-1.8/_locales/bg/ 0000775 0000000 0000000 00000000000 13623463752 0015521 5 ustar 00root root 0000000 0000000 DAV-4-TbSync-1.8/_locales/bg/dav.dtd 0000664 0000000 0000000 00000007454 13623463752 0017002 0 ustar 00root root 0000000 0000000
DAV-4-TbSync-1.8/_locales/bg/dav.properties 0000664 0000000 0000000 00000020001 13623463752 0020402 0 ustar 00root root 0000000 0000000 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 при включена синхронизация на групите от контакти. Изключете едно от двете, докато грешката не бъде отстранена.
DAV-4-TbSync-1.8/_locales/bg/messages.json 0000664 0000000 0000000 00000000472 13623463752 0020226 0 ustar 00root root 0000000 0000000 {
"extensionName": {
"message": "Доставчик за CalDAV и CardDAV"
},
"extensionDescription": {
"message": "Разширява TbSync и позволява синхронизацията с CalDAV и CardDAV регистрации (контакти, задачи, календари)."
}
} DAV-4-TbSync-1.8/_locales/de/ 0000775 0000000 0000000 00000000000 13623463752 0015521 5 ustar 00root root 0000000 0000000 DAV-4-TbSync-1.8/_locales/de/dav.dtd 0000664 0000000 0000000 00000005461 13623463752 0016776 0 ustar 00root root 0000000 0000000
DAV-4-TbSync-1.8/_locales/de/dav.properties 0000664 0000000 0000000 00000014307 13623463752 0020416 0 ustar 00root root 0000000 0000000 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.
DAV-4-TbSync-1.8/_locales/de/messages.json 0000664 0000000 0000000 00000000356 13623463752 0020227 0 ustar 00root root 0000000 0000000 {
"extensionName": {
"message": "Provider für CalDAV & CardDAV"
},
"extensionDescription": {
"message": "Erweitert TbSync und erlaubt die Synchronisation von CalDAV & CardDAV Konten (Kontakte, Aufgaben und Kalender)."
}
} DAV-4-TbSync-1.8/_locales/en-US/ 0000775 0000000 0000000 00000000000 13623463752 0016060 5 ustar 00root root 0000000 0000000 DAV-4-TbSync-1.8/_locales/en-US/dav.dtd 0000664 0000000 0000000 00000005220 13623463752 0017326 0 ustar 00root root 0000000 0000000
DAV-4-TbSync-1.8/_locales/en-US/dav.properties 0000664 0000000 0000000 00000013071 13623463752 0020752 0 ustar 00root root 0000000 0000000 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.
DAV-4-TbSync-1.8/_locales/en-US/messages.json 0000664 0000000 0000000 00000000270 13623463752 0020561 0 ustar 00root root 0000000 0000000 {
"extensionName": {
"message": "Provider for CalDAV & CardDAV"
},
"extensionDescription": {
"message": "Add sync support for CalDAV & CardDAV accounts to TbSync."
}
}
DAV-4-TbSync-1.8/_locales/fr/ 0000775 0000000 0000000 00000000000 13623463752 0015540 5 ustar 00root root 0000000 0000000 DAV-4-TbSync-1.8/_locales/fr/dav.dtd 0000664 0000000 0000000 00000006070 13623463752 0017012 0 ustar 00root root 0000000 0000000
DAV-4-TbSync-1.8/_locales/fr/dav.properties 0000664 0000000 0000000 00000015077 13623463752 0020442 0 ustar 00root root 0000000 0000000 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.
DAV-4-TbSync-1.8/_locales/fr/messages.json 0000664 0000000 0000000 00000000300 13623463752 0020233 0 ustar 00root root 0000000 0000000 {
"extensionName": {
"message": "Provider pour CalDAV & CardDAV"
},
"extensionDescription": {
"message": "Ajoute à TbSync la prise en charge des comptes CalDAV & CardDAV."
}
} DAV-4-TbSync-1.8/_locales/hu/ 0000775 0000000 0000000 00000000000 13623463752 0015545 5 ustar 00root root 0000000 0000000 DAV-4-TbSync-1.8/_locales/hu/dav.dtd 0000664 0000000 0000000 00000005723 13623463752 0017023 0 ustar 00root root 0000000 0000000
DAV-4-TbSync-1.8/_locales/hu/dav.properties 0000664 0000000 0000000 00000013714 13623463752 0020443 0 ustar 00root root 0000000 0000000 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.
DAV-4-TbSync-1.8/_locales/hu/messages.json 0000664 0000000 0000000 00000000441 13623463752 0020246 0 ustar 00root root 0000000 0000000 {
"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)."
}
} DAV-4-TbSync-1.8/_locales/it/ 0000775 0000000 0000000 00000000000 13623463752 0015545 5 ustar 00root root 0000000 0000000 DAV-4-TbSync-1.8/_locales/it/dav.dtd 0000664 0000000 0000000 00000005471 13623463752 0017023 0 ustar 00root root 0000000 0000000
DAV-4-TbSync-1.8/_locales/it/dav.properties 0000664 0000000 0000000 00000013654 13623463752 0020446 0 ustar 00root root 0000000 0000000 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.
DAV-4-TbSync-1.8/_locales/it/messages.json 0000664 0000000 0000000 00000000317 13623463752 0020250 0 ustar 00root root 0000000 0000000 {
"extensionName": {
"message": "Provider CalDAV e CardDAV"
},
"extensionDescription": {
"message": "Aggiunge il supporto per la sincronizzazione degli account CalDAV e CardDAV a TbSync."
}
} DAV-4-TbSync-1.8/_locales/pl/ 0000775 0000000 0000000 00000000000 13623463752 0015544 5 ustar 00root root 0000000 0000000 DAV-4-TbSync-1.8/_locales/pl/dav.dtd 0000664 0000000 0000000 00000005360 13623463752 0017017 0 ustar 00root root 0000000 0000000
DAV-4-TbSync-1.8/_locales/pl/dav.properties 0000664 0000000 0000000 00000013624 13623463752 0020442 0 ustar 00root root 0000000 0000000 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.
DAV-4-TbSync-1.8/_locales/pl/messages.json 0000664 0000000 0000000 00000000273 13623463752 0020250 0 ustar 00root root 0000000 0000000 {
"extensionName": {
"message": "Dostawca dla CalDAV i CardDAV"
},
"extensionDescription": {
"message": "Dodaj do TbSync wsparcie synchronizacji dla CalDAV i CardDAV."
}
} DAV-4-TbSync-1.8/_locales/pt_BR/ 0000775 0000000 0000000 00000000000 13623463752 0016137 5 ustar 00root root 0000000 0000000 DAV-4-TbSync-1.8/_locales/pt_BR/dav.dtd 0000664 0000000 0000000 00000005635 13623463752 0017417 0 ustar 00root root 0000000 0000000
DAV-4-TbSync-1.8/_locales/pt_BR/dav.properties 0000664 0000000 0000000 00000014033 13623463752 0021030 0 ustar 00root root 0000000 0000000 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.
DAV-4-TbSync-1.8/_locales/pt_BR/messages.json 0000664 0000000 0000000 00000000301 13623463752 0020633 0 ustar 00root root 0000000 0000000 {
"extensionName": {
"message": "Provedor CalDAV e CardDAV"
},
"extensionDescription": {
"message": "Adiciona suporte para sincronizar contas CalDAV e CardDAV com o TbSync."
}
} DAV-4-TbSync-1.8/_locales/ru/ 0000775 0000000 0000000 00000000000 13623463752 0015557 5 ustar 00root root 0000000 0000000 DAV-4-TbSync-1.8/_locales/ru/dav.dtd 0000664 0000000 0000000 00000007504 13623463752 0017034 0 ustar 00root root 0000000 0000000
DAV-4-TbSync-1.8/_locales/ru/dav.properties 0000664 0000000 0000000 00000016011 13623463752 0020446 0 ustar 00root root 0000000 0000000 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.
DAV-4-TbSync-1.8/_locales/ru/messages.json 0000664 0000000 0000000 00000000564 13623463752 0020266 0 ustar 00root root 0000000 0000000 {
"extensionName": {
"message": "Provider for CalDAV & CardDAV"
},
"extensionDescription": {
"message": "Добавляет в TbSync поддержку базирующегося на http/https протокола синхронизации для учетных записей CalDAV & CardDAV (контакты, задачи и календари)."
}
} DAV-4-TbSync-1.8/beta-release-channel-update.json 0000664 0000000 0000000 00000000503 13623463752 0021460 0 ustar 00root root 0000000 0000000 {
"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" } } }
]
}
}
} DAV-4-TbSync-1.8/bootstrap.js 0000664 0000000 0000000 00000006331 13623463752 0015726 0 ustar 00root root 0000000 0000000 /*
* 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/.
*/
// no need to create namespace, we are in a sandbox
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
let thisID = "";
let component = {};
let onInitDoneObserver = {
observe: async function (aSubject, aTopic, aData) {
let valid = false;
try {
var { TbSync } = ChromeUtils.import("chrome://tbsync/content/tbsync.jsm");
valid = TbSync.enabled;
} catch (e) {
// If this fails, TbSync is not loaded yet and we will get the notification later again.
}
//load this provider add-on into TbSync
if (valid) {
await TbSync.providers.loadProvider(thisID, "dav", "chrome://dav4tbsync/content/provider.js");
}
}
}
function install(data, reason) {
}
function uninstall(data, reason) {
}
function startup(data, reason) {
// Possible reasons: APP_STARTUP, ADDON_ENABLE, ADDON_INSTALL, ADDON_UPGRADE, or ADDON_DOWNGRADE.
thisID = data.id;
Services.obs.addObserver(onInitDoneObserver, "tbsync.observer.initialized", false);
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) {
// 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;
}
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 {
var { TbSync } = ChromeUtils.import("chrome://tbsync/content/tbsync.jsm");
TbSync.providers.unloadProvider("dav");
} catch (e) {
//if this fails, TbSync has been unloaded already and has unloaded this addon as well
}
Services.obs.notifyObservers(null, "chrome-flush-caches", null);
}
DAV-4-TbSync-1.8/chrome.manifest 0000664 0000000 0000000 00000000675 13623463752 0016365 0 ustar 00root root 0000000 0000000 content dav4tbsync content/
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/
DAV-4-TbSync-1.8/content/ 0000775 0000000 0000000 00000000000 13623463752 0015022 5 ustar 00root root 0000000 0000000 DAV-4-TbSync-1.8/content/includes/ 0000775 0000000 0000000 00000000000 13623463752 0016630 5 ustar 00root root 0000000 0000000 DAV-4-TbSync-1.8/content/includes/abUI.js 0000664 0000000 0000000 00000051727 13623463752 0020022 0 ustar 00root root 0000000 0000000 /*
* 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;
}
},
}
DAV-4-TbSync-1.8/content/includes/network.js 0000664 0000000 0000000 00000062122 13623463752 0020662 0 ustar 00root root 0000000 0000000 /*
* 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);
});
}
}
DAV-4-TbSync-1.8/content/includes/sync.js 0000664 0000000 0000000 00000142247 13623463752 0020154 0 ustar 00root root 0000000 0000000 /*
/*
* 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);
},
}
DAV-4-TbSync-1.8/content/includes/tbSyncDavCalendar.js 0000664 0000000 0000000 00000017253 13623463752 0022525 0 ustar 00root root 0000000 0000000 /* 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);
};
DAV-4-TbSync-1.8/content/includes/tools.js 0000664 0000000 0000000 00000162204 13623463752 0020333 0 ustar 00root root 0000000 0000000 /*
* 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) {
card.addPhoto(TbSync.generateUUID(), vCardData[vCardField.item][0].value, type || "jpg");
} 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 rv = await dav.network.sendRequest("", vCardData[vCardField.item][0].value , "GET", connectionData, {}, {responseType: "base64"});
card.addPhoto(TbSync.generateUUID(), rv, type || this.getImageExtension(vCardData[vCardField.item][0].value), vCardData[vCardField.item][0].value);
} catch(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 = "";
try {
extension = filename.toString().split(".").pop().toUpperCase();
} catch (e) {}
return extension;
},
//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;
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};
},
}
DAV-4-TbSync-1.8/content/includes/vcard/ 0000775 0000000 0000000 00000000000 13623463752 0017727 5 ustar 00root root 0000000 0000000 DAV-4-TbSync-1.8/content/includes/vcard/LICENSE 0000664 0000000 0000000 00000002060 13623463752 0020732 0 ustar 00root root 0000000 0000000 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.
DAV-4-TbSync-1.8/content/includes/vcard/SOURCE 0000664 0000000 0000000 00000000065 13623463752 0020653 0 ustar 00root root 0000000 0000000 https://github.com/Heymdall/vcard/releases/tag/v0.2.7 DAV-4-TbSync-1.8/content/includes/vcard/vcard.js 0000664 0000000 0000000 00000024526 13623463752 0021375 0 ustar 00root root 0000000 0000000 (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);
DAV-4-TbSync-1.8/content/manager/ 0000775 0000000 0000000 00000000000 13623463752 0016434 5 ustar 00root root 0000000 0000000 DAV-4-TbSync-1.8/content/manager/createAccount.js 0000664 0000000 0000000 00000061435 13623463752 0021563 0 ustar 00root root 0000000 0000000 /*
* 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 { 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);
},
// HELPER FUNCTIONS
addProviderEntry: function (icon, serviceprovider) {
let name = TbSync.getString("add.serverprofile."+serviceprovider, "dav");
let description = TbSync.getString("add.serverprofile."+serviceprovider+".description", "dav");
//left column
let image = document.createXULElement("image");
image.setAttribute("src", "chrome://dav4tbsync/skin/" + icon);
image.setAttribute("style", "margin:1ex;");
let leftColumn = document.createXULElement("vbox");
leftColumn.appendChild(image);
//right column
let label = document.createXULElement("label");
label.setAttribute("class", "header");
label.setAttribute("value", name);
let desc = document.createXULElement("description");
desc.setAttribute("style", "width: 300px");
desc.textContent = description;
let rightColumn = document.createXULElement("vbox");
rightColumn.appendChild(label);
rightColumn.appendChild(desc);
//columns
let columns = document.createXULElement("hbox");
columns.appendChild(leftColumn);
columns.appendChild(rightColumn);
//richlistitem
let richlistitem = document.createXULElement("richlistitem");
richlistitem.setAttribute("style", "padding:4px");
richlistitem.setAttribute("value", serviceprovider);
richlistitem.appendChild(columns);
return richlistitem;
},
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;
}
}
return;
},
advance: function () {
document.getElementById("tbsync.newaccount.wizard").advance(null);
},
// RESET AND INIT FUNCTIONS
clearValues: function () {
//clear fields
this.username = "";
this.password = "";
this.server = "";
this.calDavServer = "";
this.cardDavServer = "";
if (this.serviceprovider == "discovery" || this.serviceprovider == "custom") {
this.accountname = "";
} else {
this.accountname = TbSync.getString("add.serverprofile." + this.serviceprovider, "dav");
}
},
resetFirstPage: function () {
// RESET / INIT first page
document.getElementById("tbsync.newaccount.wizard").canRewind = false;
document.getElementById("tbsync.newaccount.wizard").canAdvance = true;
this.isLocked = false;
this.validated = false;
},
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") : "");
//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." + this.serviceprovider + ".details" + i;
let dLocaleValue = TbSync.getString(dLocaleString, "dav");
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 {
dFound++;
dElement.textContent = dLocaleValue
dElement.hidden =false;
}
}
//hide Notes header, if no descriptions avail
let dLabel = document.getElementById("tbsync.newaccount.details.header");
dLabel.hidden = (dFound == 0);
//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;
} 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 {
// 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;
}
},
// 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
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;
}
}
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},
];
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 (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 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 "503":
case "security":
document.getElementById("tbsync.error.message").textContent = TbSync.getString("status."+davJobs[failedJob].error, "dav");
break;
default:
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.error").hidden = false;
this.unlockUI();
}
},
};
DAV-4-TbSync-1.8/content/manager/createAccount.xul 0000664 0000000 0000000 00000013712 13623463752 0021752 0 ustar 00root root 0000000 0000000
%tbsyncDTD;
%davDTD;
]>
&add.serverprofile.description;&add.data.description;&add.finish.description;&add.finish.details;