[frameworks/kdav] /: Add fetching DavPush data in DavCollectionsFetchJob

Dominique MICHEL <[email protected]>
Newsgroups gmane.comp.kde.cvs
Message-ID <[email protected]>
Git commit 69d31019eef975e0ad9661e7f367528ba7761b5f by Dominique MICHEL.
Committed on 24/07/2026 at 08:12.
Pushed by aloeil into branch 'master'.

Add fetching DavPush data in DavCollectionsFetchJob

M  +19   -2    autotests/data/dataitemmultifetchjob-caldav-collections.txt
M  +19   -2    autotests/data/davcollectionsfetchjob-caldav-color-collections.txt
M  +4    -1    autotests/data/davcollectionsfetchjob-collection-homeset-404.txt
M  +4    -1    autotests/data/davcollectionsfetchjob-fallback-404.txt
M  +5    -0    autotests/davcollectionsmultifetchjobtest.cpp
M  +16   -0    autotests/davcollectiontest.cpp
M  +3    -0    src/CMakeLists.txt
M  +12   -0    src/common/davcollection.cpp
M  +15   -0    src/common/davcollection.h
M  +7    -0    src/common/davcollectionsfetchjob.cpp
A  +65   -0    src/common/davpushsupport.cpp     [License: LGPL(v2.0+)]
A  +82   -0    src/common/davpushsupport.h     [License: LGPL(v2.0+)]
M  +27   -0    src/common/utils.cpp
M  +12   -0    src/common/utils_p.h
M  +9    -1    src/protocols/caldavprotocol.cpp

https://invent.kde.org/frameworks/kdav/-/commit/69d31019eef975e0ad9661e7f367528ba7761b5f

diff --git a/autotests/data/dataitemmultifetchjob-caldav-collections.txt b/autotests/data/dataitemmultifetchjob-caldav-collections.txt
index dce8f56..2b44fa1 100644
--- a/autotests/data/dataitemmultifetchjob-caldav-collections.txt
+++ b/autotests/data/dataitemmultifetchjob-caldav-collections.txt
@@ -1,6 +1,6 @@
 C: PROPFIND /caldav/dfaure%40example.com/ HTTP/1.1
 B: <?xml version="1.0" encoding="utf-8"?>
-B: <propfind xmlns="DAV:">
+B: <propfind xmlns="DAV:" xmlns:P="https://bitfire.at/webdav-push">
 B:  <prop xmlns="DAV:">
 B:   <displayname xmlns="DAV:"/>
 B:   <resourcetype xmlns="DAV:"/>
@@ -8,6 +8,9 @@ B:   <calendar-color xmlns="http://apple.com/ns/ical/"/>
 B:   <supported-calendar-component-set xmlns="urn:ietf:params:xml:ns:caldav"/>
 B:   <current-user-privilege-set xmlns="DAV:"/>
 B:   <getctag xmlns="http://calendarserver.org/ns/"/>
+B:   <P:transports xmlns:P="https://bitfire.at/webdav-push"/>
+B:   <P:topic xmlns:P="https://bitfire.at/webdav-push"/>
+B:   <P:supported-triggers xmlns:P="https://bitfire.at/webdav-push"/>
 B:  </prop>
 B: </propfind>
 S: HTTP/1.0 207 Multi-Status
@@ -16,7 +19,7 @@ S: Last-Modified: Wed, 04 Jan 2017 18:26:47 GMT
 S: DAV: 1, 3, extended-mkcol, access-control, calendarserver-principal-property-search, calendar-access, calendar-proxy, calendar-schedule, calendar-auto-schedule, addressbook, 2
 S: Content-Type: application/xml; charset=utf-8
 D: <?xml version="1.0" encoding="utf-8" ?>
-D: <multistatus xmlns="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
+D: <multistatus xmlns="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav" xmlns:P="https://bitfire.at/webdav-push">
 D:   <response xmlns="DAV:">
 D:    <href xmlns="DAV:">/caldav.php/test1.user/home/</href>
 D:    <propstat xmlns="DAV:">
@@ -40,6 +43,20 @@ D:            <read xmlns="DAV:"/>
 D:          </privilege>
 D:        </current-user-privilege-set>
 D:        <getctag xmlns="http://calendarserver.org/ns/">12345</getctag>
+D:        <P:transports>
+D:          <P:web-push>
+D:            <P:vapid-public-key type="p256ecdsa">TXlWYXBpZFB1YmxpY0tleQ</P:vapid-public-key>
+D:          </P:web-push>
+D:        </P:transports>
+D:        <P:topic>MyTopic-1</P:topic>
+D:        <P:supported-triggers>
+D:          <P:content-update>
+D:            <depth>1</depth>
+D:          </P:content-update>
+D:          <P:property-update>
+D:            <depth>0</depth>
+D:          </P:property-update>
+D:        </P:supported-triggers>
 D:      </prop>
 D:      <status xmlns="DAV:">HTTP/1.1 200 OK</status>
 D:    </propstat>
diff --git a/autotests/data/davcollectionsfetchjob-caldav-color-collections.txt b/autotests/data/davcollectionsfetchjob-caldav-color-collections.txt
index 278475d..8824451 100644
--- a/autotests/data/davcollectionsfetchjob-caldav-color-collections.txt
+++ b/autotests/data/davcollectionsfetchjob-caldav-color-collections.txt
@@ -1,6 +1,6 @@
 C: PROPFIND /caldav/dfaure%40example.com/ HTTP/1.1
 B: <?xml version="1.0" encoding="utf-8"?>
-B: <propfind xmlns="DAV:">
+B: <propfind xmlns="DAV:" xmlns:P="https://bitfire.at/webdav-push">
 B:  <prop xmlns="DAV:">
 B:   <displayname xmlns="DAV:"/>
 B:   <resourcetype xmlns="DAV:"/>
@@ -8,12 +8,15 @@ B:   <calendar-color xmlns="http://apple.com/ns/ical/"/>
 B:   <supported-calendar-component-set xmlns="urn:ietf:params:xml:ns:caldav"/>
 B:   <current-user-privilege-set xmlns="DAV:"/>
 B:   <getctag xmlns="http://calendarserver.org/ns/"/>
+B:   <P:transports xmlns:P="https://bitfire.at/webdav-push"/>
+B:   <P:topic xmlns:P="https://bitfire.at/webdav-push"/>
+B:   <P:supported-triggers xmlns:P="https://bitfire.at/webdav-push"/>
 B:  </prop>
 B: </propfind>
 S: HTTP/1.0 207 Multi-Status
 S: Content-Type: application/xml; charset=utf-8
 D: <?xml version="1.0" encoding="utf-8" ?>
-D: <multistatus xmlns="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav" xmlns:ical="http://apple.com/ns/ical/">
+D: <multistatus xmlns="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav" xmlns:ical="http://apple.com/ns/ical/" xmlns:P="https://bitfire.at/webdav-push">
 D:   <response xmlns="DAV:">
 D:    <href xmlns="DAV:">/caldav.php/test1.user/home/</href>
 D:    <propstat xmlns="DAV:">
@@ -28,6 +31,20 @@ D:        </resourcetype>
 D:        <displayname xmlns="DAV:">Color Calendar</displayname>
 D:        <ical:calendar-color xmlns:ical="http://apple.com/ns/ical/">#FF0000FF</ical:calendar-color>
 D:        <getctag xmlns="http://calendarserver.org/ns/">99999</getctag>
+D:        <P:transports>
+D:          <P:web-push>
+D:            <P:vapid-public-key>MyVapidPublicKey</P:vapid-public-key>
+D:          </P:web-push>
+D:        </P:transports>
+D:        <P:topic>MyTopic-1</P:topic>
+D:        <P:supported-triggers>
+D:          <P:content-update>
+D:            <depth>1</depth>
+D:          </P:content-update>
+D:          <P:property-update>
+D:            <depth>0</depth>
+D:          </P:property-update>
+D:        </P:supported-triggers>
 D:      </prop>
 D:      <status xmlns="DAV:">HTTP/1.1 200 OK</status>
 D:    </propstat>
diff --git a/autotests/data/davcollectionsfetchjob-collection-homeset-404.txt b/autotests/data/davcollectionsfetchjob-collection-homeset-404.txt
index 9b2e8e1..35a547f 100644
--- a/autotests/data/davcollectionsfetchjob-collection-homeset-404.txt
+++ b/autotests/data/davcollectionsfetchjob-collection-homeset-404.txt
@@ -1,6 +1,6 @@
 C: PROPFIND /caldav/dfaure%40example.com/ HTTP/1.1
 B: <?xml version="1.0" encoding="utf-8"?>
-B: <propfind xmlns="DAV:">
+B: <propfind xmlns="DAV:" xmlns:P="https://bitfire.at/webdav-push">
 B:  <prop xmlns="DAV:">
 B:   <displayname xmlns="DAV:"/>
 B:   <resourcetype xmlns="DAV:"/>
@@ -8,6 +8,9 @@ B:   <calendar-color xmlns="http://apple.com/ns/ical/"/>
 B:   <supported-calendar-component-set xmlns="urn:ietf:params:xml:ns:caldav"/>
 B:   <current-user-privilege-set xmlns="DAV:"/>
 B:   <getctag xmlns="http://calendarserver.org/ns/"/>
+B:   <P:transports xmlns:P="https://bitfire.at/webdav-push"/>
+B:   <P:topic xmlns:P="https://bitfire.at/webdav-push"/>
+B:   <P:supported-triggers xmlns:P="https://bitfire.at/webdav-push"/>
 B:  </prop>
 B: </propfind>
 S: HTTP/1.1 404 Not Found
diff --git a/autotests/data/davcollectionsfetchjob-fallback-404.txt b/autotests/data/davcollectionsfetchjob-fallback-404.txt
index 431dd26..1554d65 100644
--- a/autotests/data/davcollectionsfetchjob-fallback-404.txt
+++ b/autotests/data/davcollectionsfetchjob-fallback-404.txt
@@ -1,6 +1,6 @@
 C: PROPFIND /caldav HTTP/1.1
 B: <?xml version="1.0" encoding="utf-8"?>
-B: <propfind xmlns="DAV:">
+B: <propfind xmlns="DAV:" xmlns:P="https://bitfire.at/webdav-push">
 B:  <prop xmlns="DAV:">
 B:   <displayname xmlns="DAV:"/>
 B:   <resourcetype xmlns="DAV:"/>
@@ -8,6 +8,9 @@ B:   <calendar-color xmlns="http://apple.com/ns/ical/"/>
 B:   <supported-calendar-component-set xmlns="urn:ietf:params:xml:ns:caldav"/>
 B:   <current-user-privilege-set xmlns="DAV:"/>
 B:   <getctag xmlns="http://calendarserver.org/ns/"/>
+B:   <P:transports xmlns:P="https://bitfire.at/webdav-push"/>
+B:   <P:topic xmlns:P="https://bitfire.at/webdav-push"/>
+B:   <P:supported-triggers xmlns:P="https://bitfire.at/webdav-push"/>
 B:  </prop>
 B: </propfind>
 S: HTTP/1.1 404 Not Found
diff --git a/autotests/davcollectionsmultifetchjobtest.cpp b/autotests/davcollectionsmultifetchjobtest.cpp
index 6d895c2..66afeae 100644
--- a/autotests/davcollectionsmultifetchjobtest.cpp
+++ b/autotests/davcollectionsmultifetchjobtest.cpp
@@ -8,6 +8,7 @@
 #include "fakeserver.h"
 
 #include <KDAV/DavCollectionsMultiFetchJob>
+#include <KDAV/DavPushSupport>
 
 #include <QSignalSpy>
 #include <QTest>
@@ -60,6 +61,10 @@ void DavCollectionsMultiFetchJobTest::runSuccessfullTest()
     QCOMPARE(calendar.url().url().path(), QStringLiteral("/caldav.php/test1.user/home/"));
     QCOMPARE(calendar.CTag(), QStringLiteral("12345"));
     QCOMPARE(calendar.privileges(), KDAV::Read);
+    auto davPush = KDAV::DavPushSupport();
+    davPush.setTopic(QStringLiteral("MyTopic-1"));
+    davPush.setVapidPublicKey("TXlWYXBpZFB1YmxpY0tleQ");
+    QCOMPARE(calendar.davPushSupport(), davPush);
 
     const KDAV::DavCollection addressbook = collections.at(cardDavIdx);
     QCOMPARE(addressbook.displayName(), QStringLiteral("My Address Book"));
diff --git a/autotests/davcollectiontest.cpp b/autotests/davcollectiontest.cpp
index ce28f99..67cbefb 100644
--- a/autotests/davcollectiontest.cpp
+++ b/autotests/davcollectiontest.cpp
@@ -7,6 +7,7 @@
 #include "davcollectiontest.h"
 
 #include <KDAV/DavCollection>
+#include <KDAV/DavPushSupport>
 #include <KDAV/DavUrl>
 
 #include <QColor>
@@ -22,6 +23,7 @@ void DavCollectionTest::createEmpty()
     QCOMPARE(davCollection.color(), QColor());
     QCOMPARE(davCollection.contentTypes(), KDAV::DavCollection::ContentTypes());
     QCOMPARE(davCollection.privileges(), KDAV::Privileges());
+    QCOMPARE(davCollection.davPushSupport(), KDAV::DavPushSupport());
 }
 
 void DavCollectionTest::storeTest()
@@ -43,6 +45,11 @@ void DavCollectionTest::setTest()
 {
     QUrl url(QStringLiteral("test://me:pw@test"));
     KDAV::DavUrl davUrl(url, KDAV::CardDav);
+
+    KDAV::DavPushSupport davPush{};
+    davPush.setVapidPublicKey("TXlWYXBpZFB1YmxpY0tleQ");
+    davPush.setTopic(QLatin1StringView("Topic"));
+
     KDAV::DavCollection davCollection;
 
     davCollection.setUrl(davUrl);
@@ -51,6 +58,7 @@ void DavCollectionTest::setTest()
     davCollection.setColor(QColor(1, 2, 3));
     davCollection.setContentTypes(KDAV::DavCollection::Events | KDAV::DavCollection::Todos);
     davCollection.setPrivileges(KDAV::Read | KDAV::Write);
+    davCollection.setDavPushSupport(davPush);
 
     QCOMPARE(davCollection.url().protocol(), KDAV::CardDav);
     QCOMPARE(davCollection.url().url(), url);
@@ -59,6 +67,7 @@ void DavCollectionTest::setTest()
     QCOMPARE(davCollection.color(), QColor(1, 2, 3));
     QCOMPARE(davCollection.contentTypes(), KDAV::DavCollection::Events | KDAV::DavCollection::Todos);
     QCOMPARE(davCollection.privileges(), KDAV::Read | KDAV::Write);
+    QCOMPARE(davCollection.davPushSupport(), davPush);
 }
 
 void DavCollectionTest::copyTest()
@@ -68,12 +77,17 @@ void DavCollectionTest::copyTest()
     QUrl url(QStringLiteral("test://me:pw@test"));
     KDAV::DavUrl davUrl(url, KDAV::CardDav);
 
+    KDAV::DavPushSupport davPush{};
+    davPush.setVapidPublicKey("TXlWYXBpZFB1YmxpY0tleQ");
+    davPush.setTopic(QLatin1StringView("Topic"));
+
     davCollection.setUrl(davUrl);
     davCollection.setCTag(QStringLiteral("ctag"));
     davCollection.setDisplayName(QStringLiteral("myname"));
     davCollection.setColor(QColor(1, 2, 3));
     davCollection.setContentTypes(KDAV::DavCollection::Events | KDAV::DavCollection::Todos);
     davCollection.setPrivileges(KDAV::Read | KDAV::Write);
+    davCollection.setDavPushSupport(davPush);
 
     KDAV::DavCollection copy1(davCollection);
     QCOMPARE(copy1.url().protocol(), davCollection.url().protocol());
@@ -83,6 +97,7 @@ void DavCollectionTest::copyTest()
     QCOMPARE(copy1.color(), davCollection.color());
     QCOMPARE(copy1.contentTypes(), davCollection.contentTypes());
     QCOMPARE(copy1.privileges(), davCollection.privileges());
+    QCOMPARE(copy1.davPushSupport(), davPush);
 
     KDAV::DavCollection copy2;
     copy2 = davCollection;
@@ -94,6 +109,7 @@ void DavCollectionTest::copyTest()
     QCOMPARE(copy2.color(), davCollection.color());
     QCOMPARE(copy2.contentTypes(), davCollection.contentTypes());
     QCOMPARE(copy2.privileges(), davCollection.privileges());
+    QCOMPARE(copy2.davPushSupport(), davPush);
 }
 
 QTEST_MAIN(DavCollectionTest)
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index fe910d4..547c7ce 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -14,6 +14,8 @@ set_target_properties(KF6DAV PROPERTIES
 target_sources(KF6DAV PRIVATE
     common/davcollection.cpp
     common/davcollection.h
+    common/davpushsupport.cpp
+    common/davpushsupport.h
     common/davcollectioncreatejob.cpp
     common/davcollectioncreatejob.h
     common/davcollectiondeletejob.cpp
@@ -80,6 +82,7 @@ ecm_generate_headers(KDAV_Camelcase_HEADERS
     HEADER_NAMES
     DavJobBase
     DavCollection
+    DavPushSupport
     DavCollectionDeleteJob
     DavCollectionsFetchJob
     DavCollectionCreateJob
diff --git a/src/common/davcollection.cpp b/src/common/davcollection.cpp
index 0ede17c..c25e011 100644
--- a/src/common/davcollection.cpp
+++ b/src/common/davcollection.cpp
@@ -7,6 +7,7 @@
 
 #include "davcollection.h"
 
+#include "davpushsupport.h"
 #include "davurl.h"
 
 #include <QColor>
@@ -23,6 +24,7 @@ public:
     QColor mColor;
     Privileges mPrivileges;
     QByteArray mTimeZone;
+    DavPushSupport mDavPushSupport;
 };
 
 DavCollection::DavCollection()
@@ -114,3 +116,13 @@ QByteArray DavCollection::timeZone() const
 {
     return d->mTimeZone;
 }
+
+void DavCollection::setDavPushSupport(const DavPushSupport &pushSupport)
+{
+    d->mDavPushSupport = pushSupport;
+}
+
+DavPushSupport DavCollection::davPushSupport() const
+{
+    return d->mDavPushSupport;
+}
diff --git a/src/common/davcollection.h b/src/common/davcollection.h
index 4af1eca..2860a4a 100644
--- a/src/common/davcollection.h
+++ b/src/common/davcollection.h
@@ -21,6 +21,7 @@ class DavCollectionPrivate;
 
 namespace KDAV
 {
+class DavPushSupport;
 class DavUrl;
 }
 
@@ -170,6 +171,20 @@ public:
      */
     [[nodiscard]] QByteArray timeZone() const;
 
+    /*!
+     * Sets the DavPushSupport on this calendar collection.
+     *
+     * \since 6.29
+     */
+    void setDavPushSupport(const DavPushSupport &pushSupport);
+
+    /*!
+     * Returns the DavPushSupport of this calendar collection, is Null if not set.
+     *
+     * \since 6.29
+     */
+    [[nodiscard]] DavPushSupport davPushSupport() const;
+
 private:
     QSharedDataPointer<DavCollectionPrivate> d;
 };
diff --git a/src/common/davcollectionsfetchjob.cpp b/src/common/davcollectionsfetchjob.cpp
index 7de7251..3ecd06d 100644
--- a/src/common/davcollectionsfetchjob.cpp
+++ b/src/common/davcollectionsfetchjob.cpp
@@ -14,6 +14,7 @@
 #include "utils_p.h"
 
 #include "libkdav_debug.h"
+#include <KDAV/DavPushSupport>
 #include <KIO/DavJob>
 #include <KIO/Job>
 
@@ -335,6 +336,12 @@ void DavCollectionsFetchJobPrivate::collectionsFetchFinished(KJob *job)
                     collection.setPrivileges(privileges);
                 }
 
+                // Extract Dav Push notification information
+                auto pushSupport = Utils::extractDavPushSupport(propElement);
+                if (pushSupport) {
+                    collection.setDavPushSupport(*pushSupport);
+                }
+
                 qCDebug(KDAV_LOG) << url.toDisplayString() << "PRIVS: " << collection.privileges();
                 mCollections << collection;
                 Q_EMIT q->collectionDiscovered(mUrl.protocol(), url.toDisplayString(), jobUrl);
diff --git a/src/common/davpushsupport.cpp b/src/common/davpushsupport.cpp
new file mode 100644
index 0000000..a9cb66f
--- /dev/null
+++ b/src/common/davpushsupport.cpp
@@ -0,0 +1,65 @@
+/*
+    SPDX-FileCopyrightText: 2026 Dominique Michel <[email protected]>
+
+    SPDX-License-Identifier: LGPL-2.0-or-later
+*/
+
+#include "davpushsupport.h"
+
+using namespace KDAV;
+
+class DavPushSupportPrivate : public QSharedData
+{
+public:
+    bool operator==(const DavPushSupportPrivate &rhs) const;
+
+public:
+    QString topic;
+    QByteArray vapidPublicKey;
+};
+
+bool DavPushSupportPrivate::operator==(const DavPushSupportPrivate &rhs) const
+{
+    return std::tie(topic, vapidPublicKey) == std::tie(rhs.topic, rhs.vapidPublicKey);
+}
+
+DavPushSupport::DavPushSupport()
+    : d(new DavPushSupportPrivate)
+{
+}
+
+DavPushSupport::DavPushSupport(const DavPushSupport &other) = default;
+DavPushSupport::DavPushSupport(DavPushSupport &&) = default;
+DavPushSupport &DavPushSupport::operator=(const DavPushSupport &other) = default;
+DavPushSupport &DavPushSupport::operator=(DavPushSupport &&) = default;
+DavPushSupport::~DavPushSupport() = default;
+
+bool DavPushSupport::operator==(const DavPushSupport &other) const
+{
+    return (*d) == (*other.d);
+}
+
+bool DavPushSupport::isValid() const
+{
+    return !d->topic.isEmpty();
+}
+
+void DavPushSupport::setTopic(const QString &topic)
+{
+    d->topic = topic;
+}
+
+QString DavPushSupport::topic() const
+{
+    return d->topic;
+}
+
+void DavPushSupport::setVapidPublicKey(const QByteArray &vapidPublicKey)
+{
+    d->vapidPublicKey = vapidPublicKey;
+}
+
+QByteArray DavPushSupport::vapidPublicKey() const
+{
+    return d->vapidPublicKey;
+}
diff --git a/src/common/davpushsupport.h b/src/common/davpushsupport.h
new file mode 100644
index 0000000..54ba345
--- /dev/null
+++ b/src/common/davpushsupport.h
@@ -0,0 +1,82 @@
+/*
+    SPDX-FileCopyrightText: 2026 Dominique Michel <[email protected]>
+
+    SPDX-License-Identifier: LGPL-2.0-or-later
+*/
+
+#ifndef KDAV_DAVPUSH_H
+#define KDAV_DAVPUSH_H
+
+#include "kdav_export.h"
+
+#include <QSharedDataPointer>
+
+class DavPushSupportPrivate;
+
+namespace KDAV
+{
+/*!
+ * \class KDAV::DavPushSupport
+ * \inheaderfile KDAV/DavPushSupport
+ * \inmodule KDAV
+ *
+ * \brief A helper class to store information about Support of DavPush.
+ *
+ * This class is used as a container to transfer DavPush support of
+ * DavCollections from the dav server.
+ * \sa https://bitfireat.github.io/webdav-push/draft-bitfire-webdav-push-00.html
+ *
+ * \since 6.29
+ */
+class KDAV_EXPORT DavPushSupport
+{
+public:
+    /*!
+     * Creates an empty DAV Push.
+     */
+    DavPushSupport();
+
+    DavPushSupport(const DavPushSupport &other);
+    DavPushSupport(DavPushSupport &&);
+    DavPushSupport &operator=(const DavPushSupport &other);
+    DavPushSupport &operator=(DavPushSupport &&);
+    ~DavPushSupport();
+
+    bool operator==(const DavPushSupport &) const;
+
+    /*!
+     * Returns true if this DavPushSupport is valid
+     */
+    [[nodiscard]] bool isValid() const;
+
+    /*!
+     * Sets this push's topic
+     */
+    void setTopic(const QString &topic);
+
+    /*!
+     * Returns this push's topic, empty if no value was found
+     */
+    [[nodiscard]] QString topic() const;
+
+    /*!
+     * Sets this push's vapid public key
+     * vapid must be in base64url (not base64)
+     */
+    void setVapidPublicKey(const QByteArray &vapidPublicKey);
+
+    /*!
+     * Returns this push's vapid public key, empty if no value was found
+     * vapid format is returned in base64url (not base64)
+     */
+    [[nodiscard]] QByteArray vapidPublicKey() const;
+
+private:
+    QSharedDataPointer<DavPushSupportPrivate> d;
+};
+
+}
+
+Q_DECLARE_TYPEINFO(KDAV::DavPushSupport, Q_RELOCATABLE_TYPE);
+
+#endif
diff --git a/src/common/utils.cpp b/src/common/utils.cpp
index ea76016..9960088 100644
--- a/src/common/utils.cpp
+++ b/src/common/utils.cpp
@@ -6,6 +6,7 @@
 
 #include "utils_p.h"
 
+#include "davpushsupport.h"
 #include "enums.h"
 
 #include <QString>
@@ -101,3 +102,29 @@ Privileges Utils::parsePrivilege(const QDomElement &element)
 
     return final;
 }
+
+std::optional<DavPushSupport> Utils::extractDavPushSupport(const QDomElement &element)
+{
+    constexpr auto davPushNS = QLatin1StringView("https://bitfire.at/webdav-push");
+    const auto topicElt = firstChildElementNS(element, davPushNS, QStringLiteral("topic"));
+    const auto transportsElt = firstChildElementNS(element, davPushNS, QStringLiteral("transports"));
+    // TODO: supported-triggers is missing from the nextcloud reference implementation, let's ignore it for now
+
+    if (topicElt.isNull() || transportsElt.isNull()) {
+        return {};
+    }
+
+    const auto webPushElt = firstChildElementNS(transportsElt, davPushNS, QStringLiteral("web-push"));
+    if (webPushElt.isNull()) {
+        return {};
+    }
+    const auto vapidPubKeyElt = firstChildElementNS(webPushElt, davPushNS, QStringLiteral("vapid-public-key"));
+    if (vapidPubKeyElt.isNull()) {
+        return {};
+    }
+
+    auto res = DavPushSupport();
+    res.setTopic(topicElt.text());
+    res.setVapidPublicKey(vapidPubKeyElt.text().toUtf8());
+    return res;
+}
diff --git a/src/common/utils_p.h b/src/common/utils_p.h
index 533af1d..eade085 100644
--- a/src/common/utils_p.h
+++ b/src/common/utils_p.h
@@ -11,6 +11,11 @@
 
 #include <QDomElement>
 
+namespace KDAV
+{
+class DavPushSupport;
+}
+
 namespace KDAV
 {
 namespace Utils
@@ -39,6 +44,13 @@ Q_REQUIRED_RESULT Privileges extractPrivileges(const QDomElement &element);
  */
 Q_REQUIRED_RESULT Privileges parsePrivilege(const QDomElement &element);
 
+/*!
+ * \internal
+ * Extracts DavPushSupport from @p element, element being a <prop> tag.
+ *
+ * \since 6.29
+ */
+[[nodiscard]] std::optional<DavPushSupport> extractDavPushSupport(const QDomElement &element);
 }
 }
 
diff --git a/src/protocols/caldavprotocol.cpp b/src/protocols/caldavprotocol.cpp
index 187e807..008b445 100644
--- a/src/protocols/caldavprotocol.cpp
+++ b/src/protocols/caldavprotocol.cpp
@@ -24,9 +24,12 @@ class CaldavCollectionQueryBuilder : public XMLQueryBuilder
 public:
     QDomDocument buildQuery() const override
     {
+        constexpr auto davPushNs = QLatin1StringView("https://bitfire.at/webdav-push");
+
         QDomDocument document;
 
         QDomElement propfindElement = document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("propfind"));
+        propfindElement.setAttribute(QStringLiteral("xmlns:P"), davPushNs);
         document.appendChild(propfindElement);
 
         QDomElement propElement = document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("prop"));
@@ -39,6 +42,11 @@ public:
         propElement.appendChild(document.createElementNS(QStringLiteral("DAV:"), QStringLiteral("current-user-privilege-set")));
         propElement.appendChild(document.createElementNS(QStringLiteral("http://calendarserver.org/ns/"), QStringLiteral("getctag")));
 
+        // Add discovery of push notifications
+        propElement.appendChild(document.createElementNS(davPushNs, QStringLiteral("P:transports")));
+        propElement.appendChild(document.createElementNS(davPushNs, QStringLiteral("P:topic")));
+        propElement.appendChild(document.createElementNS(davPushNs, QStringLiteral("P:supported-triggers")));
+
         return document;
     }
 
@@ -438,7 +446,7 @@ void CaldavProtocol::writeMkCol(QXmlStreamWriter &writer, KDAV::DavCollection &c
 
     if (collection.color().isValid()) {
         // If it's ARGB, we need to print it in RGBA format
-        const auto color = [&] -> QString {
+        const auto color = [&]() -> QString {
             if (collection.color().alpha() == 255) {
                 return collection.color().name(QColor::HexRgb);
             }
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.