├── .github ├── FUNDING.yml ├── workflows │ ├── test.yml │ └── build-kdoc.yml └── dependabot.yml ├── gradle.properties ├── gradle ├── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties └── libs.versions.toml ├── CHANGELOG.md ├── AUTHORS ├── .idea ├── codeStyles │ ├── codeStyleConfig.xml │ └── Project.xml └── copyright │ ├── profiles_settings.xml │ └── MPL.xml ├── SECURITY.md ├── src ├── main │ └── kotlin │ │ └── at │ │ └── bitfire │ │ └── dav4jvm │ │ ├── property │ │ ├── push │ │ │ ├── PushTransport.kt │ │ │ ├── Topic.kt │ │ │ ├── AuthSecret.kt │ │ │ ├── VapidPublicKey.kt │ │ │ ├── SubscriptionPublicKey.kt │ │ │ ├── PushResource.kt │ │ │ ├── Subscription.kt │ │ │ ├── WebPush.kt │ │ │ ├── PushTransports.kt │ │ │ ├── Trigger.kt │ │ │ ├── WebDAVPush.kt │ │ │ ├── PropertyUpdate.kt │ │ │ ├── SupportedTriggers.kt │ │ │ ├── ContentUpdate.kt │ │ │ ├── PushMessage.kt │ │ │ ├── WebPushSubscription.kt │ │ │ └── PushRegister.kt │ │ ├── caldav │ │ │ ├── Source.kt │ │ │ ├── GetCTag.kt │ │ │ ├── CalendarHomeSet.kt │ │ │ ├── CalendarProxyReadFor.kt │ │ │ ├── MaxResourceSize.kt │ │ │ ├── CalendarProxyWriteFor.kt │ │ │ ├── CalendarUserAddressSet.kt │ │ │ ├── CalendarTimezone.kt │ │ │ ├── CalendarTimezoneId.kt │ │ │ ├── CalendarDescription.kt │ │ │ ├── CalendarData.kt │ │ │ ├── SupportedCalendarData.kt │ │ │ ├── ScheduleTag.kt │ │ │ ├── CalendarColor.kt │ │ │ ├── SupportedCalendarComponentSet.kt │ │ │ └── CalDAV.kt │ │ ├── webdav │ │ │ ├── GroupMembership.kt │ │ │ ├── CreationDate.kt │ │ │ ├── QuotaUsedBytes.kt │ │ │ ├── GetContentLength.kt │ │ │ ├── Owner.kt │ │ │ ├── SyncToken.kt │ │ │ ├── QuotaAvailableBytes.kt │ │ │ ├── GetContentType.kt │ │ │ ├── DisplayName.kt │ │ │ ├── AddMember.kt │ │ │ ├── GetLastModified.kt │ │ │ ├── SyncLevel.kt │ │ │ ├── CurrentUserPrincipal.kt │ │ │ ├── Depth.kt │ │ │ ├── SupportedReportSet.kt │ │ │ ├── ResourceType.kt │ │ │ ├── GetETag.kt │ │ │ ├── CurrentUserPrivilegeSet.kt │ │ │ └── WebDAV.kt │ │ ├── carddav │ │ │ ├── AddressbookHomeSet.kt │ │ │ ├── MaxResourceSize.kt │ │ │ ├── AddressbookDescription.kt │ │ │ ├── AddressData.kt │ │ │ ├── CardDAV.kt │ │ │ └── SupportedAddressData.kt │ │ └── common │ │ │ └── HrefListProperty.kt │ │ ├── okhttp │ │ ├── exception │ │ │ ├── GoneException.kt │ │ │ ├── ConflictException.kt │ │ │ ├── ForbiddenException.kt │ │ │ ├── NotFoundException.kt │ │ │ ├── UnauthorizedException.kt │ │ │ ├── PreconditionFailedException.kt │ │ │ ├── HttpException.kt │ │ │ ├── DavException.kt │ │ │ └── ServiceUnavailableException.kt │ │ ├── CallbackInterfaces.kt │ │ ├── OkHttpUtils.kt │ │ └── PropStat.kt │ │ ├── ktor │ │ ├── PropStat.kt │ │ ├── exception │ │ │ ├── GoneException.kt │ │ │ ├── ConflictException.kt │ │ │ ├── NotFoundException.kt │ │ │ ├── ForbiddenException.kt │ │ │ ├── UnauthorizedException.kt │ │ │ ├── PreconditionFailedException.kt │ │ │ ├── DavException.kt │ │ │ └── ServiceUnavailableException.kt │ │ ├── PropStatParser.kt │ │ ├── CallbackInterfaces.kt │ │ ├── MultiStatusParser.kt │ │ └── Response.kt │ │ ├── PropertyFactory.kt │ │ ├── QuotedStringUtils.kt │ │ ├── Error.kt │ │ ├── Property.kt │ │ ├── XmlUtils.kt │ │ └── HttpUtils.kt └── test │ └── kotlin │ └── at │ └── bitfire │ └── dav4jvm │ ├── XmlUtilsTest.kt │ ├── ktor │ ├── ErrorTest.kt │ ├── ResponseParserTest.kt │ ├── DavCalendarTest.kt │ ├── PropertyTest.kt │ ├── exception │ │ ├── ServiceUnavailableExceptionTest.kt │ │ └── DavExceptionTest.kt │ └── UrlUtilsTest.kt │ ├── okhttp │ ├── ErrorTest.kt │ ├── property │ │ ├── PropertyTest.kt │ │ ├── CalendarDescriptionTest.kt │ │ ├── OwnerTest.kt │ │ └── GetETagTest.kt │ ├── OkHttpUtilsTest.kt │ ├── QuotedStringUtilsTest.kt │ ├── DavCalendarTest.kt │ ├── exception │ │ ├── ServiceUnavailableExceptionTest.kt │ │ └── DavExceptionTest.kt │ └── UrlUtilsTest.kt │ ├── property │ ├── PropertyTest.kt │ ├── CalendarDescriptionTest.kt │ ├── OwnerTest.kt │ └── GetETagTest.kt │ ├── PropertyTest.kt │ └── HttpUtilsTest.kt ├── .gitignore └── gradlew.bat /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | 2 | github: bitfireAT 3 | custom: https://www.davx5.com/donate 4 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Dokka 2 | org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled 3 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfireAT/dav4jvm/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | See https://github.com/bitfireAT/dav4jvm/compare/, for instance 3 | https://github.com/bitfireAT/dav4jvm/compare/2.1.2...2.1.3 4 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | You can view the list of people who have contributed to the code base in the version control history: 2 | https://github.com/bitfireAT/dav4jvm/graphs/contributors 3 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Reporting a Vulnerability 4 | 5 | Please report security vulnerabilities using the [DAVx⁵ support form](https://www.davx5.com/support) or via email to support@davx5.com. 6 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | on: push 3 | jobs: 4 | test: 5 | name: Run tests 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v6 9 | - uses: actions/setup-java@v5 10 | with: 11 | distribution: temurin 12 | java-version: 17 13 | - uses: gradle/actions/setup-gradle@v5 14 | 15 | - name: Check 16 | run: ./gradlew --no-daemon check 17 | 18 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 9 | 10 | 13 | 14 | -------------------------------------------------------------------------------- /.idea/copyright/MPL.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/push/PushTransport.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.property.push 12 | 13 | import at.bitfire.dav4jvm.Property 14 | 15 | /** 16 | * Identifies a property as a push transport. 17 | */ 18 | interface PushTransport: Property 19 | -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/okhttp/exception/GoneException.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.okhttp.exception 12 | 13 | import okhttp3.Response 14 | 15 | class GoneException: HttpException { 16 | 17 | constructor(response: Response) : super(response) { 18 | if (response.code != 410) 19 | throw IllegalArgumentException("Status code must be 410") 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/okhttp/exception/ConflictException.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.okhttp.exception 12 | 13 | import okhttp3.Response 14 | 15 | class ConflictException: HttpException { 16 | 17 | constructor(response: Response) : super(response) { 18 | if (response.code != 409) 19 | throw IllegalArgumentException("Status code must be 409") 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/okhttp/exception/ForbiddenException.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.okhttp.exception 12 | 13 | import okhttp3.Response 14 | 15 | class ForbiddenException: HttpException { 16 | 17 | constructor(response: Response) : super(response) { 18 | if (response.code != 403) 19 | throw IllegalArgumentException("Status code must be 403") 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/okhttp/exception/NotFoundException.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.okhttp.exception 12 | 13 | import okhttp3.Response 14 | 15 | class NotFoundException : HttpException { 16 | 17 | constructor(response: Response) : super(response) { 18 | if (response.code != 404) 19 | throw IllegalArgumentException("Status code must be 404") 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/okhttp/exception/UnauthorizedException.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.okhttp.exception 12 | 13 | import okhttp3.Response 14 | 15 | class UnauthorizedException: HttpException { 16 | 17 | constructor(response: Response) : super(response) { 18 | if (response.code != 401) 19 | throw IllegalArgumentException("Status code must be 401") 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/okhttp/exception/PreconditionFailedException.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.okhttp.exception 12 | 13 | import okhttp3.Response 14 | 15 | class PreconditionFailedException: HttpException { 16 | 17 | constructor(response: Response) : super(response) { 18 | if (response.code != 412) 19 | throw IllegalArgumentException("Status code must be 412") 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /src/test/kotlin/at/bitfire/dav4jvm/XmlUtilsTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm 12 | 13 | import org.junit.Assert.assertNotNull 14 | import org.junit.Test 15 | 16 | class XmlUtilsTest { 17 | 18 | @Test 19 | fun newPullParser() { 20 | assertNotNull(XmlUtils.newPullParser()) 21 | } 22 | 23 | @Test 24 | fun newSerializer() { 25 | assertNotNull(XmlUtils.newSerializer()) 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 | 6 | version: 2 7 | updates: 8 | 9 | - package-ecosystem: "github-actions" 10 | directory: "/" 11 | schedule: 12 | interval: "weekly" 13 | commit-message: 14 | prefix: "[CI] " 15 | groups: 16 | ci-actions: 17 | patterns: ["*"] 18 | 19 | - package-ecosystem: "gradle" 20 | directory: "/" 21 | schedule: 22 | interval: "weekly" 23 | groups: 24 | lib-dependencies: 25 | patterns: ["*"] 26 | -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/ktor/PropStat.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.ktor 12 | 13 | import at.bitfire.dav4jvm.Error 14 | import at.bitfire.dav4jvm.Property 15 | import io.ktor.http.HttpStatusCode 16 | 17 | /** 18 | * Represents a WebDAV propstat XML element. 19 | * 20 | * 21 | */ 22 | data class PropStat( 23 | val properties: List, 24 | val status: HttpStatusCode, 25 | val error: List? = null 26 | ) -------------------------------------------------------------------------------- /src/test/kotlin/at/bitfire/dav4jvm/ktor/ErrorTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.ktor 12 | 13 | import at.bitfire.dav4jvm.Error 14 | import at.bitfire.dav4jvm.Property 15 | import at.bitfire.dav4jvm.property.webdav.WebDAV 16 | import org.junit.Assert.assertTrue 17 | import org.junit.Test 18 | 19 | class ErrorTest { 20 | 21 | @Test 22 | fun testEquals() { 23 | val errors = listOf(Error(Property.Name("DAV:", "valid-sync-token"))) 24 | assertTrue(errors.contains(Error(WebDAV.ValidSyncToken))) 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /src/test/kotlin/at/bitfire/dav4jvm/okhttp/ErrorTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.okhttp 12 | 13 | import at.bitfire.dav4jvm.Error 14 | import at.bitfire.dav4jvm.Property 15 | import at.bitfire.dav4jvm.property.webdav.WebDAV 16 | import org.junit.Assert.assertTrue 17 | import org.junit.Test 18 | 19 | class ErrorTest { 20 | 21 | @Test 22 | fun testEquals() { 23 | val errors = listOf(Error(Property.Name("DAV:", "valid-sync-token"))) 24 | assertTrue(errors.contains(Error(WebDAV.ValidSyncToken))) 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/caldav/Source.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.property.caldav 12 | 13 | import at.bitfire.dav4jvm.property.common.HrefListProperty 14 | import org.xmlpull.v1.XmlPullParser 15 | 16 | class Source( 17 | override val hrefs: List = emptyList() 18 | ): HrefListProperty(hrefs) { 19 | 20 | object Factory: HrefListProperty.Factory() { 21 | 22 | override fun getName() = CalDAV.Source 23 | 24 | override fun create(parser: XmlPullParser) = create(parser, ::Source) 25 | 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/caldav/GetCTag.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.property.caldav 12 | 13 | import at.bitfire.dav4jvm.Property 14 | import at.bitfire.dav4jvm.PropertyFactory 15 | import at.bitfire.dav4jvm.XmlReader 16 | import org.xmlpull.v1.XmlPullParser 17 | 18 | data class GetCTag( 19 | val cTag: String? 20 | ): Property { 21 | 22 | object Factory: PropertyFactory { 23 | 24 | override fun getName() = CalDAV.GetCTag 25 | 26 | override fun create(parser: XmlPullParser) = GetCTag(XmlReader(parser).readText()) 27 | 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/webdav/GroupMembership.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.property.webdav 12 | 13 | import at.bitfire.dav4jvm.property.common.HrefListProperty 14 | import org.xmlpull.v1.XmlPullParser 15 | 16 | class GroupMembership( 17 | override val hrefs: List 18 | ): HrefListProperty(hrefs) { 19 | 20 | object Factory: HrefListProperty.Factory() { 21 | 22 | override fun getName() = WebDAV.GroupMembership 23 | 24 | override fun create(parser: XmlPullParser) = create(parser, ::GroupMembership) 25 | 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/caldav/CalendarHomeSet.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.property.caldav 12 | 13 | import at.bitfire.dav4jvm.property.common.HrefListProperty 14 | import org.xmlpull.v1.XmlPullParser 15 | 16 | data class CalendarHomeSet( 17 | override val hrefs: List = emptyList() 18 | ): HrefListProperty(hrefs) { 19 | 20 | object Factory: HrefListProperty.Factory() { 21 | 22 | override fun getName() = CalDAV.CalendarHomeSet 23 | 24 | override fun create(parser: XmlPullParser) = create(parser, ::CalendarHomeSet) 25 | 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/carddav/AddressbookHomeSet.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.property.carddav 12 | 13 | import at.bitfire.dav4jvm.property.common.HrefListProperty 14 | import org.xmlpull.v1.XmlPullParser 15 | 16 | class AddressbookHomeSet( 17 | override val hrefs: List = emptyList() 18 | ): HrefListProperty(hrefs) { 19 | 20 | object Factory: HrefListProperty.Factory() { 21 | 22 | override fun getName() = CardDAV.AddressbookHomeSet 23 | 24 | override fun create(parser: XmlPullParser) = create(parser, ::AddressbookHomeSet) 25 | 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/test/kotlin/at/bitfire/dav4jvm/property/PropertyTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.property 12 | 13 | import at.bitfire.dav4jvm.Property 14 | import at.bitfire.dav4jvm.XmlUtils 15 | import java.io.StringReader 16 | 17 | open class PropertyTest { 18 | 19 | companion object { 20 | 21 | fun parseProperty(s: String): List { 22 | val parser = XmlUtils.newPullParser() 23 | parser.setInput(StringReader("$s")) 24 | parser.nextTag() // move into 25 | return Property.parse(parser) 26 | } 27 | 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/caldav/CalendarProxyReadFor.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.property.caldav 12 | 13 | import at.bitfire.dav4jvm.property.common.HrefListProperty 14 | import org.xmlpull.v1.XmlPullParser 15 | 16 | data class CalendarProxyReadFor( 17 | override val hrefs: List = emptyList() 18 | ): HrefListProperty(hrefs) { 19 | 20 | object Factory: HrefListProperty.Factory() { 21 | 22 | override fun getName() = CalDAV.CalendarProxyReadFor 23 | 24 | override fun create(parser: XmlPullParser) = create(parser, ::CalendarProxyReadFor) 25 | 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/caldav/MaxResourceSize.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.property.caldav 12 | 13 | import at.bitfire.dav4jvm.Property 14 | import at.bitfire.dav4jvm.PropertyFactory 15 | import at.bitfire.dav4jvm.XmlReader 16 | import org.xmlpull.v1.XmlPullParser 17 | 18 | data class MaxResourceSize( 19 | val maxSize: Long? 20 | ) : Property { 21 | 22 | object Factory: PropertyFactory { 23 | override fun getName() = CalDAV.MaxResourceSize 24 | 25 | override fun create(parser: XmlPullParser) = 26 | MaxResourceSize(XmlReader(parser).readLong()) 27 | } 28 | } -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/caldav/CalendarProxyWriteFor.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.property.caldav 12 | 13 | import at.bitfire.dav4jvm.property.common.HrefListProperty 14 | import org.xmlpull.v1.XmlPullParser 15 | 16 | data class CalendarProxyWriteFor( 17 | override val hrefs: List = emptyList() 18 | ): HrefListProperty(hrefs) { 19 | 20 | object Factory: HrefListProperty.Factory() { 21 | 22 | override fun getName() = CalDAV.CalendarProxyWriteFor 23 | 24 | override fun create(parser: XmlPullParser) = create(parser, ::CalendarProxyWriteFor) 25 | 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/test/kotlin/at/bitfire/dav4jvm/okhttp/property/PropertyTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.okhttp.property 12 | 13 | import at.bitfire.dav4jvm.Property 14 | import at.bitfire.dav4jvm.XmlUtils 15 | import java.io.StringReader 16 | 17 | open class PropertyTest { 18 | 19 | companion object { 20 | 21 | fun parseProperty(s: String): List { 22 | val parser = XmlUtils.newPullParser() 23 | parser.setInput(StringReader("$s")) 24 | parser.nextTag() // move into 25 | return Property.parse(parser) 26 | } 27 | 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/caldav/CalendarUserAddressSet.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.property.caldav 12 | 13 | import at.bitfire.dav4jvm.property.common.HrefListProperty 14 | import org.xmlpull.v1.XmlPullParser 15 | 16 | data class CalendarUserAddressSet( 17 | override val hrefs: List = emptyList() 18 | ): HrefListProperty(hrefs) { 19 | 20 | object Factory: HrefListProperty.Factory() { 21 | 22 | override fun getName() = CalDAV.CalendarUserAddressSet 23 | 24 | override fun create(parser: XmlPullParser) = create(parser, ::CalendarUserAddressSet) 25 | 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/webdav/CreationDate.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.property.webdav 12 | 13 | import at.bitfire.dav4jvm.Property 14 | import at.bitfire.dav4jvm.PropertyFactory 15 | import at.bitfire.dav4jvm.XmlReader 16 | import org.xmlpull.v1.XmlPullParser 17 | 18 | data class CreationDate( 19 | var creationDate: String? 20 | ): Property { 21 | 22 | object Factory: PropertyFactory { 23 | 24 | override fun getName() = WebDAV.CreationDate 25 | 26 | override fun create(parser: XmlPullParser) = 27 | CreationDate(XmlReader(parser).readText()) 28 | 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/carddav/MaxResourceSize.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.property.carddav 12 | 13 | import at.bitfire.dav4jvm.Property 14 | import at.bitfire.dav4jvm.PropertyFactory 15 | import at.bitfire.dav4jvm.XmlReader 16 | import org.xmlpull.v1.XmlPullParser 17 | 18 | data class MaxResourceSize( 19 | val maxSize: Long? 20 | ) : Property { 21 | 22 | object Factory: PropertyFactory { 23 | 24 | override fun getName() = CardDAV.MaxResourceSize 25 | 26 | override fun create(parser: XmlPullParser) = 27 | MaxResourceSize(XmlReader(parser).readLong()) 28 | 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/webdav/QuotaUsedBytes.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.property.webdav 12 | 13 | import at.bitfire.dav4jvm.Property 14 | import at.bitfire.dav4jvm.PropertyFactory 15 | import at.bitfire.dav4jvm.XmlReader 16 | import org.xmlpull.v1.XmlPullParser 17 | 18 | data class QuotaUsedBytes( 19 | val quotaUsedBytes: Long? 20 | ) : Property { 21 | 22 | object Factory: PropertyFactory { 23 | 24 | override fun getName() = WebDAV.QuotaUsedBytes 25 | 26 | override fun create(parser: XmlPullParser) = 27 | QuotaUsedBytes(XmlReader(parser).readLong()) 28 | 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/webdav/GetContentLength.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.property.webdav 12 | 13 | import at.bitfire.dav4jvm.Property 14 | import at.bitfire.dav4jvm.PropertyFactory 15 | import at.bitfire.dav4jvm.XmlReader 16 | import org.xmlpull.v1.XmlPullParser 17 | 18 | data class GetContentLength( 19 | val contentLength: Long? 20 | ) : Property { 21 | 22 | object Factory: PropertyFactory { 23 | 24 | override fun getName() = WebDAV.GetContentLength 25 | 26 | override fun create(parser: XmlPullParser) = 27 | GetContentLength(XmlReader(parser).readLong()) 28 | 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/test/kotlin/at/bitfire/dav4jvm/property/CalendarDescriptionTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.property 12 | 13 | import at.bitfire.dav4jvm.property.caldav.CalendarDescription 14 | import org.junit.Assert.assertEquals 15 | import org.junit.Test 16 | 17 | class CalendarDescriptionTest: PropertyTest() { 18 | 19 | @Test 20 | fun testCalendarDescription() { 21 | val results = parseProperty("My Calendar") 22 | val result = results.first() as CalendarDescription 23 | assertEquals("My Calendar", result.description) 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/ktor/exception/GoneException.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.ktor.exception 12 | 13 | import io.ktor.http.HttpStatusCode 14 | 15 | class GoneException internal constructor( 16 | responseInfo: HttpResponseInfo 17 | ): HttpException( 18 | status = responseInfo.status, 19 | requestExcerpt = responseInfo.requestExcerpt, 20 | responseExcerpt = responseInfo.responseExcerpt, 21 | errors = responseInfo.errors 22 | ) { 23 | 24 | init { 25 | if (responseInfo.status != HttpStatusCode.Gone) 26 | throw IllegalArgumentException("Status must be ${HttpStatusCode.Gone}") 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/webdav/Owner.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.property.webdav 12 | 13 | import at.bitfire.dav4jvm.Property 14 | import at.bitfire.dav4jvm.XmlReader 15 | import at.bitfire.dav4jvm.property.common.HrefListProperty 16 | import org.xmlpull.v1.XmlPullParser 17 | 18 | data class Owner( 19 | val href: String? 20 | ): Property { 21 | 22 | object Factory: HrefListProperty.Factory() { 23 | 24 | override fun getName() = WebDAV.Owner 25 | 26 | override fun create(parser: XmlPullParser): Owner = 27 | Owner(XmlReader(parser).readTextProperty(WebDAV.Href)) 28 | 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/webdav/SyncToken.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.property.webdav 12 | 13 | import at.bitfire.dav4jvm.Property 14 | import at.bitfire.dav4jvm.PropertyFactory 15 | import at.bitfire.dav4jvm.XmlReader 16 | import org.xmlpull.v1.XmlPullParser 17 | 18 | data class SyncToken( 19 | val token: String? 20 | ): Property { 21 | 22 | object Factory: PropertyFactory { 23 | 24 | override fun getName() = WebDAV.SyncToken 25 | 26 | override fun create(parser: XmlPullParser) = 27 | // 28 | SyncToken(XmlReader(parser).readText()) 29 | 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/test/kotlin/at/bitfire/dav4jvm/okhttp/property/CalendarDescriptionTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.okhttp.property 12 | 13 | import at.bitfire.dav4jvm.property.caldav.CalendarDescription 14 | import org.junit.Assert.assertEquals 15 | import org.junit.Test 16 | 17 | class CalendarDescriptionTest: PropertyTest() { 18 | 19 | @Test 20 | fun testCalendarDescription() { 21 | val results = parseProperty("My Calendar") 22 | val result = results.first() as CalendarDescription 23 | assertEquals("My Calendar", result.description) 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/ktor/exception/ConflictException.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.ktor.exception 12 | 13 | import io.ktor.http.HttpStatusCode 14 | 15 | class ConflictException internal constructor( 16 | responseInfo: HttpResponseInfo 17 | ): HttpException( 18 | status = responseInfo.status, 19 | requestExcerpt = responseInfo.requestExcerpt, 20 | responseExcerpt = responseInfo.responseExcerpt, 21 | errors = responseInfo.errors 22 | ) { 23 | 24 | init { 25 | if (responseInfo.status != HttpStatusCode.Conflict) 26 | throw IllegalArgumentException("Status must be ${HttpStatusCode.Conflict}") 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/ktor/exception/NotFoundException.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.ktor.exception 12 | 13 | import io.ktor.http.HttpStatusCode 14 | 15 | class NotFoundException internal constructor( 16 | responseInfo: HttpResponseInfo 17 | ): HttpException( 18 | status = responseInfo.status, 19 | requestExcerpt = responseInfo.requestExcerpt, 20 | responseExcerpt = responseInfo.responseExcerpt, 21 | errors = responseInfo.errors 22 | ) { 23 | 24 | init { 25 | if (responseInfo.status != HttpStatusCode.NotFound) 26 | throw IllegalArgumentException("Status must be ${HttpStatusCode.NotFound}") 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/webdav/QuotaAvailableBytes.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.property.webdav 12 | 13 | import at.bitfire.dav4jvm.Property 14 | import at.bitfire.dav4jvm.PropertyFactory 15 | import at.bitfire.dav4jvm.XmlReader 16 | import org.xmlpull.v1.XmlPullParser 17 | 18 | data class QuotaAvailableBytes( 19 | val quotaAvailableBytes: Long? 20 | ) : Property { 21 | 22 | object Factory: PropertyFactory { 23 | 24 | override fun getName() = WebDAV.QuotaAvailableBytes 25 | 26 | override fun create(parser: XmlPullParser) = 27 | QuotaAvailableBytes(XmlReader(parser).readLong()) 28 | 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/ktor/exception/ForbiddenException.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.ktor.exception 12 | 13 | import io.ktor.http.HttpStatusCode 14 | 15 | class ForbiddenException internal constructor( 16 | responseInfo: HttpResponseInfo 17 | ): HttpException( 18 | status = responseInfo.status, 19 | requestExcerpt = responseInfo.requestExcerpt, 20 | responseExcerpt = responseInfo.responseExcerpt, 21 | errors = responseInfo.errors 22 | ) { 23 | 24 | init { 25 | if (responseInfo.status != HttpStatusCode.Forbidden) 26 | throw IllegalArgumentException("Status must be ${HttpStatusCode.Forbidden}") 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/webdav/GetContentType.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.property.webdav 12 | 13 | import at.bitfire.dav4jvm.Property 14 | import at.bitfire.dav4jvm.PropertyFactory 15 | import at.bitfire.dav4jvm.XmlReader 16 | import org.xmlpull.v1.XmlPullParser 17 | 18 | data class GetContentType( 19 | val type: String? 20 | ): Property { 21 | 22 | object Factory: PropertyFactory { 23 | 24 | override fun getName() = WebDAV.GetContentType 25 | 26 | override fun create(parser: XmlPullParser) = 27 | // 28 | GetContentType(XmlReader(parser).readText()) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/ktor/exception/UnauthorizedException.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.ktor.exception 12 | 13 | import io.ktor.http.HttpStatusCode 14 | 15 | class UnauthorizedException internal constructor( 16 | responseInfo: HttpResponseInfo 17 | ): HttpException( 18 | status = responseInfo.status, 19 | requestExcerpt = responseInfo.requestExcerpt, 20 | responseExcerpt = responseInfo.responseExcerpt, 21 | errors = responseInfo.errors 22 | ) { 23 | 24 | init { 25 | if (responseInfo.status != HttpStatusCode.Unauthorized) 26 | throw IllegalArgumentException("Status must be ${HttpStatusCode.Unauthorized}") 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/webdav/DisplayName.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.property.webdav 12 | 13 | import at.bitfire.dav4jvm.Property 14 | import at.bitfire.dav4jvm.PropertyFactory 15 | import at.bitfire.dav4jvm.XmlReader 16 | import org.xmlpull.v1.XmlPullParser 17 | 18 | data class DisplayName( 19 | val displayName: String? 20 | ): Property { 21 | 22 | object Factory: PropertyFactory { 23 | 24 | override fun getName() = WebDAV.DisplayName 25 | 26 | override fun create(parser: XmlPullParser) = 27 | // 28 | DisplayName(XmlReader(parser).readText()) 29 | 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/caldav/CalendarTimezone.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.property.caldav 12 | 13 | import at.bitfire.dav4jvm.Property 14 | import at.bitfire.dav4jvm.PropertyFactory 15 | import at.bitfire.dav4jvm.XmlReader 16 | import org.xmlpull.v1.XmlPullParser 17 | 18 | data class CalendarTimezone( 19 | val vTimeZone: String? 20 | ): Property { 21 | 22 | object Factory: PropertyFactory { 23 | 24 | override fun getName() = CalDAV.CalendarTimezone 25 | 26 | override fun create(parser: XmlPullParser) = 27 | // 28 | CalendarTimezone(XmlReader(parser).readText()) 29 | 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/webdav/AddMember.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.property.webdav 12 | 13 | import at.bitfire.dav4jvm.Property 14 | import at.bitfire.dav4jvm.PropertyFactory 15 | import at.bitfire.dav4jvm.XmlReader 16 | import org.xmlpull.v1.XmlPullParser 17 | 18 | /** 19 | * Defined in RFC 5995 3.2.1 DAV:add-member Property (Protected). 20 | */ 21 | data class AddMember( 22 | val href: String? 23 | ): Property { 24 | 25 | object Factory: PropertyFactory { 26 | 27 | override fun getName() = WebDAV.AddMember 28 | 29 | override fun create(parser: XmlPullParser) = AddMember(XmlReader(parser).readTextProperty(WebDAV.Href)) 30 | 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/ktor/exception/PreconditionFailedException.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.ktor.exception 12 | 13 | import io.ktor.http.HttpStatusCode 14 | 15 | class PreconditionFailedException internal constructor( 16 | responseInfo: HttpResponseInfo 17 | ): HttpException( 18 | status = responseInfo.status, 19 | requestExcerpt = responseInfo.requestExcerpt, 20 | responseExcerpt = responseInfo.responseExcerpt, 21 | errors = responseInfo.errors 22 | ) { 23 | 24 | init { 25 | if (responseInfo.status != HttpStatusCode.PreconditionFailed) 26 | throw IllegalArgumentException("Status must be ${HttpStatusCode.PreconditionFailed}") 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/caldav/CalendarTimezoneId.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.property.caldav 12 | 13 | import at.bitfire.dav4jvm.Property 14 | import at.bitfire.dav4jvm.PropertyFactory 15 | import at.bitfire.dav4jvm.XmlReader 16 | import org.xmlpull.v1.XmlPullParser 17 | 18 | data class CalendarTimezoneId( 19 | val identifier: String? 20 | ): Property { 21 | 22 | object Factory: PropertyFactory { 23 | 24 | override fun getName() = CalDAV.CalendarTimezoneId 25 | 26 | override fun create(parser: XmlPullParser) = 27 | // 28 | CalendarTimezoneId(XmlReader(parser).readText()) 29 | 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /.github/workflows/build-kdoc.yml: -------------------------------------------------------------------------------- 1 | name: Build KDoc 2 | on: 3 | push: 4 | branches: [main] 5 | 6 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 7 | permissions: 8 | contents: read 9 | pages: write 10 | id-token: write 11 | 12 | jobs: 13 | build: 14 | name: Build and publish KDoc 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v6 18 | - uses: actions/setup-java@v5 19 | with: 20 | distribution: temurin 21 | java-version: 17 22 | - uses: gradle/actions/setup-gradle@v5 23 | 24 | - name: Build KDoc 25 | run: ./gradlew --no-daemon dokkaGenerate 26 | 27 | - uses: actions/upload-pages-artifact@v4 28 | with: 29 | path: build/dokka/html 30 | 31 | deploy: 32 | environment: 33 | name: github-pages 34 | url: ${{ steps.deployment.outputs.page_url }} 35 | runs-on: ubuntu-latest 36 | needs: build 37 | steps: 38 | - name: Deploy to GitHub Pages 39 | id: deployment 40 | uses: actions/deploy-pages@v4 41 | -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/caldav/CalendarDescription.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.property.caldav 12 | 13 | import at.bitfire.dav4jvm.Property 14 | import at.bitfire.dav4jvm.PropertyFactory 15 | import at.bitfire.dav4jvm.XmlReader 16 | import org.xmlpull.v1.XmlPullParser 17 | 18 | data class CalendarDescription( 19 | val description: String? 20 | ): Property { 21 | 22 | object Factory: PropertyFactory { 23 | 24 | override fun getName() = CalDAV.CalendarDescription 25 | 26 | override fun create(parser: XmlPullParser) = 27 | // 28 | CalendarDescription(XmlReader(parser).readText()) 29 | 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/carddav/AddressbookDescription.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.property.carddav 12 | 13 | import at.bitfire.dav4jvm.Property 14 | import at.bitfire.dav4jvm.PropertyFactory 15 | import at.bitfire.dav4jvm.XmlReader 16 | import org.xmlpull.v1.XmlPullParser 17 | 18 | data class AddressbookDescription( 19 | val description: String? = null 20 | ): Property { 21 | 22 | object Factory: PropertyFactory { 23 | 24 | override fun getName() = CardDAV.AddressbookDescription 25 | 26 | override fun create(parser: XmlPullParser) = 27 | // 28 | AddressbookDescription(XmlReader(parser).readText()) 29 | 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/push/Topic.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.property.push 12 | 13 | import at.bitfire.dav4jvm.Property 14 | import at.bitfire.dav4jvm.PropertyFactory 15 | import at.bitfire.dav4jvm.XmlReader 16 | import org.xmlpull.v1.XmlPullParser 17 | 18 | /** 19 | * Represents a [NS_WEBDAV_PUSH]`:topic` property. 20 | * 21 | * Experimental! See https://github.com/bitfireAT/webdav-push/ 22 | */ 23 | data class Topic( 24 | val topic: String? = null 25 | ): Property { 26 | 27 | object Factory: PropertyFactory { 28 | 29 | override fun getName() = WebDAVPush.Topic 30 | 31 | override fun create(parser: XmlPullParser): Topic = 32 | Topic(XmlReader(parser).readText()) 33 | 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/push/AuthSecret.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.property.push 12 | 13 | import at.bitfire.dav4jvm.Property 14 | import at.bitfire.dav4jvm.PropertyFactory 15 | import at.bitfire.dav4jvm.XmlReader 16 | import org.xmlpull.v1.XmlPullParser 17 | 18 | /** 19 | * Represents an `auth-secret` property. 20 | * 21 | * Experimental! See https://github.com/bitfireAT/webdav-push/ 22 | */ 23 | data class AuthSecret( 24 | val secret: String? = null 25 | ): Property { 26 | 27 | object Factory: PropertyFactory { 28 | 29 | override fun getName() = WebDAVPush.AuthSecret 30 | 31 | override fun create(parser: XmlPullParser): AuthSecret = 32 | AuthSecret(XmlReader(parser).readText()) 33 | 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/webdav/GetLastModified.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.property.webdav 12 | 13 | import at.bitfire.dav4jvm.Property 14 | import at.bitfire.dav4jvm.PropertyFactory 15 | import at.bitfire.dav4jvm.XmlReader 16 | import org.xmlpull.v1.XmlPullParser 17 | import java.time.Instant 18 | 19 | data class GetLastModified( 20 | val lastModified: Instant? 21 | ): Property { 22 | 23 | object Factory: PropertyFactory { 24 | 25 | override fun getName() = WebDAV.GetLastModified 26 | 27 | override fun create(parser: XmlPullParser): GetLastModified { 28 | // 29 | return GetLastModified( 30 | XmlReader(parser).readHttpDate() 31 | ) 32 | } 33 | 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | dokka = "2.1.0" 3 | junit4 = "4.13.2" 4 | kotlin = "2.3.0" 5 | kotlin-coroutines = "1.10.2" 6 | okhttpVersion = "5.3.2" 7 | spotbugs = "4.9.8" 8 | xpp3Version = "1.1.6" 9 | ktor = "3.3.3" 10 | 11 | [libraries] 12 | junit4 = { module = "junit:junit", version.ref = "junit4" } 13 | kotlin-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlin-coroutines" } 14 | ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" } 15 | ktor-client-encoding = { module = "io.ktor:ktor-client-encoding", version.ref = "ktor" } 16 | ktor-client-mock = { module = "io.ktor:ktor-client-mock", version.ref = "ktor" } 17 | okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttpVersion" } 18 | okhttp-mockwebserver = { module = "com.squareup.okhttp3:mockwebserver3", version.ref = "okhttpVersion" } 19 | spotbugs-annotations = { module = "com.github.spotbugs:spotbugs-annotations", version.ref = "spotbugs" } 20 | xpp3 = { module = "org.ogce:xpp3", version.ref = "xpp3Version" } 21 | 22 | [plugins] 23 | dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" } 24 | kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } 25 | -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/caldav/CalendarData.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.property.caldav 12 | 13 | import at.bitfire.dav4jvm.Property 14 | import at.bitfire.dav4jvm.PropertyFactory 15 | import at.bitfire.dav4jvm.XmlReader 16 | import org.xmlpull.v1.XmlPullParser 17 | 18 | data class CalendarData( 19 | val iCalendar: String? 20 | ): Property { 21 | 22 | companion object { 23 | // attributes 24 | const val CONTENT_TYPE = "content-type" 25 | const val VERSION = "version" 26 | } 27 | 28 | 29 | object Factory: PropertyFactory { 30 | 31 | override fun getName() = CalDAV.CalendarData 32 | 33 | override fun create(parser: XmlPullParser) = 34 | // 35 | CalendarData(XmlReader(parser).readText()) 36 | 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/carddav/AddressData.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.property.carddav 12 | 13 | import at.bitfire.dav4jvm.Property 14 | import at.bitfire.dav4jvm.PropertyFactory 15 | import at.bitfire.dav4jvm.XmlReader 16 | import org.xmlpull.v1.XmlPullParser 17 | 18 | data class AddressData( 19 | val card: String? 20 | ): Property { 21 | 22 | companion object { 23 | 24 | // attributes 25 | const val CONTENT_TYPE = "content-type" 26 | const val VERSION = "version" 27 | 28 | } 29 | 30 | 31 | object Factory: PropertyFactory { 32 | 33 | override fun getName() = CardDAV.AddressData 34 | 35 | override fun create(parser: XmlPullParser) = 36 | // 37 | AddressData(XmlReader(parser).readText()) 38 | 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/test/kotlin/at/bitfire/dav4jvm/okhttp/OkHttpUtilsTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.okhttp 12 | 13 | import okhttp3.HttpUrl.Companion.toHttpUrl 14 | import org.junit.Assert.assertEquals 15 | import org.junit.Test 16 | 17 | class OkHttpUtilsTest { 18 | 19 | @Test 20 | fun fileName() { 21 | assertEquals("", OkHttpUtils.fileName("https://example.com".toHttpUrl())) 22 | assertEquals("", OkHttpUtils.fileName("https://example.com/".toHttpUrl())) 23 | assertEquals("file1", OkHttpUtils.fileName("https://example.com/file1".toHttpUrl())) 24 | assertEquals("dir1", OkHttpUtils.fileName("https://example.com/dir1/".toHttpUrl())) 25 | assertEquals("file2", OkHttpUtils.fileName("https://example.com/dir1/file2".toHttpUrl())) 26 | assertEquals("dir2", OkHttpUtils.fileName("https://example.com/dir1/dir2/".toHttpUrl())) 27 | } 28 | } -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/PropertyFactory.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm 12 | 13 | import org.xmlpull.v1.XmlPullParser 14 | import org.xmlpull.v1.XmlPullParserException 15 | 16 | interface PropertyFactory { 17 | 18 | /** 19 | * Name of the Property the factory creates, 20 | * e.g. `Property.Name("DAV:", "displayname")` if the factory creates 21 | * [at.bitfire.dav4jvm.property.webdav.DisplayName] objects) 22 | */ 23 | fun getName(): Property.Name 24 | 25 | /** 26 | * Parses XML of a property and returns its data class. 27 | * 28 | * Implementations shouldn't make assumptions on which sub-properties are available 29 | * or not and in doubt return an empty [Property]. 30 | * 31 | * @throws XmlPullParserException in case of parsing errors 32 | */ 33 | fun create(parser: XmlPullParser): Property 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/webdav/SyncLevel.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.property.webdav 12 | 13 | import at.bitfire.dav4jvm.Property 14 | import at.bitfire.dav4jvm.PropertyFactory 15 | import at.bitfire.dav4jvm.XmlReader 16 | import org.xmlpull.v1.XmlPullParser 17 | 18 | /** 19 | * Represents a [NS_WEBDAV]`:sync-level` property. 20 | */ 21 | data class SyncLevel( 22 | /** May be `0`, `1` or [Int.MAX_VALUE] (infinite). */ 23 | val level: Int? = null 24 | ): Property { 25 | 26 | object Factory: PropertyFactory { 27 | 28 | override fun getName() = WebDAV.SyncLevel 29 | 30 | override fun create(parser: XmlPullParser): SyncLevel { 31 | val text = XmlReader(parser).readText() 32 | val level = if (text == "infinite") Int.MAX_VALUE else text?.toIntOrNull() 33 | return SyncLevel(level) 34 | } 35 | 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/push/VapidPublicKey.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.property.push 12 | 13 | import at.bitfire.dav4jvm.Property 14 | import at.bitfire.dav4jvm.PropertyFactory 15 | import at.bitfire.dav4jvm.XmlReader 16 | import org.xmlpull.v1.XmlPullParser 17 | 18 | /** 19 | * Represents a [NS_WEBDAV_PUSH]`:vapid-public-key` property. 20 | * 21 | * Experimental! See https://github.com/bitfireAT/webdav-push/ 22 | */ 23 | data class VapidPublicKey( 24 | val type: String? = null, 25 | val key: String? = null 26 | ): Property { 27 | 28 | object Factory : PropertyFactory { 29 | 30 | override fun getName() = WebDAVPush.VapidPublicKey 31 | 32 | override fun create(parser: XmlPullParser): VapidPublicKey { 33 | return VapidPublicKey( 34 | type = parser.getAttributeValue(null, "type"), 35 | key = XmlReader(parser).readText() 36 | ) 37 | } 38 | 39 | } 40 | 41 | } -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/webdav/CurrentUserPrincipal.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.property.webdav 12 | 13 | import at.bitfire.dav4jvm.Property 14 | import at.bitfire.dav4jvm.PropertyFactory 15 | import at.bitfire.dav4jvm.XmlReader 16 | import org.xmlpull.v1.XmlPullParser 17 | 18 | // see RFC 5397: WebDAV Current Principal Extension 19 | 20 | data class CurrentUserPrincipal( 21 | val href: String? 22 | ): Property { 23 | 24 | object Factory: PropertyFactory { 25 | 26 | override fun getName() = WebDAV.CurrentUserPrincipal 27 | 28 | override fun create(parser: XmlPullParser): CurrentUserPrincipal { 29 | // 30 | var href: String? = null 31 | XmlReader(parser).processTag(WebDAV.Href) { 32 | href = readText() 33 | } 34 | return CurrentUserPrincipal(href) 35 | } 36 | 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/push/SubscriptionPublicKey.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.property.push 12 | 13 | import at.bitfire.dav4jvm.Property 14 | import at.bitfire.dav4jvm.PropertyFactory 15 | import at.bitfire.dav4jvm.XmlReader 16 | import org.xmlpull.v1.XmlPullParser 17 | 18 | /** 19 | * Represents a [NS_WEBDAV_PUSH]`:subscription-public-key` property. 20 | * 21 | * Experimental! See https://github.com/bitfireAT/webdav-push/ 22 | */ 23 | data class SubscriptionPublicKey( 24 | val type: String? = null, 25 | val key: String? = null 26 | ): Property { 27 | 28 | object Factory : PropertyFactory { 29 | 30 | override fun getName() = WebDAVPush.SubscriptionPublicKey 31 | 32 | override fun create(parser: XmlPullParser): SubscriptionPublicKey { 33 | return SubscriptionPublicKey( 34 | type = parser.getAttributeValue(null, "type"), 35 | key = XmlReader(parser).readText() 36 | ) 37 | } 38 | 39 | } 40 | 41 | } -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/webdav/Depth.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.property.webdav 12 | 13 | import at.bitfire.dav4jvm.Property 14 | import at.bitfire.dav4jvm.PropertyFactory 15 | import at.bitfire.dav4jvm.XmlReader 16 | import org.xmlpull.v1.XmlPullParser 17 | 18 | /** 19 | * Represents a [NS_WEBDAV]`:depth` property. 20 | */ 21 | data class Depth( 22 | /** May be `0`, `1` or [Int.MAX_VALUE] (infinite). */ 23 | val depth: Int? = null 24 | ): Property { 25 | 26 | companion object { 27 | const val INFINITY = Int.MAX_VALUE 28 | } 29 | 30 | 31 | object Factory: PropertyFactory { 32 | 33 | override fun getName() = WebDAV.Depth 34 | 35 | override fun create(parser: XmlPullParser): Depth { 36 | val text = XmlReader(parser).readText() 37 | val level = if (text.equals("infinity", true)) 38 | INFINITY 39 | else 40 | text?.toIntOrNull() 41 | return Depth(level) 42 | } 43 | 44 | } 45 | 46 | } -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/QuotedStringUtils.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm 12 | 13 | object QuotedStringUtils { 14 | 15 | fun asQuotedString(raw: String) = 16 | "\"" + raw.replace("\\" ,"\\\\").replace("\"", "\\\"") + "\"" 17 | 18 | fun decodeQuotedString(quoted: String): String { 19 | /* quoted-string = ( <"> *(qdtext | quoted-pair ) <"> ) 20 | qdtext = > 21 | quoted-pair = "\" CHAR 22 | */ 23 | 24 | val len = quoted.length 25 | if (len >= 2 && quoted[0] == '"' && quoted[len-1] == '"') { 26 | val result = StringBuffer(len) 27 | var pos = 1 28 | while (pos < len-1) { 29 | var c = quoted[pos] 30 | if (c == '\\' && pos != len-2) 31 | c = quoted[++pos] 32 | result.append(c) 33 | pos++ 34 | } 35 | return result.toString() 36 | } else 37 | return quoted 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/carddav/CardDAV.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.property.carddav 12 | 13 | import at.bitfire.dav4jvm.Property 14 | 15 | object CardDAV { 16 | 17 | // CardDAV (RFC 6352) 18 | 19 | const val NS_CARDDAV = "urn:ietf:params:xml:ns:carddav" 20 | 21 | val Addressbook = Property.Name(NS_CARDDAV, "addressbook") // CardDAV 22 | val AddressData = Property.Name(NS_CARDDAV, "address-data") 23 | val AddressDataType = Property.Name(NS_CARDDAV, "address-data-type") 24 | val AddressbookDescription = Property.Name(NS_CARDDAV, "addressbook-description") 25 | val AddressbookHomeSet = Property.Name(NS_CARDDAV, "addressbook-home-set") 26 | val AddressbookMultiget = Property.Name(NS_CARDDAV, "addressbook-multiget") 27 | val AddressbookQuery = Property.Name(NS_CARDDAV, "addressbook-query") 28 | val Filter = Property.Name(NS_CARDDAV, "filter") 29 | val MaxResourceSize = Property.Name(NS_CARDDAV, "max-resource-size") 30 | val SupportedAddressData = Property.Name(NS_CARDDAV, "supported-address-data") 31 | 32 | } -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/push/PushResource.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.property.push 12 | 13 | import at.bitfire.dav4jvm.Property 14 | import at.bitfire.dav4jvm.PropertyFactory 15 | import at.bitfire.dav4jvm.XmlReader 16 | import org.xmlpull.v1.XmlPullParser 17 | import java.net.URI 18 | import java.net.URISyntaxException 19 | 20 | /** 21 | * Represents a [NS_WEBDAV_PUSH]`:push-resource` property. 22 | * 23 | * Experimental! See https://github.com/bitfireAT/webdav-push/ 24 | */ 25 | data class PushResource( 26 | val uri: URI? = null 27 | ): Property { 28 | 29 | object Factory: PropertyFactory { 30 | 31 | override fun getName() = WebDAVPush.PushResource 32 | 33 | override fun create(parser: XmlPullParser): PushResource = 34 | PushResource( 35 | uri = XmlReader(parser).readText()?.let { uri -> 36 | try { 37 | URI(uri) 38 | } catch (_: URISyntaxException) { 39 | null 40 | } 41 | } 42 | ) 43 | 44 | } 45 | 46 | } -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/push/Subscription.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.property.push 12 | 13 | import at.bitfire.dav4jvm.Property 14 | import at.bitfire.dav4jvm.PropertyFactory 15 | import at.bitfire.dav4jvm.XmlReader 16 | import org.xmlpull.v1.XmlPullParser 17 | 18 | /** 19 | * Represents a [NS_WEBDAV_PUSH]`:subscription` property. 20 | * 21 | * Experimental! See https://github.com/bitfireAT/webdav-push/ 22 | */ 23 | data class Subscription private constructor( 24 | val webPushSubscription: WebPushSubscription? = null 25 | ): Property { 26 | 27 | object Factory: PropertyFactory { 28 | 29 | override fun getName() = WebDAVPush.Subscription 30 | 31 | override fun create(parser: XmlPullParser): Subscription { 32 | // currently we only support WebPushSubscription 33 | var webPushSubscription: WebPushSubscription? = null 34 | 35 | XmlReader(parser).processTag(WebDAVPush.WebPushSubscription) { 36 | webPushSubscription = WebPushSubscription.Factory.create(parser) 37 | } 38 | 39 | return Subscription(webPushSubscription) 40 | } 41 | 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/caldav/SupportedCalendarData.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.property.caldav 12 | 13 | import at.bitfire.dav4jvm.Property 14 | import at.bitfire.dav4jvm.PropertyFactory 15 | import at.bitfire.dav4jvm.XmlReader 16 | import io.ktor.http.ContentType 17 | import org.xmlpull.v1.XmlPullParser 18 | 19 | data class SupportedCalendarData( 20 | val types: Set = emptySet() 21 | ): Property { 22 | 23 | companion object { 24 | 25 | const val CONTENT_TYPE = "content-type" 26 | const val VERSION = "version" 27 | 28 | } 29 | 30 | fun hasJCal() = types 31 | .map { ContentType.parse(it) } 32 | .any { ContentType.Application.contains(it) && "calendar+json".equals(it.contentSubtype, true) } 33 | 34 | 35 | object Factory: PropertyFactory { 36 | 37 | override fun getName() = CalDAV.SupportedCalendarData 38 | 39 | override fun create(parser: XmlPullParser): SupportedCalendarData { 40 | val supportedTypes = mutableSetOf() 41 | 42 | XmlReader(parser).readContentTypes(CalDAV.CalendarData, supportedTypes::add) 43 | 44 | return SupportedCalendarData(supportedTypes) 45 | } 46 | 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/webdav/SupportedReportSet.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.property.webdav 12 | 13 | import at.bitfire.dav4jvm.Property 14 | import at.bitfire.dav4jvm.PropertyFactory 15 | import at.bitfire.dav4jvm.XmlReader 16 | import org.xmlpull.v1.XmlPullParser 17 | 18 | data class SupportedReportSet( 19 | val reports: Set = emptySet() 20 | ): Property { 21 | 22 | object Factory: PropertyFactory { 23 | 24 | override fun getName() = WebDAV.SupportedReportSet 25 | 26 | override fun create(parser: XmlPullParser): SupportedReportSet { 27 | /* 28 | 29 | 30 | */ 31 | 32 | val reports = mutableSetOf() 33 | 34 | XmlReader(parser).processTag(WebDAV.SupportedReport) { 35 | processTag(WebDAV.Report) { 36 | parser.nextTag() 37 | if (parser.eventType == XmlPullParser.START_TAG) 38 | reports += Property.Name(parser.namespace, parser.name) 39 | } 40 | } 41 | return SupportedReportSet(reports) 42 | } 43 | 44 | } 45 | 46 | } -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/ktor/PropStatParser.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.ktor 12 | 13 | import at.bitfire.dav4jvm.Property 14 | import at.bitfire.dav4jvm.XmlUtils.propertyName 15 | import at.bitfire.dav4jvm.property.webdav.WebDAV 16 | import io.ktor.http.HttpStatusCode 17 | import org.xmlpull.v1.XmlPullParser 18 | import java.util.LinkedList 19 | 20 | object PropStatParser { 21 | 22 | private val ASSUMING_OK = HttpStatusCode(200, "Assuming OK") 23 | 24 | fun parse(parser: XmlPullParser): PropStat { 25 | val depth = parser.depth 26 | 27 | var status: HttpStatusCode? = null 28 | val prop = LinkedList() 29 | 30 | var eventType = parser.eventType 31 | while (!(eventType == XmlPullParser.END_TAG && parser.depth == depth)) { 32 | if (eventType == XmlPullParser.START_TAG && parser.depth == depth + 1) 33 | when (parser.propertyName()) { 34 | WebDAV.Prop -> 35 | prop.addAll(Property.parse(parser)) 36 | WebDAV.Status -> 37 | status = KtorHttpUtils.parseStatusLine(parser.nextText()) 38 | } 39 | eventType = parser.next() 40 | } 41 | 42 | return PropStat(prop, status ?: ASSUMING_OK) 43 | } 44 | 45 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io 2 | 3 | ### Android ### 4 | # Built application files 5 | *.apk 6 | *.ap_ 7 | 8 | # Files for the Dalvik VM 9 | *.dex 10 | 11 | # Java/Kotlin files 12 | *.class 13 | .kotlin/ 14 | 15 | # Generated files 16 | bin/ 17 | gen/ 18 | 19 | # Gradle files 20 | .gradle/ 21 | build/ 22 | 23 | # Local configuration file (sdk path, etc) 24 | local.properties 25 | 26 | # Proguard folder generated by Eclipse 27 | proguard/ 28 | 29 | # Log Files 30 | *.log 31 | 32 | 33 | ### Intellij ### 34 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm 35 | 36 | *.iml 37 | 38 | ## Directory-based project format: 39 | .idea/ 40 | # if you remove the above rule, at least ignore the following: 41 | 42 | # User-specific stuff: 43 | # .idea/workspace.xml 44 | # .idea/tasks.xml 45 | # .idea/dictionaries 46 | 47 | # Sensitive or high-churn files: 48 | # .idea/dataSources.ids 49 | # .idea/dataSources.xml 50 | # .idea/sqlDataSources.xml 51 | # .idea/dynamic.xml 52 | # .idea/uiDesigner.xml 53 | 54 | # Gradle: 55 | # .idea/gradle.xml 56 | # .idea/libraries 57 | 58 | # Mongo Explorer plugin: 59 | # .idea/mongoSettings.xml 60 | 61 | ## File-based project format: 62 | *.ipr 63 | *.iws 64 | 65 | ## Plugin-specific files: 66 | 67 | # IntelliJ 68 | out/ 69 | 70 | # mpeltonen/sbt-idea plugin 71 | .idea_modules/ 72 | 73 | # JIRA plugin 74 | atlassian-ide-plugin.xml 75 | 76 | # Crashlytics plugin (for Android Studio and IntelliJ) 77 | com_crashlytics_export_strings.xml 78 | crashlytics.properties 79 | crashlytics-build.properties 80 | 81 | 82 | ### Gradle ### 83 | .gradle 84 | build/ 85 | 86 | # Ignore Gradle GUI config 87 | gradle-app.setting 88 | 89 | 90 | ### external libs ### 91 | .svn 92 | -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/common/HrefListProperty.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.property.common 12 | 13 | import at.bitfire.dav4jvm.Property 14 | import at.bitfire.dav4jvm.PropertyFactory 15 | import at.bitfire.dav4jvm.XmlReader 16 | import at.bitfire.dav4jvm.property.webdav.WebDAV 17 | import org.xmlpull.v1.XmlPullParser 18 | 19 | /** 20 | * Represents a list of hrefs. 21 | * 22 | * Every [HrefListProperty] must be a data class. 23 | */ 24 | abstract class HrefListProperty( 25 | open val hrefs: List 26 | ): Property { 27 | 28 | abstract class Factory : PropertyFactory { 29 | 30 | @Deprecated("hrefs is no longer mutable.", level = DeprecationLevel.ERROR) 31 | fun create(parser: XmlPullParser, list: HrefListProperty): HrefListProperty { 32 | val hrefs = list.hrefs.toMutableList() 33 | XmlReader(parser).readTextPropertyList(WebDAV.Href, hrefs) 34 | return list 35 | } 36 | 37 | fun create( 38 | parser: XmlPullParser, 39 | constructor: (hrefs: List 40 | ) -> PropertyType): PropertyType { 41 | val hrefs = mutableListOf() 42 | XmlReader(parser).readTextPropertyList(WebDAV.Href, hrefs) 43 | return constructor(hrefs) 44 | } 45 | 46 | } 47 | 48 | } -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/caldav/ScheduleTag.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.property.caldav 12 | 13 | import at.bitfire.dav4jvm.Property 14 | import at.bitfire.dav4jvm.PropertyFactory 15 | import at.bitfire.dav4jvm.QuotedStringUtils 16 | import at.bitfire.dav4jvm.XmlReader 17 | import io.ktor.client.statement.HttpResponse 18 | import io.ktor.http.HttpHeaders 19 | import okhttp3.Response 20 | import org.xmlpull.v1.XmlPullParser 21 | 22 | data class ScheduleTag( 23 | val rawScheduleTag: String? 24 | ): Property { 25 | 26 | companion object { 27 | 28 | fun fromHttpResponse(response: HttpResponse) = 29 | response.headers[HttpHeaders.ScheduleTag]?.let { ScheduleTag(it) } 30 | 31 | fun fromResponse(response: Response) = 32 | response.header(HttpHeaders.ScheduleTag)?.let { ScheduleTag(it) } 33 | 34 | } 35 | 36 | /* Value: opaque-tag 37 | opaque-tag = quoted-string 38 | */ 39 | val scheduleTag: String? = rawScheduleTag?.let { QuotedStringUtils.decodeQuotedString(it) } 40 | 41 | override fun toString() = scheduleTag ?: "(null)" 42 | 43 | 44 | object Factory: PropertyFactory { 45 | 46 | override fun getName() = CalDAV.ScheduleTag 47 | 48 | override fun create(parser: XmlPullParser) = ScheduleTag(XmlReader(parser).readText()) 49 | 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/test/kotlin/at/bitfire/dav4jvm/ktor/ResponseParserTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.ktor 12 | 13 | import io.ktor.http.Url 14 | import org.junit.Assert.assertEquals 15 | import org.junit.Test 16 | 17 | class ResponseParserTest { 18 | 19 | val baseUrl = Url("https://example.com/collection/") 20 | val parser = ResponseParser(baseUrl, callback = { _, _ -> 21 | // no-op 22 | }) 23 | 24 | @Test 25 | fun `resolveHref with absolute URL`() { 26 | assertEquals( 27 | Url("https://example.com/collection/member"), 28 | parser.resolveHref("https://example.com/collection/member") 29 | ) 30 | } 31 | 32 | @Test 33 | fun `resolveHref with absolute path`() { 34 | assertEquals( 35 | Url("https://example.com/collection/member"), 36 | parser.resolveHref("/collection/member") 37 | ) 38 | } 39 | 40 | @Test 41 | fun `resolveHref with relative path`() { 42 | assertEquals( 43 | Url("https://example.com/collection/member"), 44 | parser.resolveHref("member") 45 | ) 46 | } 47 | 48 | @Test 49 | fun `resolveHref with relative path with colon`() { 50 | assertEquals( 51 | Url("https://example.com/collection/mem:ber"), 52 | parser.resolveHref("mem:ber") 53 | ) 54 | } 55 | 56 | } -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/Error.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm 12 | 13 | import org.xmlpull.v1.XmlPullParser 14 | import java.io.Serializable 15 | 16 | /** 17 | * Represents an XML precondition/postcondition error. Every error has a name, which is the XML element 18 | * name. Subclassed errors may have more specific information available. 19 | * 20 | * At the moment, there is no logic for subclassing errors. 21 | * 22 | * @param name property name for the XML error element 23 | */ 24 | data class Error( 25 | val name: Property.Name 26 | ): Serializable { 27 | 28 | companion object { 29 | 30 | fun parseError(parser: XmlPullParser): List { 31 | val names = mutableSetOf() 32 | 33 | val depth = parser.depth 34 | var eventType = parser.eventType 35 | while (!(eventType == XmlPullParser.END_TAG && parser.depth == depth)) { 36 | if (eventType == XmlPullParser.START_TAG && parser.depth == depth + 1) 37 | names += Property.Name(parser.namespace, parser.name) 38 | eventType = parser.next() 39 | } 40 | 41 | return names.map { Error(it) } 42 | } 43 | 44 | } 45 | 46 | override fun equals(other: Any?) = 47 | (other is Error) && other.name == name 48 | 49 | override fun hashCode() = name.hashCode() 50 | 51 | } -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/push/WebPush.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.property.push 12 | 13 | import at.bitfire.dav4jvm.Property 14 | import at.bitfire.dav4jvm.PropertyFactory 15 | import at.bitfire.dav4jvm.XmlUtils.propertyName 16 | import org.xmlpull.v1.XmlPullParser 17 | 18 | /** 19 | * Represents a [NS_WEBDAV_PUSH]`:web-push` property. 20 | * 21 | * Experimental! See https://github.com/bitfireAT/webdav-push/ 22 | */ 23 | data class WebPush( 24 | val vapidPublicKey: VapidPublicKey? = null 25 | ) : PushTransport { 26 | 27 | object Factory : PropertyFactory { 28 | 29 | override fun getName(): Property.Name = WebDAVPush.WebPush 30 | 31 | override fun create(parser: XmlPullParser): WebPush { 32 | var vapidPublicKey: VapidPublicKey? = null 33 | val depth = parser.depth 34 | var eventType = parser.eventType 35 | while (!(eventType == XmlPullParser.END_TAG && parser.depth == depth)) { 36 | if (eventType == XmlPullParser.START_TAG) { 37 | when (parser.propertyName()) { 38 | WebDAVPush.VapidPublicKey -> vapidPublicKey = VapidPublicKey.Factory.create(parser) 39 | } 40 | } 41 | eventType = parser.next() 42 | } 43 | return WebPush(vapidPublicKey) 44 | } 45 | 46 | } 47 | 48 | } -------------------------------------------------------------------------------- /src/test/kotlin/at/bitfire/dav4jvm/okhttp/QuotedStringUtilsTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.okhttp 12 | 13 | import at.bitfire.dav4jvm.QuotedStringUtils 14 | import org.junit.Assert.assertEquals 15 | import org.junit.Test 16 | 17 | class QuotedStringUtilsTest { 18 | 19 | @Test 20 | fun testAsQuotedString() { 21 | assertEquals("\"\"", QuotedStringUtils.asQuotedString("")) 22 | assertEquals("\"\\\"\"", QuotedStringUtils.asQuotedString("\"")) 23 | assertEquals("\"\\\\\"", QuotedStringUtils.asQuotedString("\\")) 24 | } 25 | 26 | @Test 27 | fun testDecodeQuotedString() { 28 | assertEquals("\"", QuotedStringUtils.decodeQuotedString("\"")) 29 | assertEquals("\\", QuotedStringUtils.decodeQuotedString("\"\\\"")) 30 | assertEquals("\"test", QuotedStringUtils.decodeQuotedString("\"test")) 31 | assertEquals("test", QuotedStringUtils.decodeQuotedString("test")) 32 | assertEquals("", QuotedStringUtils.decodeQuotedString("\"\"")) 33 | assertEquals("test", QuotedStringUtils.decodeQuotedString("\"test\"")) 34 | assertEquals("test\\", QuotedStringUtils.decodeQuotedString("\"test\\\"")) 35 | assertEquals("test", QuotedStringUtils.decodeQuotedString("\"t\\e\\st\"")) 36 | assertEquals("12\"34", QuotedStringUtils.decodeQuotedString("\"12\\\"34\"")) 37 | assertEquals("1234\"", QuotedStringUtils.decodeQuotedString("\"1234\\\"\"")) 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/okhttp/CallbackInterfaces.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.okhttp 12 | 13 | import okhttp3.Response 14 | 15 | /** 16 | * Callback for the OPTIONS request. 17 | */ 18 | fun interface CapabilitiesCallback { 19 | fun onCapabilities(davCapabilities: Set, response: Response) 20 | } 21 | 22 | /** 23 | * Callback for 207 Multi-Status responses. 24 | */ 25 | fun interface MultiResponseCallback { 26 | /** 27 | * Called for every `` element in the `` body. For instance, 28 | * in response to a `PROPFIND` request, this callback will be called once for every found 29 | * member resource. 30 | * 31 | * Known collections have [response] `href` with trailing slash, see [at.bitfire.dav4jvm.okhttp.Response.parse] for details. 32 | * 33 | * @param response the parsed response (including URL) 34 | * @param relation relation of the response to the called resource 35 | */ 36 | fun onResponse(response: at.bitfire.dav4jvm.okhttp.Response, relation: at.bitfire.dav4jvm.okhttp.Response.HrefRelation) 37 | } 38 | 39 | /** 40 | * Callback for HTTP responses. 41 | */ 42 | fun interface ResponseCallback { 43 | /** 44 | * Called for a HTTP response. Typically this is only called for successful/redirect 45 | * responses because HTTP errors throw an exception before this callback is called. 46 | */ 47 | fun onResponse(response: Response) 48 | } 49 | -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/push/PushTransports.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.property.push 12 | 13 | import at.bitfire.dav4jvm.Property 14 | import at.bitfire.dav4jvm.PropertyFactory 15 | import at.bitfire.dav4jvm.XmlUtils.propertyName 16 | import org.xmlpull.v1.XmlPullParser 17 | 18 | /** 19 | * Represents a [NS_WEBDAV_PUSH]`:push-transports` property. 20 | * 21 | * Experimental! See https://github.com/bitfireAT/webdav-push/ 22 | */ 23 | class PushTransports private constructor( 24 | val transports: Set 25 | ): Property { 26 | 27 | fun hasWebPush() = transports.any { it is WebPush } 28 | 29 | 30 | object Factory: PropertyFactory { 31 | 32 | override fun getName() = WebDAVPush.Transports 33 | 34 | override fun create(parser: XmlPullParser): PushTransports { 35 | val transports = mutableListOf() 36 | val depth = parser.depth 37 | var eventType = parser.eventType 38 | while (!(eventType == XmlPullParser.END_TAG && parser.depth == depth)) { 39 | if (eventType == XmlPullParser.START_TAG && parser.depth == depth + 1) { 40 | when (parser.propertyName()) { 41 | WebDAVPush.WebPush -> 42 | transports += WebPush.Factory.create(parser) 43 | } 44 | } 45 | eventType = parser.next() 46 | } 47 | return PushTransports(transports.toSet()) 48 | } 49 | 50 | } 51 | 52 | } -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/carddav/SupportedAddressData.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.property.carddav 12 | 13 | import at.bitfire.dav4jvm.Property 14 | import at.bitfire.dav4jvm.PropertyFactory 15 | import at.bitfire.dav4jvm.XmlReader 16 | import io.ktor.http.ContentType 17 | import org.xmlpull.v1.XmlPullParser 18 | 19 | class SupportedAddressData( 20 | val types: Set = emptySet() 21 | ): Property { 22 | 23 | companion object { 24 | 25 | const val CONTENT_TYPE = "content-type" 26 | const val VERSION = "version" 27 | 28 | } 29 | 30 | fun hasVCard4() = types 31 | .map { try { ContentType.parse(it) } catch (_: Exception) { ContentType.Any } } 32 | .any { "text/vcard; version=4.0".equals(it.toString(), true) } 33 | fun hasJCard() = types 34 | .map { try { ContentType.parse(it) } catch (_: Exception) { ContentType.Any } } 35 | .any { ContentType.Application.contains(it) && "vcard+json".equals(it.contentSubtype, true) } 36 | 37 | override fun toString() = "[${types.joinToString(", ")}]" 38 | 39 | 40 | object Factory: PropertyFactory { 41 | 42 | override fun getName() = CardDAV.SupportedAddressData 43 | 44 | override fun create(parser: XmlPullParser): SupportedAddressData { 45 | val supportedTypes = mutableSetOf() 46 | 47 | XmlReader(parser).readContentTypes(CardDAV.AddressDataType, supportedTypes::add) 48 | 49 | return SupportedAddressData(supportedTypes) 50 | } 51 | 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/push/Trigger.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.property.push 12 | 13 | import at.bitfire.dav4jvm.Property 14 | import at.bitfire.dav4jvm.PropertyFactory 15 | import at.bitfire.dav4jvm.XmlUtils.propertyName 16 | import org.xmlpull.v1.XmlPullParser 17 | 18 | data class Trigger( 19 | val contentUpdate: ContentUpdate? = null, 20 | val propertyUpdate: PropertyUpdate? = null 21 | ) : Property { 22 | 23 | object Factory : PropertyFactory { 24 | 25 | override fun getName() = WebDAVPush.Trigger 26 | 27 | override fun create(parser: XmlPullParser): Trigger { 28 | var trigger = Trigger() 29 | 30 | val depth = parser.depth 31 | var eventType = parser.eventType 32 | while (!(eventType == XmlPullParser.END_TAG && parser.depth == depth)) { 33 | if (eventType == XmlPullParser.START_TAG && parser.depth == depth + 1) { 34 | when (parser.propertyName()) { 35 | WebDAVPush.ContentUpdate -> trigger = trigger.copy( 36 | contentUpdate = ContentUpdate.Factory.create(parser) 37 | ) 38 | WebDAVPush.PropertyUpdate -> trigger = trigger.copy( 39 | propertyUpdate = PropertyUpdate.Factory.create(parser) 40 | ) 41 | } 42 | } 43 | eventType = parser.next() 44 | } 45 | 46 | return trigger 47 | } 48 | 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/push/WebDAVPush.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.property.push 12 | 13 | import at.bitfire.dav4jvm.Property 14 | 15 | object WebDAVPush { 16 | 17 | /** 18 | * XML namespace of WebDAV-Push (draft), see: 19 | * https://github.com/bitfireAT/webdav-push/ 20 | * 21 | * Experimental! 22 | */ 23 | const val NS_WEBDAV_PUSH = "https://bitfire.at/webdav-push" 24 | 25 | val AuthSecret = Property.Name(NS_WEBDAV_PUSH, "auth-secret") 26 | val ContentUpdate = Property.Name(NS_WEBDAV_PUSH, "content-update") 27 | val Expires = Property.Name(NS_WEBDAV_PUSH, "expires") 28 | val PropertyUpdate = Property.Name(NS_WEBDAV_PUSH, "property-update") 29 | val PushMessage = Property.Name(NS_WEBDAV_PUSH, "push-message") 30 | val PushRegister = Property.Name(NS_WEBDAV_PUSH, "push-register") 31 | val PushResource = Property.Name(NS_WEBDAV_PUSH, "push-resource") 32 | val Subscription = Property.Name(NS_WEBDAV_PUSH, "subscription") 33 | val SubscriptionPublicKey = Property.Name(NS_WEBDAV_PUSH, "subscription-public-key") 34 | val SupportedTriggers = Property.Name(NS_WEBDAV_PUSH, "supported-triggers") 35 | val Topic = Property.Name(NS_WEBDAV_PUSH, "topic") 36 | val Transports = Property.Name(NS_WEBDAV_PUSH, "transports") 37 | val Trigger = Property.Name(NS_WEBDAV_PUSH, "trigger") 38 | val VapidPublicKey = Property.Name(NS_WEBDAV_PUSH, "vapid-public-key") 39 | val WebPush = Property.Name(NS_WEBDAV_PUSH, "web-push") 40 | val WebPushSubscription = Property.Name(NS_WEBDAV_PUSH, "web-push-subscription") 41 | 42 | } -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/push/PropertyUpdate.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.property.push 12 | 13 | import at.bitfire.dav4jvm.Property 14 | import at.bitfire.dav4jvm.PropertyFactory 15 | import at.bitfire.dav4jvm.XmlUtils.propertyName 16 | import at.bitfire.dav4jvm.property.webdav.SyncLevel 17 | import at.bitfire.dav4jvm.property.webdav.WebDAV 18 | import org.xmlpull.v1.XmlPullParser 19 | 20 | /** 21 | * Represents a [NS_WEBDAV_PUSH]`:property-update` property. 22 | * 23 | * Experimental! See https://github.com/bitfireAT/webdav-push/ 24 | */ 25 | data class PropertyUpdate( 26 | val syncLevel: SyncLevel? = null, 27 | ): Property { 28 | 29 | object Factory: PropertyFactory { 30 | 31 | override fun getName() = WebDAVPush.PropertyUpdate 32 | 33 | override fun create(parser: XmlPullParser): PropertyUpdate { 34 | var propertyUpdate = PropertyUpdate() 35 | 36 | val depth = parser.depth 37 | var eventType = parser.eventType 38 | while (!(eventType == XmlPullParser.END_TAG && parser.depth == depth)) { 39 | if (eventType == XmlPullParser.START_TAG && parser.depth == depth + 1) { 40 | when (parser.propertyName()) { 41 | WebDAV.SyncLevel -> propertyUpdate = propertyUpdate.copy( 42 | syncLevel = SyncLevel.Factory.create(parser) 43 | ) 44 | } 45 | } 46 | eventType = parser.next() 47 | } 48 | return propertyUpdate 49 | } 50 | 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/ktor/CallbackInterfaces.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.ktor 12 | 13 | import io.ktor.client.statement.HttpResponse 14 | 15 | /** 16 | * Callback for the OPTIONS request. 17 | */ 18 | fun interface CapabilitiesCallback { 19 | suspend fun onCapabilities(davCapabilities: Set, response: HttpResponse) 20 | } 21 | 22 | /** 23 | * Callback for 207 Multi-Status responses. 24 | */ 25 | fun interface MultiResponseCallback { 26 | /** 27 | * Called for every `` element in the `` body. For instance, 28 | * in response to a `PROPFIND` request, this callback will be called once for every found 29 | * member resource. 30 | * 31 | * Known collections have [response] `href` with trailing slash, see [Response.parse] for details. 32 | * 33 | * @param response the parsed response (including URL) 34 | * @param relation relation of the response to the called resource 35 | */ 36 | suspend fun onResponse(response: Response, relation: Response.HrefRelation) 37 | } 38 | 39 | /** 40 | * Callback for HTTP responses. 41 | */ 42 | fun interface ResponseCallback { 43 | /** 44 | * Called for a HTTP response. Typically this is only called for successful/redirect 45 | * responses because HTTP errors throw an exception before this callback is called. 46 | * 47 | * @param response scoped response that can be used to access the body 48 | * in a streaming way (**body won't be accessible anymore when 49 | * callback returns!**) 50 | */ 51 | suspend fun onResponse(response: HttpResponse) 52 | } -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/okhttp/OkHttpUtils.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.okhttp 12 | 13 | import okhttp3.HttpUrl 14 | import okhttp3.Response 15 | 16 | object OkHttpUtils { 17 | 18 | /** 19 | * Gets the resource name (the last segment of the path) from an URL. 20 | * Empty if the resource is the base directory. 21 | * 22 | * * `dir` for `https://example.com/dir/` 23 | * * `file` for `https://example.com/file` 24 | * * `` for `https://example.com` or `https://example.com/` 25 | * 26 | * @return resource name 27 | */ 28 | fun fileName(url: HttpUrl): String { 29 | val pathSegments = url.pathSegments.dropLastWhile { it == "" } 30 | return pathSegments.lastOrNull() ?: "" 31 | } 32 | 33 | /** 34 | * Gets all values of a header that is defined as a list [RFC 9110 5.6.1], 35 | * regardless of they're sent as one line or as multiple lines. 36 | * 37 | * For instance, regardless of whether a server sends: 38 | * 39 | * ``` 40 | * DAV: 1 41 | * DAV: 2 42 | * ``` 43 | * 44 | * or 45 | * 46 | * ``` 47 | * DAV: 1, 2 48 | * ``` 49 | * 50 | * this method would return `arrayOf("1","2")` for the `DAV` header. 51 | * 52 | * @param response the HTTP response to evaluate 53 | * @param name header name (for instance: `DAV`) 54 | * 55 | * @return all values for the given header name 56 | */ 57 | fun listHeader(response: Response, name: String): Array { 58 | val value = response.headers(name).joinToString(",") 59 | return value.split(',').filter { it.isNotEmpty() }.toTypedArray() 60 | } 61 | 62 | } -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/ktor/MultiStatusParser.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.ktor 12 | 13 | import at.bitfire.dav4jvm.Property 14 | import at.bitfire.dav4jvm.XmlReader 15 | import at.bitfire.dav4jvm.XmlUtils.propertyName 16 | import at.bitfire.dav4jvm.property.webdav.SyncToken 17 | import at.bitfire.dav4jvm.property.webdav.WebDAV 18 | import io.ktor.http.Url 19 | import org.xmlpull.v1.XmlPullParser 20 | 21 | /** 22 | * Parses a WebDAV `` XML response. 23 | * 24 | * @param location location of the request (used to resolve possible relative `` in responses) 25 | */ 26 | class MultiStatusParser( 27 | private val location: Url, 28 | private val callback: MultiResponseCallback 29 | ) { 30 | 31 | suspend fun parseResponse(parser: XmlPullParser): List { 32 | val responseProperties = mutableListOf() 33 | val responseParser = ResponseParser(location, callback) 34 | 35 | // 37 | val depth = parser.depth 38 | var eventType = parser.eventType 39 | while (!(eventType == XmlPullParser.END_TAG && parser.depth == depth)) { 40 | if (eventType == XmlPullParser.START_TAG && parser.depth == depth + 1) 41 | when (parser.propertyName()) { 42 | WebDAV.Response -> 43 | responseParser.parseResponse(parser) 44 | WebDAV.SyncToken -> 45 | XmlReader(parser).readText()?.let { 46 | responseProperties += SyncToken(it) 47 | } 48 | } 49 | eventType = parser.next() 50 | } 51 | 52 | return responseProperties 53 | } 54 | 55 | } -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/push/SupportedTriggers.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.property.push 12 | 13 | import at.bitfire.dav4jvm.Property 14 | import at.bitfire.dav4jvm.PropertyFactory 15 | import at.bitfire.dav4jvm.XmlUtils.propertyName 16 | import org.xmlpull.v1.XmlPullParser 17 | 18 | /** 19 | * Represents a [NS_WEBDAV_PUSH]`:content-update` property. 20 | * 21 | * Experimental! See https://github.com/bitfireAT/webdav-push/ 22 | */ 23 | data class SupportedTriggers( 24 | val contentUpdate: ContentUpdate? = null, 25 | val propertyUpdate: PropertyUpdate? = null 26 | ): Property { 27 | 28 | object Factory: PropertyFactory { 29 | 30 | override fun getName() = WebDAVPush.SupportedTriggers 31 | 32 | override fun create(parser: XmlPullParser): SupportedTriggers { 33 | var supportedTriggers = SupportedTriggers() 34 | 35 | val depth = parser.depth 36 | var eventType = parser.eventType 37 | while (!(eventType == XmlPullParser.END_TAG && parser.depth == depth)) { 38 | if (eventType == XmlPullParser.START_TAG && parser.depth == depth + 1) { 39 | when (parser.propertyName()) { 40 | WebDAVPush.ContentUpdate -> supportedTriggers = supportedTriggers.copy( 41 | contentUpdate = ContentUpdate.Factory.create(parser) 42 | ) 43 | WebDAVPush.PropertyUpdate -> supportedTriggers = supportedTriggers.copy( 44 | propertyUpdate = PropertyUpdate.Factory.create(parser) 45 | ) 46 | } 47 | } 48 | eventType = parser.next() 49 | } 50 | 51 | return supportedTriggers 52 | } 53 | 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/push/ContentUpdate.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.property.push 12 | 13 | import at.bitfire.dav4jvm.Property 14 | import at.bitfire.dav4jvm.PropertyFactory 15 | import at.bitfire.dav4jvm.XmlUtils.propertyName 16 | import at.bitfire.dav4jvm.property.webdav.Depth 17 | import at.bitfire.dav4jvm.property.webdav.SyncToken 18 | import at.bitfire.dav4jvm.property.webdav.WebDAV 19 | import org.xmlpull.v1.XmlPullParser 20 | 21 | /** 22 | * Represents a [NS_WEBDAV_PUSH]`:content-update` property. 23 | * 24 | * Experimental! See https://github.com/bitfireAT/webdav-push/ 25 | */ 26 | data class ContentUpdate( 27 | val depth: Depth? = null, 28 | val syncToken: SyncToken? = null 29 | ): Property { 30 | 31 | object Factory: PropertyFactory { 32 | 33 | override fun getName() = WebDAVPush.ContentUpdate 34 | 35 | override fun create(parser: XmlPullParser): ContentUpdate { 36 | var contentUpdate = ContentUpdate() 37 | 38 | val depth = parser.depth 39 | var eventType = parser.eventType 40 | while (!(eventType == XmlPullParser.END_TAG && parser.depth == depth)) { 41 | if (eventType == XmlPullParser.START_TAG && parser.depth == depth + 1) { 42 | when (parser.propertyName()) { 43 | WebDAV.SyncLevel -> contentUpdate = contentUpdate.copy( 44 | depth = Depth.Factory.create(parser) 45 | ) 46 | WebDAV.SyncToken -> contentUpdate = contentUpdate.copy( 47 | syncToken = SyncToken.Factory.create(parser) 48 | ) 49 | } 50 | } 51 | eventType = parser.next() 52 | } 53 | 54 | return contentUpdate 55 | } 56 | 57 | } 58 | 59 | } -------------------------------------------------------------------------------- /src/test/kotlin/at/bitfire/dav4jvm/property/OwnerTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.property 12 | 13 | import at.bitfire.dav4jvm.property.webdav.Owner 14 | import org.junit.Assert.assertEquals 15 | import org.junit.Assert.assertNull 16 | import org.junit.Assert.assertTrue 17 | import org.junit.Test 18 | 19 | class OwnerTest: PropertyTest() { 20 | 21 | @Test 22 | fun testOwner_Empty() { 23 | val results = parseProperty("") 24 | assertTrue(results.isEmpty()) 25 | } 26 | 27 | @Test 28 | fun testOwner_PlainText() { 29 | val results = parseProperty("https://example.com") 30 | val owner = results.first() as Owner 31 | assertNull(owner.href) 32 | } 33 | 34 | @Test 35 | fun testOwner_PlainTextAndHref() { 36 | val results = parseProperty("Principal Name. mailto:owner@example.com (test)") 37 | val owner = results.first() as Owner 38 | assertEquals("mailto:owner@example.com", owner.href) 39 | } 40 | 41 | @Test 42 | fun testOwner_Href() { 43 | val results = parseProperty("https://example.com") 44 | val owner = results.first() as Owner 45 | assertEquals("https://example.com", owner.href) 46 | } 47 | 48 | @Test 49 | fun testOwner_TwoHrefs() { 50 | val results = parseProperty("" + 51 | "https://example.com/owner1" + 52 | "https://example.com/owner2" + 53 | "") 54 | val owner = results.first() as Owner 55 | assertEquals("https://example.com/owner1", owner.href) 56 | } 57 | 58 | @Test 59 | fun testOwner_WithoutHref() { 60 | val results = parseProperty("invalid") 61 | assertTrue(results.isEmpty()) 62 | } 63 | 64 | } -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/push/PushMessage.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.property.push 12 | 13 | import at.bitfire.dav4jvm.Property 14 | import at.bitfire.dav4jvm.PropertyFactory 15 | import at.bitfire.dav4jvm.XmlUtils.propertyName 16 | import org.xmlpull.v1.XmlPullParser 17 | 18 | /** 19 | * Represents a [NS_WEBDAV_PUSH]`:push-message` property. 20 | * 21 | * Experimental! See https://github.com/bitfireAT/webdav-push/ 22 | */ 23 | data class PushMessage( 24 | val topic: Topic? = null, 25 | val contentUpdate: ContentUpdate? = null, 26 | val propertyUpdate: PropertyUpdate? = null 27 | ): Property { 28 | 29 | object Factory: PropertyFactory { 30 | 31 | override fun getName() = WebDAVPush.PushMessage 32 | 33 | override fun create(parser: XmlPullParser): PushMessage { 34 | var message = PushMessage() 35 | 36 | val depth = parser.depth 37 | var eventType = parser.eventType 38 | while (!(eventType == XmlPullParser.END_TAG && parser.depth == depth)) { 39 | if (eventType == XmlPullParser.START_TAG && parser.depth == depth + 1) { 40 | when (parser.propertyName()) { 41 | WebDAVPush.Topic -> message = message.copy( 42 | topic = Topic.Factory.create(parser) 43 | ) 44 | WebDAVPush.ContentUpdate -> message = message.copy( 45 | contentUpdate = ContentUpdate.Factory.create(parser) 46 | ) 47 | WebDAVPush.PropertyUpdate -> message = message.copy( 48 | propertyUpdate = PropertyUpdate.Factory.create(parser) 49 | ) 50 | } 51 | } 52 | eventType = parser.next() 53 | } 54 | 55 | return message 56 | } 57 | 58 | } 59 | 60 | } -------------------------------------------------------------------------------- /src/test/kotlin/at/bitfire/dav4jvm/property/GetETagTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.property 12 | 13 | import at.bitfire.dav4jvm.property.webdav.GetETag 14 | import org.junit.Assert.assertEquals 15 | import org.junit.Assert.assertFalse 16 | import org.junit.Assert.assertTrue 17 | import org.junit.Test 18 | 19 | class GetETagTest: PropertyTest() { 20 | 21 | @Test 22 | fun testGetETag_Strong() { 23 | val results = parseProperty("\"Correct strong ETag\"") 24 | val getETag = results.first() as GetETag 25 | assertEquals("Correct strong ETag", getETag.eTag) 26 | assertFalse(getETag.weak) 27 | } 28 | 29 | @Test 30 | fun testGetETag_Strong_NoQuotes() { 31 | val results = parseProperty("Strong ETag without quotes") 32 | val getETag = results.first() as GetETag 33 | assertEquals("Strong ETag without quotes", getETag.eTag) 34 | assertFalse(getETag.weak) 35 | } 36 | 37 | @Test 38 | fun testGetETag_Weak() { 39 | val results = parseProperty("W/\"Correct weak ETag\"") 40 | val getETag = results.first() as GetETag 41 | assertEquals("Correct weak ETag", getETag.eTag) 42 | assertTrue(getETag.weak) 43 | } 44 | 45 | @Test 46 | fun testGetETag_Weak_Empty() { 47 | val results = parseProperty("W/") 48 | val getETag = results.first() as GetETag 49 | assertEquals("", getETag.eTag) 50 | assertTrue(getETag.weak) 51 | } 52 | 53 | @Test 54 | fun testGetETag_Weak_NoQuotes() { 55 | val results = parseProperty("W/Weak ETag without quotes") 56 | val getETag = results.first() as GetETag 57 | assertEquals("Weak ETag without quotes", getETag.eTag) 58 | assertTrue(getETag.weak) 59 | } 60 | 61 | } -------------------------------------------------------------------------------- /src/test/kotlin/at/bitfire/dav4jvm/okhttp/property/OwnerTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.okhttp.property 12 | 13 | import at.bitfire.dav4jvm.property.webdav.Owner 14 | import org.junit.Assert.assertEquals 15 | import org.junit.Assert.assertNull 16 | import org.junit.Assert.assertTrue 17 | import org.junit.Test 18 | 19 | class OwnerTest: PropertyTest() { 20 | 21 | @Test 22 | fun testOwner_Empty() { 23 | val results = parseProperty("") 24 | assertTrue(results.isEmpty()) 25 | } 26 | 27 | @Test 28 | fun testOwner_PlainText() { 29 | val results = parseProperty("https://example.com") 30 | val owner = results.first() as Owner 31 | assertNull(owner.href) 32 | } 33 | 34 | @Test 35 | fun testOwner_PlainTextAndHref() { 36 | val results = parseProperty("Principal Name. mailto:owner@example.com (test)") 37 | val owner = results.first() as Owner 38 | assertEquals("mailto:owner@example.com", owner.href) 39 | } 40 | 41 | @Test 42 | fun testOwner_Href() { 43 | val results = parseProperty("https://example.com") 44 | val owner = results.first() as Owner 45 | assertEquals("https://example.com", owner.href) 46 | } 47 | 48 | @Test 49 | fun testOwner_TwoHrefs() { 50 | val results = parseProperty("" + 51 | "https://example.com/owner1" + 52 | "https://example.com/owner2" + 53 | "") 54 | val owner = results.first() as Owner 55 | assertEquals("https://example.com/owner1", owner.href) 56 | } 57 | 58 | @Test 59 | fun testOwner_WithoutHref() { 60 | val results = parseProperty("invalid") 61 | assertTrue(results.isEmpty()) 62 | } 63 | 64 | } -------------------------------------------------------------------------------- /src/test/kotlin/at/bitfire/dav4jvm/okhttp/property/GetETagTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.okhttp.property 12 | 13 | import at.bitfire.dav4jvm.property.webdav.GetETag 14 | import org.junit.Assert.assertEquals 15 | import org.junit.Assert.assertFalse 16 | import org.junit.Assert.assertTrue 17 | import org.junit.Test 18 | 19 | class GetETagTest: PropertyTest() { 20 | 21 | @Test 22 | fun testGetETag_Strong() { 23 | val results = parseProperty("\"Correct strong ETag\"") 24 | val getETag = results.first() as GetETag 25 | assertEquals("Correct strong ETag", getETag.eTag) 26 | assertFalse(getETag.weak) 27 | } 28 | 29 | @Test 30 | fun testGetETag_Strong_NoQuotes() { 31 | val results = parseProperty("Strong ETag without quotes") 32 | val getETag = results.first() as GetETag 33 | assertEquals("Strong ETag without quotes", getETag.eTag) 34 | assertFalse(getETag.weak) 35 | } 36 | 37 | @Test 38 | fun testGetETag_Weak() { 39 | val results = parseProperty("W/\"Correct weak ETag\"") 40 | val getETag = results.first() as GetETag 41 | assertEquals("Correct weak ETag", getETag.eTag) 42 | assertTrue(getETag.weak) 43 | } 44 | 45 | @Test 46 | fun testGetETag_Weak_Empty() { 47 | val results = parseProperty("W/") 48 | val getETag = results.first() as GetETag 49 | assertEquals("", getETag.eTag) 50 | assertTrue(getETag.weak) 51 | } 52 | 53 | @Test 54 | fun testGetETag_Weak_NoQuotes() { 55 | val results = parseProperty("W/Weak ETag without quotes") 56 | val getETag = results.first() as GetETag 57 | assertEquals("Weak ETag without quotes", getETag.eTag) 58 | assertTrue(getETag.weak) 59 | } 60 | 61 | } -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/push/WebPushSubscription.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.property.push 12 | 13 | import at.bitfire.dav4jvm.Property 14 | import at.bitfire.dav4jvm.PropertyFactory 15 | import at.bitfire.dav4jvm.XmlUtils.propertyName 16 | import org.xmlpull.v1.XmlPullParser 17 | 18 | /** 19 | * Represents a [NS_WEBDAV_PUSH]`:web-push-subscription` property. 20 | * 21 | * Experimental! See https://github.com/bitfireAT/webdav-push/ 22 | */ 23 | data class WebPushSubscription( 24 | val pushResource: PushResource? = null, 25 | val subscriptionPublicKey: SubscriptionPublicKey? = null, 26 | val authSecret: AuthSecret? = null 27 | ): Property { 28 | 29 | object Factory: PropertyFactory { 30 | 31 | override fun getName() = WebDAVPush.WebPushSubscription 32 | 33 | override fun create(parser: XmlPullParser): WebPushSubscription { 34 | var subscription = WebPushSubscription() 35 | 36 | val depth = parser.depth 37 | var eventType = parser.eventType 38 | while (!(eventType == XmlPullParser.END_TAG && parser.depth == depth)) { 39 | if (eventType == XmlPullParser.START_TAG && parser.depth == depth + 1) { 40 | when (parser.propertyName()) { 41 | WebDAVPush.PushResource -> 42 | subscription = subscription.copy(pushResource = PushResource.Factory.create(parser)) 43 | WebDAVPush.SubscriptionPublicKey -> 44 | subscription = subscription.copy(subscriptionPublicKey = SubscriptionPublicKey.Factory.create(parser)) 45 | WebDAVPush.AuthSecret -> 46 | subscription = subscription.copy(authSecret = AuthSecret.Factory.create(parser)) 47 | } 48 | } 49 | eventType = parser.next() 50 | } 51 | 52 | return subscription 53 | } 54 | 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/test/kotlin/at/bitfire/dav4jvm/okhttp/DavCalendarTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.okhttp 12 | 13 | import mockwebserver3.MockResponse 14 | import mockwebserver3.MockWebServer 15 | import okhttp3.OkHttpClient 16 | import org.junit.After 17 | import org.junit.Assert.assertEquals 18 | import org.junit.Before 19 | import org.junit.Test 20 | import java.time.Instant 21 | 22 | class DavCalendarTest { 23 | 24 | private val httpClient = OkHttpClient.Builder() 25 | .followRedirects(false) 26 | .build() 27 | private val mockServer = MockWebServer() 28 | 29 | @Before 30 | fun startServer() { 31 | mockServer.start() 32 | } 33 | 34 | @After 35 | fun stopServer() { 36 | mockServer.close() 37 | } 38 | 39 | 40 | @Test 41 | fun calendarQuery_formatStartEnd() { 42 | val cal = DavCalendar(httpClient, mockServer.url("/")) 43 | mockServer.enqueue(MockResponse.Builder().code(207).body("").build()) 44 | cal.calendarQuery("VEVENT", 45 | start = Instant.ofEpochSecond(784111777), 46 | end = Instant.ofEpochSecond(1689324577)) { _, _ -> } 47 | val rq = mockServer.takeRequest() 48 | assertEquals("" + 49 | "" + 50 | "" + 51 | "" + 52 | "" + 53 | "" + 54 | "" + 55 | "" + 56 | "" + 57 | "" + 58 | "" + 59 | "" + 60 | "", rq.body?.utf8()) 61 | } 62 | 63 | } -------------------------------------------------------------------------------- /src/test/kotlin/at/bitfire/dav4jvm/okhttp/exception/ServiceUnavailableExceptionTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.okhttp.exception 12 | 13 | import at.bitfire.dav4jvm.HttpUtils 14 | import okhttp3.Protocol 15 | import okhttp3.Request 16 | import okhttp3.Response 17 | import org.junit.Assert.assertNotNull 18 | import org.junit.Assert.assertNull 19 | import org.junit.Assert.assertTrue 20 | import org.junit.Test 21 | import java.time.Instant 22 | 23 | class ServiceUnavailableExceptionTest { 24 | 25 | val response503 = Response.Builder() 26 | .request( 27 | Request.Builder() 28 | .url("http://www.example.com") 29 | .get() 30 | .build() 31 | ) 32 | .protocol(Protocol.HTTP_1_1) 33 | .code(503).message("Try later") 34 | .build() 35 | 36 | @Test 37 | fun testRetryAfter_NoTime() { 38 | val e = ServiceUnavailableException(response503) 39 | assertNull(e.retryAfter) 40 | } 41 | 42 | @Test 43 | fun testRetryAfter_Seconds() { 44 | val response = response503.newBuilder() 45 | .header("Retry-After", "120") 46 | .build() 47 | val e = ServiceUnavailableException(response) 48 | assertNotNull(e.retryAfter) 49 | assertTrue(withinTimeRange(e.retryAfter!!, 120)) 50 | } 51 | 52 | @Test 53 | fun testRetryAfter_Date() { 54 | val after30min = Instant.now().plusSeconds(30 * 60) 55 | val response = response503.newBuilder() 56 | .header("Retry-After", HttpUtils.formatDate(after30min)) 57 | .build() 58 | val e = ServiceUnavailableException(response) 59 | assertNotNull(e.retryAfter) 60 | assertTrue(withinTimeRange(e.retryAfter!!, 30 * 60)) 61 | } 62 | 63 | 64 | private fun withinTimeRange(d: Instant, seconds: Long) = 65 | d.isBefore( 66 | Instant.now() 67 | .plusSeconds(seconds) 68 | .plusSeconds(5) // tolerance for test running 69 | ) 70 | 71 | } -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/caldav/CalendarColor.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.property.caldav 12 | 13 | import at.bitfire.dav4jvm.Property 14 | import at.bitfire.dav4jvm.PropertyFactory 15 | import at.bitfire.dav4jvm.XmlReader 16 | import org.xmlpull.v1.XmlPullParser 17 | import java.util.logging.Level 18 | import java.util.logging.Logger 19 | import java.util.regex.Pattern 20 | 21 | data class CalendarColor( 22 | val color: Int? 23 | ): Property { 24 | 25 | companion object { 26 | 27 | private val PATTERN = Pattern.compile("#?(\\p{XDigit}{6})(\\p{XDigit}{2})?")!! 28 | 29 | /** 30 | * Converts a WebDAV color from one of these formats: 31 | * #RRGGBB (alpha = 0xFF) 32 | * RRGGBB (alpha = 0xFF) 33 | * #RRGGBBAA 34 | * RRGGBBAA 35 | * to an [Int] with alpha. 36 | */ 37 | @Throws(IllegalArgumentException::class) 38 | fun parseARGBColor(davColor: String): Int { 39 | val m = PATTERN.matcher(davColor) 40 | if (m.find()) { 41 | val color_rgb = Integer.parseInt(m.group(1), 16) 42 | val color_alpha = m.group(2)?.let { Integer.parseInt(m.group(2), 16) and 0xFF } ?: 0xFF 43 | return (color_alpha shl 24) or color_rgb 44 | } else 45 | throw IllegalArgumentException("Couldn't parse color value: $davColor") 46 | } 47 | } 48 | 49 | 50 | object Factory: PropertyFactory { 51 | 52 | override fun getName() = CalDAV.CalendarColor 53 | 54 | override fun create(parser: XmlPullParser): CalendarColor { 55 | XmlReader(parser).readText()?.let { 56 | try { 57 | return CalendarColor(parseARGBColor(it)) 58 | } catch (e: IllegalArgumentException) { 59 | val logger = Logger.getLogger(javaClass.name) 60 | logger.log(Level.WARNING, "Couldn't parse color, ignoring", e) 61 | } 62 | } 63 | return CalendarColor(null) 64 | } 65 | 66 | } 67 | 68 | } -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/webdav/ResourceType.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.property.webdav 12 | 13 | import at.bitfire.dav4jvm.Property 14 | import at.bitfire.dav4jvm.PropertyFactory 15 | import at.bitfire.dav4jvm.property.caldav.CalDAV 16 | import at.bitfire.dav4jvm.property.carddav.CardDAV 17 | import org.xmlpull.v1.XmlPullParser 18 | 19 | class ResourceType( 20 | val types: Set = emptySet() 21 | ): Property { 22 | 23 | companion object { 24 | 25 | 26 | } 27 | 28 | 29 | object Factory: PropertyFactory { 30 | 31 | override fun getName() = WebDAV.ResourceType 32 | 33 | override fun create(parser: XmlPullParser): ResourceType { 34 | val types = mutableSetOf() 35 | 36 | val depth = parser.depth 37 | var eventType = parser.eventType 38 | while (!(eventType == XmlPullParser.END_TAG && parser.depth == depth)) { 39 | if (eventType == XmlPullParser.START_TAG && parser.depth == depth + 1) { 40 | // use static objects to allow types.contains() 41 | var typeName = Property.Name(parser.namespace, parser.name) 42 | when (typeName) { // if equals(), replace by our instance 43 | WebDAV.Collection -> typeName = WebDAV.Collection 44 | WebDAV.Principal -> typeName = WebDAV.Principal 45 | CardDAV.Addressbook -> typeName = CardDAV.Addressbook 46 | CalDAV.Calendar -> typeName = CalDAV.Calendar 47 | CalDAV.CalendarProxyRead -> typeName = CalDAV.CalendarProxyRead 48 | CalDAV.CalendarProxyWrite -> typeName = CalDAV.CalendarProxyWrite 49 | CalDAV.Subscribed -> typeName = CalDAV.Subscribed 50 | } 51 | types.add(typeName) 52 | } 53 | eventType = parser.next() 54 | } 55 | assert(parser.depth == depth) 56 | 57 | return ResourceType(types) 58 | } 59 | 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/okhttp/PropStat.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.okhttp 12 | 13 | import at.bitfire.dav4jvm.Property 14 | import at.bitfire.dav4jvm.XmlUtils.propertyName 15 | import at.bitfire.dav4jvm.property.webdav.WebDAV 16 | import okhttp3.Protocol 17 | import okhttp3.internal.http.StatusLine 18 | import org.xmlpull.v1.XmlPullParser 19 | import java.net.ProtocolException 20 | import java.util.LinkedList 21 | 22 | /** 23 | * Represents a WebDAV propstat XML element. 24 | * 25 | * 26 | */ 27 | data class PropStat( 28 | val properties: List, 29 | val status: StatusLine, 30 | val error: List? = null 31 | ) { 32 | 33 | companion object { 34 | 35 | private val ASSUMING_OK = StatusLine(Protocol.HTTP_1_1, 200, "Assuming OK") 36 | private val INVALID_STATUS = StatusLine(Protocol.HTTP_1_1, 500, "Invalid status line") 37 | 38 | fun parse(parser: XmlPullParser): PropStat { 39 | val depth = parser.depth 40 | 41 | var status: StatusLine? = null 42 | val prop = LinkedList() 43 | 44 | var eventType = parser.eventType 45 | while (!(eventType == XmlPullParser.END_TAG && parser.depth == depth)) { 46 | if (eventType == XmlPullParser.START_TAG && parser.depth == depth + 1) 47 | when (parser.propertyName()) { 48 | WebDAV.Prop -> 49 | prop.addAll(Property.parse(parser)) 50 | WebDAV.Status -> 51 | status = try { 52 | StatusLine.parse(parser.nextText()) 53 | } catch (e: ProtocolException) { 54 | // invalid status line, treat as 500 Internal Server Error 55 | INVALID_STATUS 56 | } 57 | } 58 | eventType = parser.next() 59 | } 60 | 61 | return PropStat(prop, status ?: ASSUMING_OK) 62 | } 63 | 64 | } 65 | 66 | 67 | fun isSuccess() = status.code/100 == 2 68 | 69 | } -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/push/PushRegister.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.property.push 12 | 13 | import at.bitfire.dav4jvm.HttpUtils 14 | import at.bitfire.dav4jvm.Property 15 | import at.bitfire.dav4jvm.PropertyFactory 16 | import at.bitfire.dav4jvm.XmlReader 17 | import at.bitfire.dav4jvm.XmlUtils.propertyName 18 | import org.xmlpull.v1.XmlPullParser 19 | import java.time.Instant 20 | 21 | /** 22 | * Represents a [NS_WEBDAV_PUSH]`:push-register` property. 23 | * 24 | * Experimental! See https://github.com/bitfireAT/webdav-push/ 25 | */ 26 | data class PushRegister( 27 | val expires: Instant? = null, 28 | val subscription: Subscription? = null, 29 | val trigger: Trigger? = null 30 | ): Property { 31 | 32 | object Factory: PropertyFactory { 33 | 34 | override fun getName() = WebDAVPush.PushRegister 35 | 36 | override fun create(parser: XmlPullParser): PushRegister { 37 | var register = PushRegister() 38 | 39 | val depth = parser.depth 40 | var eventType = parser.eventType 41 | while (!(eventType == XmlPullParser.END_TAG && parser.depth == depth)) { 42 | if (eventType == XmlPullParser.START_TAG && parser.depth == depth + 1) 43 | when (parser.propertyName()) { 44 | WebDAVPush.Expires -> 45 | register = register.copy( 46 | expires = XmlReader(parser).readText()?.let { 47 | HttpUtils.parseDate(it) 48 | } 49 | ) 50 | WebDAVPush.Subscription -> 51 | register = register.copy( 52 | subscription = Subscription.Factory.create(parser) 53 | ) 54 | WebDAVPush.Trigger -> 55 | register = register.copy( 56 | trigger = Trigger.Factory.create(parser) 57 | ) 58 | } 59 | eventType = parser.next() 60 | } 61 | 62 | return register 63 | } 64 | 65 | } 66 | 67 | } -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/okhttp/exception/HttpException.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.okhttp.exception 12 | 13 | import at.bitfire.dav4jvm.Error 14 | import okhttp3.Response 15 | import javax.annotation.WillNotClose 16 | 17 | /** 18 | * Signals that a HTTP error was sent by the server in the context of a WebDAV operation. 19 | */ 20 | open class HttpException( 21 | message: String? = null, 22 | cause: Throwable? = null, 23 | override val statusCode: Int, 24 | requestExcerpt: String?, 25 | responseExcerpt: String?, 26 | errors: List = emptyList() 27 | ): DavException(message, cause, statusCode, requestExcerpt, responseExcerpt, errors) { 28 | 29 | // constructor from Response 30 | 31 | /** 32 | * Takes the request, response and errors from a given HTTP response. 33 | * 34 | * @param response response to extract status code and request/response excerpt from (if possible) 35 | * @param message optional exception message 36 | * @param cause optional exception cause 37 | */ 38 | constructor( 39 | @WillNotClose response: Response, 40 | message: String = "HTTP ${response.code} ${response.message}", 41 | cause: Throwable? = null 42 | ) : this(HttpResponseInfo.fromResponse(response), message, cause) 43 | 44 | private constructor( 45 | httpResponseInfo: HttpResponseInfo, 46 | message: String?, 47 | cause: Throwable? = null 48 | ): this( 49 | message = message, 50 | cause = cause, 51 | statusCode = httpResponseInfo.statusCode, 52 | requestExcerpt = httpResponseInfo.requestExcerpt, 53 | responseExcerpt = httpResponseInfo.responseExcerpt, 54 | errors = httpResponseInfo.errors 55 | ) 56 | 57 | 58 | // status code classes 59 | 60 | /** Whether the [statusCode] is 3xx and thus indicates a redirection. */ 61 | val isRedirect 62 | get() = statusCode / 100 == 3 63 | 64 | /** Whether the [statusCode] is 4xx and thus indicates a client error. */ 65 | val isClientError 66 | get() = statusCode / 100 == 4 67 | 68 | /** Whether the [statusCode] is 5xx and thus indicates a server error. */ 69 | val isServerError 70 | get() = statusCode / 100 == 5 71 | 72 | } -------------------------------------------------------------------------------- /src/test/kotlin/at/bitfire/dav4jvm/PropertyTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm 12 | 13 | import at.bitfire.dav4jvm.property.webdav.GetETag 14 | import org.junit.Assert.assertEquals 15 | import org.junit.Test 16 | import org.xmlpull.v1.XmlPullParser 17 | import org.xmlpull.v1.XmlPullParserFactory 18 | import java.io.StringReader 19 | 20 | class PropertyTest { 21 | 22 | @Test 23 | fun testParse_InvalidProperty() { 24 | val parser = XmlPullParserFactory.newInstance().apply { 25 | isNamespaceAware = true 26 | }.newPullParser() 27 | parser.setInput(StringReader("")) 28 | do { 29 | parser.next() 30 | } while (parser.eventType != XmlPullParser.START_TAG && parser.name != "multistatus") 31 | 32 | // we're now at the start of 33 | assertEquals(XmlPullParser.START_TAG, parser.eventType) 34 | assertEquals("multistatus", parser.name) 35 | 36 | // parse invalid DAV:getetag 37 | Property.Companion.parse(parser).let { 38 | assertEquals(1, it.size) 39 | assertEquals(GetETag(null), it[0]) 40 | } 41 | 42 | // we're now at the end of 43 | assertEquals(XmlPullParser.END_TAG, parser.eventType) 44 | assertEquals("multistatus", parser.name) 45 | } 46 | 47 | @Test 48 | fun testParse_ValidProperty() { 49 | val parser = XmlPullParserFactory.newInstance().apply { 50 | isNamespaceAware = true 51 | }.newPullParser() 52 | parser.setInput(StringReader("12345")) 53 | do { 54 | parser.next() 55 | } while (parser.eventType != XmlPullParser.START_TAG && parser.name != "multistatus") 56 | 57 | // we're now at the start of 58 | assertEquals(XmlPullParser.START_TAG, parser.eventType) 59 | assertEquals("multistatus", parser.name) 60 | 61 | val etag = Property.Companion.parse(parser).first() 62 | assertEquals(GetETag("12345"), etag) 63 | 64 | // we're now at the end of 65 | assertEquals(XmlPullParser.END_TAG, parser.eventType) 66 | assertEquals("multistatus", parser.name) 67 | } 68 | 69 | } -------------------------------------------------------------------------------- /src/test/kotlin/at/bitfire/dav4jvm/ktor/DavCalendarTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.ktor 12 | 13 | import io.ktor.client.HttpClient 14 | import io.ktor.client.engine.mock.MockEngine 15 | import io.ktor.client.engine.mock.respond 16 | import io.ktor.client.engine.mock.toByteArray 17 | import io.ktor.http.ContentType 18 | import io.ktor.http.HttpHeaders 19 | import io.ktor.http.HttpStatusCode 20 | import io.ktor.http.Url 21 | import io.ktor.http.headersOf 22 | import io.ktor.http.withCharset 23 | import kotlinx.coroutines.test.runTest 24 | import org.junit.Assert.assertEquals 25 | import org.junit.Test 26 | import java.time.Instant 27 | 28 | class DavCalendarTest { 29 | 30 | @Test 31 | fun calendarQuery_formatStartEnd() = runTest { 32 | val mockEngine = MockEngine { request -> 33 | respond( 34 | content = "", 35 | status = HttpStatusCode.MultiStatus, // 207 36 | headers = headersOf(HttpHeaders.ContentType, ContentType.Text.Xml.withCharset(Charsets.UTF_8).toString()) 37 | ) 38 | } 39 | val httpClient = HttpClient(mockEngine) { followRedirects = false } 40 | val cal = DavCalendar(httpClient, Url("/")) 41 | 42 | cal.calendarQuery( 43 | "VEVENT", 44 | start = Instant.ofEpochSecond(784111777), 45 | end = Instant.ofEpochSecond(1689324577) 46 | ) { _, _ -> } 47 | 48 | assertEquals( 49 | "" + 50 | "" + 51 | "" + 52 | "" + 53 | "" + 54 | "" + 55 | "" + 56 | "" + 57 | "" + 58 | "" + 59 | "" + 60 | "" + 61 | "", 62 | mockEngine.requestHistory.last().body.toByteArray().toString(Charsets.UTF_8) 63 | ) 64 | } 65 | 66 | } -------------------------------------------------------------------------------- /src/test/kotlin/at/bitfire/dav4jvm/ktor/PropertyTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.ktor 12 | 13 | import at.bitfire.dav4jvm.Property 14 | import at.bitfire.dav4jvm.property.webdav.GetETag 15 | import org.junit.Assert.assertEquals 16 | import org.junit.Test 17 | import org.xmlpull.v1.XmlPullParser 18 | import org.xmlpull.v1.XmlPullParserFactory 19 | import java.io.StringReader 20 | 21 | class PropertyTest { 22 | 23 | @Test 24 | fun testParse_InvalidProperty() { 25 | val parser = XmlPullParserFactory.newInstance().apply { 26 | isNamespaceAware = true 27 | }.newPullParser() 28 | parser.setInput(StringReader("")) 29 | do { 30 | parser.next() 31 | } while (parser.eventType != XmlPullParser.START_TAG && parser.name != "multistatus") 32 | 33 | // we're now at the start of 34 | assertEquals(XmlPullParser.START_TAG, parser.eventType) 35 | assertEquals("multistatus", parser.name) 36 | 37 | // parse invalid DAV:getetag 38 | Property.parse(parser).let { 39 | assertEquals(1, it.size) 40 | assertEquals(GetETag(null), it[0]) 41 | } 42 | 43 | // we're now at the end of 44 | assertEquals(XmlPullParser.END_TAG, parser.eventType) 45 | assertEquals("multistatus", parser.name) 46 | } 47 | 48 | @Test 49 | fun testParse_ValidProperty() { 50 | val parser = XmlPullParserFactory.newInstance().apply { 51 | isNamespaceAware = true 52 | }.newPullParser() 53 | parser.setInput(StringReader("12345")) 54 | do { 55 | parser.next() 56 | } while (parser.eventType != XmlPullParser.START_TAG && parser.name != "multistatus") 57 | 58 | // we're now at the start of 59 | assertEquals(XmlPullParser.START_TAG, parser.eventType) 60 | assertEquals("multistatus", parser.name) 61 | 62 | val etag = Property.parse(parser).first() 63 | assertEquals(GetETag("12345"), etag) 64 | 65 | // we're now at the end of 66 | assertEquals(XmlPullParser.END_TAG, parser.eventType) 67 | assertEquals("multistatus", parser.name) 68 | } 69 | 70 | } -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/Property.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm 12 | 13 | import org.xmlpull.v1.XmlPullParser 14 | import java.io.Serializable 15 | import java.util.LinkedList 16 | import java.util.logging.Level 17 | import java.util.logging.Logger 18 | 19 | /** 20 | * Represents a WebDAV property. 21 | * 22 | * Every [Property] must define a static field (use `@JvmStatic`) called `NAME` of type [Property.Name], 23 | * which will be accessed by reflection. 24 | * 25 | * Every [Property] should be a data class in order to be able to compare it against others, and convert to a useful 26 | * string for debugging. 27 | */ 28 | interface Property { 29 | 30 | data class Name( 31 | val namespace: String, 32 | val name: String 33 | ): Serializable { 34 | 35 | override fun toString() = "$namespace:$name" 36 | 37 | } 38 | 39 | companion object { 40 | 41 | fun parse(parser: XmlPullParser): List { 42 | val logger = Logger.getLogger(Property::javaClass.name) 43 | 44 | // 45 | val depth = parser.depth 46 | val properties = LinkedList() 47 | 48 | var eventType = parser.eventType 49 | while (!(eventType == XmlPullParser.END_TAG && parser.depth == depth)) { 50 | if (eventType == XmlPullParser.START_TAG && parser.depth == depth + 1) { 51 | val depthBeforeParsing = parser.depth 52 | val name = Name(parser.namespace, parser.name) 53 | 54 | try { 55 | val property = PropertyRegistry.create(name, parser) 56 | assert(parser.depth == depthBeforeParsing) 57 | 58 | if (property != null) { 59 | properties.add(property) 60 | } else 61 | logger.fine("Ignoring unknown property $name") 62 | } catch (e: Exception) { // catching here generic exception in order to avoid dependency on specific okhttp or ktor Exception 63 | logger.log(Level.WARNING, "Ignoring invalid property", e) 64 | } 65 | } 66 | 67 | eventType = parser.next() 68 | } 69 | 70 | return properties 71 | } 72 | 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/caldav/SupportedCalendarComponentSet.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.property.caldav 12 | 13 | import at.bitfire.dav4jvm.Property 14 | import at.bitfire.dav4jvm.PropertyFactory 15 | import at.bitfire.dav4jvm.XmlUtils.propertyName 16 | import org.xmlpull.v1.XmlPullParser 17 | 18 | data class SupportedCalendarComponentSet( 19 | val supportsEvents: Boolean, 20 | val supportsTasks: Boolean, 21 | val supportsJournal: Boolean 22 | ): Property { 23 | 24 | 25 | object Factory: PropertyFactory { 26 | 27 | override fun getName() = CalDAV.SupportedCalendarComponentSet 28 | 29 | override fun create(parser: XmlPullParser): SupportedCalendarComponentSet { 30 | /* 31 | 32 | 33 | */ 34 | var components = SupportedCalendarComponentSet( 35 | supportsEvents = false, 36 | supportsTasks = false, 37 | supportsJournal = false 38 | ) 39 | 40 | val depth = parser.depth 41 | var eventType = parser.eventType 42 | while (!(eventType == XmlPullParser.END_TAG && parser.depth == depth)) { 43 | if (eventType == XmlPullParser.START_TAG && parser.depth == depth + 1) { 44 | when (parser.propertyName()) { 45 | CalDAV.AllComp -> { 46 | components = SupportedCalendarComponentSet( 47 | supportsEvents = true, 48 | supportsTasks = true, 49 | supportsJournal = true 50 | ) 51 | } 52 | CalDAV.Comp -> 53 | when (parser.getAttributeValue(null, "name")?.uppercase()) { 54 | "VEVENT" -> components = components.copy(supportsEvents = true) 55 | "VTODO" -> components = components.copy(supportsTasks = true) 56 | "VJOURNAL" -> components = components.copy(supportsJournal = true) 57 | } 58 | } 59 | } 60 | eventType = parser.next() 61 | } 62 | 63 | return components 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/webdav/GetETag.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.property.webdav 12 | 13 | import at.bitfire.dav4jvm.Property 14 | import at.bitfire.dav4jvm.PropertyFactory 15 | import at.bitfire.dav4jvm.QuotedStringUtils 16 | import at.bitfire.dav4jvm.XmlReader 17 | import io.ktor.client.statement.HttpResponse 18 | import io.ktor.http.HttpHeaders 19 | import okhttp3.Response 20 | import org.xmlpull.v1.XmlPullParser 21 | 22 | /** 23 | * The GetETag property. 24 | * 25 | * Can also be used to parse ETags from HTTP responses – just pass the raw ETag 26 | * header value to the constructor and then use [eTag] and [weak]. 27 | */ 28 | data class GetETag( 29 | val rawETag: String? 30 | ): Property { 31 | 32 | companion object { 33 | 34 | fun fromHttpResponse(response: HttpResponse) = 35 | response.headers[HttpHeaders.ETag]?.let { GetETag(it) } 36 | 37 | fun fromResponse(response: Response) = 38 | response.header(HttpHeaders.ETag)?.let { GetETag(it) } 39 | } 40 | 41 | /** 42 | * The parsed ETag value, excluding the weakness indicator and the quotes. 43 | */ 44 | val eTag: String? 45 | 46 | /** 47 | * Whether the ETag is weak. 48 | */ 49 | var weak: Boolean 50 | 51 | init { 52 | /* entity-tag = [ weak ] opaque-tag 53 | weak = "W/" 54 | opaque-tag = quoted-string 55 | */ 56 | 57 | if (rawETag != null) { 58 | val tag: String? 59 | // remove trailing "W/" 60 | if (rawETag.startsWith("W/")) { 61 | // entity tag is weak 62 | tag = rawETag.substring(2) 63 | weak = true 64 | } else { 65 | tag = rawETag 66 | weak = false 67 | } 68 | eTag = QuotedStringUtils.decodeQuotedString(tag) 69 | } else { 70 | eTag = null 71 | weak = false 72 | } 73 | } 74 | 75 | override fun equals(other: Any?): Boolean { 76 | if (other !is GetETag) 77 | return false 78 | return eTag == other.eTag && weak == other.weak 79 | } 80 | 81 | override fun hashCode(): Int { 82 | return eTag.hashCode() xor weak.hashCode() 83 | } 84 | 85 | 86 | object Factory: PropertyFactory { 87 | 88 | override fun getName() = WebDAV.GetETag 89 | 90 | override fun create(parser: XmlPullParser): GetETag = 91 | GetETag(XmlReader(parser).readText()) 92 | 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/XmlUtils.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm 12 | 13 | import org.xmlpull.v1.XmlPullParser 14 | import org.xmlpull.v1.XmlPullParserException 15 | import org.xmlpull.v1.XmlPullParserFactory 16 | import org.xmlpull.v1.XmlSerializer 17 | 18 | object XmlUtils { 19 | 20 | /** 21 | * Requests the parser to be as lenient as possible when parsing invalid XML. 22 | * 23 | * See [https://www.xmlpull.org/](xmlpull.org) and specific implementations, for instance 24 | * [Android XML](https://developer.android.com/reference/android/util/Xml#FEATURE_RELAXED) 25 | */ 26 | private const val FEATURE_RELAXED = "http://xmlpull.org/v1/doc/features.html#relaxed" 27 | 28 | /** [XmlPullParserFactory] that is namespace-aware and does relaxed parsing */ 29 | private val relaxedFactory = 30 | XmlPullParserFactory.newInstance().apply { 31 | isNamespaceAware = true 32 | setFeature(FEATURE_RELAXED, true) 33 | } 34 | 35 | /** [XmlPullParserFactory] that is namespace-aware */ 36 | private val standardFactory: XmlPullParserFactory = 37 | XmlPullParserFactory.newInstance().apply { 38 | isNamespaceAware = true 39 | } 40 | 41 | /** 42 | * Creates a new [XmlPullParser]. 43 | * 44 | * First tries to create a namespace-aware parser that supports [FEATURE_RELAXED]. If that 45 | * fails, it falls back to a namespace-aware parser without relaxed parsing. 46 | * 47 | * @throws XmlPullParserException when no parser could be created 48 | */ 49 | fun newPullParser(): XmlPullParser = 50 | try { 51 | relaxedFactory.newPullParser() 52 | } catch (_: XmlPullParserException) { 53 | // FEATURE_RELAXED may not be supported, try without it 54 | null 55 | } 56 | ?: standardFactory.newPullParser() 57 | 58 | fun newSerializer(): XmlSerializer = standardFactory.newSerializer() 59 | 60 | 61 | fun XmlSerializer.insertTag(name: Property.Name, contentGenerator: XmlSerializer.() -> Unit = {}) { 62 | startTag(name.namespace, name.name) 63 | contentGenerator(this) 64 | endTag(name.namespace, name.name) 65 | } 66 | 67 | fun XmlPullParser.propertyName(): Property.Name { 68 | val propNs = namespace 69 | val propName = name 70 | if (propNs == null || propName == null) 71 | throw IllegalStateException("Current event must be START_TAG or END_TAG") 72 | return Property.Name(propNs, propName) 73 | } 74 | 75 | } -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/ktor/Response.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.ktor 12 | 13 | import at.bitfire.dav4jvm.Error 14 | import at.bitfire.dav4jvm.Property 15 | import io.ktor.http.HttpStatusCode 16 | import io.ktor.http.Url 17 | import io.ktor.http.isSuccess 18 | 19 | /** 20 | * Represents a WebDAV response XML Element. 21 | * 22 | * 24 | */ 25 | @Suppress("unused") 26 | data class Response( 27 | /** 28 | * URL of the requested resource. For instance, if `this` is a result 29 | * of a PROPFIND request, the `requestedUrl` would be the URL where the 30 | * PROPFIND request has been sent to (usually the collection URL). 31 | */ 32 | val requestedUrl: Url, 33 | 34 | /** 35 | * URL of this response (`href` element) 36 | */ 37 | val href: Url, 38 | 39 | /** 40 | * status of this response (`status` XML element) 41 | */ 42 | val status: HttpStatusCode?, 43 | 44 | /** 45 | * property/status elements (`propstat` XML elements) 46 | */ 47 | val propstat: List, 48 | 49 | /** 50 | * list of precondition/postcondition elements (`error` XML elements) 51 | */ 52 | val error: List? = null, 53 | 54 | /** 55 | * new location of this response (`location` XML element), used for redirects 56 | */ 57 | val newLocation: Url? = null 58 | ) { 59 | 60 | enum class HrefRelation { 61 | SELF, MEMBER, OTHER 62 | } 63 | 64 | /** 65 | * All properties from propstat elements with empty status or status code 2xx. 66 | */ 67 | val properties: List by lazy { 68 | if (isSuccess()) 69 | propstat.filter { it.status.isSuccess() }.map { it.properties }.flatten() 70 | else 71 | emptyList() 72 | } 73 | 74 | /** 75 | * Convenience method to get a certain property with empty status or status code 2xx 76 | * from the current response. 77 | */ 78 | operator fun get(clazz: Class) = 79 | properties.filterIsInstance(clazz).firstOrNull() 80 | 81 | /** 82 | * Returns whether the request was successful. 83 | * 84 | * @return true: no status XML element or status code 2xx; false: otherwise 85 | */ 86 | fun isSuccess() = status == null || status.isSuccess() 87 | 88 | /** 89 | * Returns the name (last path segment) of the resource. 90 | */ 91 | fun hrefName() = KtorHttpUtils.fileName(href) 92 | 93 | } -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/caldav/CalDAV.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.property.caldav 12 | 13 | import at.bitfire.dav4jvm.Property 14 | 15 | object CalDAV { 16 | 17 | // CalDAV (RFC 4791) 18 | 19 | const val NS_CALDAV = "urn:ietf:params:xml:ns:caldav" 20 | 21 | val AllComp = Property.Name(NS_CALDAV, "allcomp") 22 | val Calendar = Property.Name(NS_CALDAV, "calendar") 23 | val Comp = Property.Name(NS_CALDAV, "comp") 24 | val CalendarData = Property.Name(NS_CALDAV, "calendar-data") 25 | val CalendarDescription = Property.Name(NS_CALDAV, "calendar-description") 26 | val CalendarHomeSet = Property.Name(NS_CALDAV, "calendar-home-set") 27 | val CalendarMultiget = Property.Name(NS_CALDAV, "calendar-multiget") 28 | val CalendarQuery = Property.Name(NS_CALDAV, "calendar-query") 29 | val CalendarTimezone = Property.Name(NS_CALDAV, "calendar-timezone") 30 | val CompFilter = Property.Name(NS_CALDAV, "comp-filter") 31 | val Filter = Property.Name(NS_CALDAV, "filter") 32 | val MaxResourceSize = Property.Name(NS_CALDAV, "max-resource-size") 33 | val SupportedCalendarComponentSet = Property.Name(NS_CALDAV, "supported-calendar-component-set") 34 | val SupportedCalendarData = Property.Name(NS_CALDAV, "supported-calendar-data") 35 | val TimeRange = Property.Name(NS_CALDAV, "time-range") 36 | 37 | 38 | // Scheduling Extensions to CalDAV (RFC 6638) 39 | 40 | val CalendarUserAddressSet = Property.Name(NS_CALDAV, "calendar-user-address-set") 41 | val ScheduleTag = Property.Name(NS_CALDAV, "schedule-tag") 42 | 43 | 44 | // Calendaring Extensions to WebDAV (CalDAV): Time Zones by Reference (RFC 7809) 45 | 46 | val CalendarTimezoneId = Property.Name(NS_CALDAV, "calendar-timezone-id") 47 | 48 | 49 | // Apple XML elements 50 | 51 | const val NS_APPLE_ICAL = "http://apple.com/ns/ical/" 52 | 53 | val CalendarColor = Property.Name(NS_APPLE_ICAL, "calendar-color") 54 | 55 | 56 | // CalendarServer XML elements 57 | 58 | const val NS_CALENDARSERVER = "http://calendarserver.org/ns/" 59 | 60 | val CalendarProxyRead = Property.Name(NS_CALENDARSERVER, "calendar-proxy-read") 61 | val CalendarProxyReadFor = Property.Name(NS_CALENDARSERVER, "calendar-proxy-read-for") 62 | val CalendarProxyWrite = Property.Name(NS_CALENDARSERVER, "calendar-proxy-write") 63 | val CalendarProxyWriteFor = Property.Name(NS_CALENDARSERVER, "calendar-proxy-write-for") 64 | val GetCTag = Property.Name(NS_CALENDARSERVER, "getctag") 65 | val Source = Property.Name(NS_CALENDARSERVER, "source") 66 | val Subscribed = Property.Name(NS_CALENDARSERVER, "subscribed") 67 | 68 | } -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /src/test/kotlin/at/bitfire/dav4jvm/ktor/exception/ServiceUnavailableExceptionTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.ktor.exception 12 | 13 | import at.bitfire.dav4jvm.HttpUtils 14 | import io.ktor.client.HttpClient 15 | import io.ktor.client.engine.mock.MockEngine 16 | import io.ktor.client.engine.mock.respond 17 | import io.ktor.client.engine.mock.respondError 18 | import io.ktor.client.request.get 19 | import io.ktor.http.HttpHeaders 20 | import io.ktor.http.HttpStatusCode 21 | import io.ktor.http.Url 22 | import io.ktor.http.headersOf 23 | import kotlinx.coroutines.test.runTest 24 | import org.junit.Assert.assertNotNull 25 | import org.junit.Assert.assertNull 26 | import org.junit.Assert.assertTrue 27 | import org.junit.Test 28 | import java.time.Instant 29 | 30 | class ServiceUnavailableExceptionTest { 31 | 32 | private val sampleUrl = Url("http://www.example.com") 33 | 34 | @Test 35 | fun testRetryAfter_NoTime() = runTest { 36 | val mockEngine = MockEngine { 37 | respondError(HttpStatusCode.ServiceUnavailable) // 503 38 | } 39 | val httpClient = HttpClient(mockEngine) 40 | val response = httpClient.get(sampleUrl) 41 | val ex = HttpException.fromResponse(response) as ServiceUnavailableException 42 | assertNull(ex.retryAfter) 43 | assertNull(ex.retryAfterAbs) 44 | } 45 | 46 | @Test 47 | fun testRetryAfter_Seconds() = runTest { 48 | val mockEngine = MockEngine { 49 | respond( 50 | content = "", 51 | status = HttpStatusCode.ServiceUnavailable, // 503 52 | headers = headersOf(HttpHeaders.RetryAfter, "120") 53 | ) 54 | } 55 | val httpClient = HttpClient(mockEngine) 56 | 57 | val response = httpClient.get(sampleUrl) 58 | val ex = HttpException.fromResponse(response) as ServiceUnavailableException 59 | assertNotNull(ex.retryAfter) 60 | assertTrue(withinTimeRange(ex.retryAfterAbs!!, 120)) 61 | } 62 | 63 | @Test 64 | fun testRetryAfter_Date() = runTest { 65 | val after30min = Instant.now().plusSeconds(30*60) 66 | val mockEngine = MockEngine { 67 | respondError( 68 | status = HttpStatusCode.ServiceUnavailable, // 503 69 | headers = headersOf(HttpHeaders.RetryAfter, HttpUtils.formatDate(after30min)) 70 | ) 71 | } 72 | val httpClient = HttpClient(mockEngine) 73 | 74 | val response = httpClient.get(sampleUrl) 75 | val ex = HttpException.fromResponse(response) as ServiceUnavailableException 76 | assertNotNull(ex.retryAfter) 77 | assertTrue(withinTimeRange(ex.retryAfterAbs!!, 30*60)) 78 | } 79 | 80 | 81 | // helpers 82 | 83 | private fun withinTimeRange(d: Instant, seconds: Long) = 84 | d.isBefore( 85 | Instant.now() 86 | .plusSeconds(seconds) 87 | .plusSeconds(5) // tolerance for test running 88 | ) 89 | 90 | } -------------------------------------------------------------------------------- /src/test/kotlin/at/bitfire/dav4jvm/HttpUtilsTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm 12 | 13 | import at.bitfire.dav4jvm.HttpUtils.toHttpUrl 14 | import at.bitfire.dav4jvm.HttpUtils.toKtorUrl 15 | import io.ktor.http.Url 16 | import okhttp3.HttpUrl.Companion.toHttpUrl 17 | import org.junit.Assert.assertEquals 18 | import org.junit.Assert.assertNull 19 | import org.junit.Test 20 | import java.time.Instant 21 | import java.time.LocalDate 22 | import java.time.LocalTime 23 | import java.time.ZoneOffset 24 | import java.time.ZonedDateTime 25 | import java.time.format.DateTimeFormatter 26 | import java.util.Calendar 27 | import java.util.Locale 28 | import java.util.TimeZone 29 | import java.util.logging.Logger 30 | 31 | class HttpUtilsTest { 32 | 33 | @Test 34 | fun formatDate() { 35 | val cal = Calendar.getInstance(TimeZone.getTimeZone("GMT")) 36 | cal.set(2023, 4, 11, 17, 26, 35) 37 | cal.timeZone = TimeZone.getTimeZone("UTC") 38 | assertEquals("Sun, 06 Nov 1994 08:49:37 GMT", HttpUtils.formatDate( 39 | ZonedDateTime.of( 40 | LocalDate.of(1994, 11, 6), 41 | LocalTime.of(8, 49, 37), 42 | ZoneOffset.UTC 43 | ).toInstant() 44 | )) 45 | } 46 | 47 | 48 | @Test 49 | fun parseDate_IMF_FixDate() { 50 | // RFC 7231 IMF-fixdate (preferred format) 51 | assertEquals(Instant.ofEpochSecond(784111777), HttpUtils.parseDate("Sun, 06 Nov 1994 08:49:37 GMT")) 52 | } 53 | 54 | @Test 55 | fun parseDate_RFC850_1994() { 56 | // obsolete RFC 850 format – fails when run after year 2000 because 06 Nov 2094 (!) is not a Sunday 57 | assertNull(HttpUtils.parseDate("Sun, 06-Nov-94 08:49:37 GMT")) 58 | } 59 | 60 | @Test 61 | fun parseDate_RFC850_2004_CEST() { 62 | // obsolete RFC 850 format with European time zone 63 | assertEquals(Instant.ofEpochSecond(1689317377), HttpUtils.parseDate("Friday, 14-Jul-23 08:49:37 CEST")) 64 | } 65 | 66 | @Test 67 | fun parseDate_RFC850_2004_GMT() { 68 | // obsolete RFC 850 format 69 | assertEquals(Instant.ofEpochSecond(1689324577), HttpUtils.parseDate("Friday, 14-Jul-23 08:49:37 GMT")) 70 | } 71 | 72 | @Test 73 | fun parseDate_ANSI_C() { 74 | // ANSI C's asctime() format 75 | val logger = Logger.getLogger(javaClass.name) 76 | logger.info("Expected date: " + DateTimeFormatter.ofPattern("EEE MMM ppd HH:mm:ss yyyy", Locale.US).format(ZonedDateTime.now())) 77 | 78 | assertEquals(Instant.ofEpochSecond(784111777), HttpUtils.parseDate("Sun Nov 6 08:49:37 1994")) 79 | } 80 | 81 | 82 | @Test 83 | fun testHttpUrl_toKtorUrl() { 84 | assertEquals(Url("https://example.com:123/path"), "https://example.com:123/path".toHttpUrl().toKtorUrl()) 85 | } 86 | 87 | @Test 88 | fun testUrl_ToHttpUrl() { 89 | assertEquals("https://example.com:123/path".toHttpUrl(), Url("https://example.com:123/path").toHttpUrl()) 90 | } 91 | 92 | } -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/okhttp/exception/DavException.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.okhttp.exception 12 | 13 | import at.bitfire.dav4jvm.Error 14 | import okhttp3.Response 15 | import javax.annotation.WillNotClose 16 | 17 | /** 18 | * Signals that an error occurred during a WebDAV-related operation. 19 | * 20 | * This could be a logical error like when a required ETag has not been received, but also an explicit HTTP error 21 | * (usually with a subclass of [HttpException], which in turn extends this class). 22 | * 23 | * Often, HTTP response bodies contain valuable information about the error in text format (for instance, a HTML page 24 | * that contains details about the error) and/or as `` XML elements. However, such response bodies 25 | * are sometimes very large. 26 | * 27 | * So, if possible and useful, a size-limited excerpt of the associated HTTP request and response can be 28 | * attached and subsequently included in application-level debug info or shown to the user. 29 | * 30 | * Note: [Exception] is serializable, so objects of this class must contain only serializable objects. 31 | * 32 | * @param statusCode status code of associated HTTP response 33 | * @param requestExcerpt cached excerpt of associated HTTP request body 34 | * @param responseExcerpt cached excerpt of associated HTTP response body 35 | * @param errors precondition/postcondition XML elements which have been found in the XML response 36 | */ 37 | open class DavException( 38 | message: String? = null, 39 | cause: Throwable? = null, 40 | open val statusCode: Int? = null, 41 | val requestExcerpt: String? = null, 42 | val responseExcerpt: String? = null, 43 | val errors: List = emptyList() 44 | ): Exception(message, cause) { 45 | 46 | // constructor from Response 47 | 48 | /** 49 | * Takes the request, response and errors from a given HTTP response. 50 | * 51 | * @param message optional exception message 52 | * @param cause optional exception cause 53 | * @param response response to extract status code and request/response excerpt from (if possible) 54 | */ 55 | constructor( 56 | message: String, 57 | cause: Throwable? = null, 58 | @WillNotClose response: Response 59 | ) : this(message, cause, HttpResponseInfo.fromResponse(response)) 60 | 61 | private constructor( 62 | message: String?, 63 | cause: Throwable? = null, 64 | httpResponseInfo: HttpResponseInfo 65 | ): this( 66 | message = message, 67 | cause = cause, 68 | statusCode = httpResponseInfo.statusCode, 69 | requestExcerpt = httpResponseInfo.requestExcerpt, 70 | responseExcerpt = httpResponseInfo.responseExcerpt, 71 | errors = httpResponseInfo.errors 72 | ) 73 | 74 | 75 | companion object { 76 | 77 | /** 78 | * maximum size of extracted response body 79 | */ 80 | const val MAX_EXCERPT_SIZE = 20*1024 81 | 82 | } 83 | 84 | } -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/okhttp/exception/ServiceUnavailableException.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.okhttp.exception 12 | 13 | import at.bitfire.dav4jvm.HttpUtils 14 | import at.bitfire.dav4jvm.okhttp.exception.ServiceUnavailableException.Companion.DELAY_UNTIL_DEFAULT 15 | import at.bitfire.dav4jvm.okhttp.exception.ServiceUnavailableException.Companion.DELAY_UNTIL_MAX 16 | import at.bitfire.dav4jvm.okhttp.exception.ServiceUnavailableException.Companion.DELAY_UNTIL_MIN 17 | import okhttp3.Response 18 | import java.time.Instant 19 | import java.util.logging.Level 20 | import java.util.logging.Logger 21 | 22 | class ServiceUnavailableException(response: Response) : HttpException(response) { 23 | 24 | private val logger 25 | get() = Logger.getLogger(javaClass.name) 26 | 27 | val retryAfter: Instant? 28 | 29 | init { 30 | if (response.code != 503) 31 | throw IllegalArgumentException("Status code must be 503") 32 | 33 | // Retry-After = "Retry-After" ":" ( HTTP-date | delta-seconds ) 34 | // HTTP-date = rfc1123-date | rfc850-date | asctime-date 35 | 36 | var retryAfterValue: Instant? = null 37 | response.header("Retry-After")?.let { after -> 38 | retryAfterValue = HttpUtils.parseDate(after) ?: 39 | // not a HTTP-date, must be delta-seconds 40 | try { 41 | val seconds = after.toLong() 42 | Instant.now().plusSeconds(seconds) 43 | } catch (e: NumberFormatException) { 44 | logger.log(Level.WARNING, "Received Retry-After which was not a HTTP-date nor delta-seconds: $after", e) 45 | null 46 | } 47 | } 48 | 49 | retryAfter = retryAfterValue 50 | } 51 | 52 | 53 | /** 54 | * Returns appropriate sync retry delay in seconds, considering the servers suggestion 55 | * in [retryAfter] ([DELAY_UNTIL_DEFAULT] if no server suggestion). 56 | * 57 | * Takes current time into account to calculate intervals. Interval 58 | * will be restricted to values between [DELAY_UNTIL_MIN] and [DELAY_UNTIL_MAX]. 59 | * 60 | * @param start timestamp to calculate the delay from (default: now) 61 | * 62 | * @return until when to wait before sync can be retried 63 | */ 64 | fun getDelayUntil(start: Instant = Instant.now()): Instant { 65 | if (retryAfter == null) 66 | return start.plusSeconds(DELAY_UNTIL_DEFAULT) 67 | 68 | // take server suggestion, but restrict to plausible min/max values 69 | return retryAfter.coerceIn( 70 | minimumValue = start.plusSeconds(DELAY_UNTIL_MIN), 71 | maximumValue = start.plusSeconds(DELAY_UNTIL_MAX) 72 | ) 73 | } 74 | 75 | 76 | companion object { 77 | 78 | // default values for getDelayUntil 79 | const val DELAY_UNTIL_DEFAULT = 15 * 60L // 15 min 80 | const val DELAY_UNTIL_MIN = 1 * 60L // 1 min 81 | const val DELAY_UNTIL_MAX = 2 * 60 * 60L // 2 hours 82 | 83 | } 84 | 85 | } -------------------------------------------------------------------------------- /src/test/kotlin/at/bitfire/dav4jvm/okhttp/exception/DavExceptionTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.okhttp.exception 12 | 13 | import at.bitfire.dav4jvm.Error 14 | import at.bitfire.dav4jvm.Property 15 | import okhttp3.MediaType.Companion.toMediaType 16 | import okhttp3.Protocol 17 | import okhttp3.Request 18 | import okhttp3.Response 19 | import okhttp3.ResponseBody.Companion.toResponseBody 20 | import org.junit.Assert.assertEquals 21 | import org.junit.Assert.assertTrue 22 | import org.junit.Test 23 | import java.io.ByteArrayInputStream 24 | import java.io.ByteArrayOutputStream 25 | import java.io.FileNotFoundException 26 | import java.io.ObjectInputStream 27 | import java.io.ObjectOutputStream 28 | 29 | class DavExceptionTest { 30 | 31 | @Test 32 | fun fromResponse() { 33 | val request = Request.Builder() 34 | .get() 35 | .url("https://example.com") 36 | .build() 37 | val cause = Exception() 38 | val result = Response.Builder() 39 | .request(request) 40 | .protocol(Protocol.HTTP_1_1) 41 | .code(200) 42 | .message("OK") 43 | .body("Your Information".toResponseBody("text/plain".toMediaType())) 44 | .build() 45 | .use { response -> 46 | DavException("Message", cause, response) 47 | } 48 | assertEquals("Message", result.message) 49 | assertEquals(cause, result.cause) 50 | assertEquals(200, result.statusCode) 51 | assertEquals("GET https://example.com/", result.requestExcerpt) 52 | assertEquals("Your Information", result.responseExcerpt) 53 | assertTrue(result.errors.isEmpty()) 54 | } 55 | 56 | @Test 57 | fun `is Java-serializable`() { 58 | val ex = DavException( 59 | message = "Some Error", 60 | statusCode = 500, 61 | requestExcerpt = "Request Body", 62 | responseExcerpt = "Response Body", 63 | errors = listOf( 64 | Error(Property.Name("Serialized", "Name")) 65 | ), 66 | cause = FileNotFoundException() 67 | ) 68 | 69 | // serialize (Java-style as in Serializable interface, not Kotlin serialization) 70 | val blob = ByteArrayOutputStream().use { baos -> 71 | ObjectOutputStream(baos).use { oos -> 72 | oos.writeObject(ex) 73 | } 74 | baos.toByteArray() 75 | } 76 | 77 | // deserialize 78 | ByteArrayInputStream(blob).use { bais -> 79 | ObjectInputStream(bais).use { ois -> 80 | val actual = ois.readObject() as DavException 81 | assertEquals(ex.message, actual.message) 82 | assertEquals(ex.statusCode, actual.statusCode) 83 | assertEquals(ex.requestExcerpt, actual.requestExcerpt) 84 | assertEquals(ex.responseExcerpt, actual.responseExcerpt) 85 | assertEquals(ex.errors, actual.errors) 86 | assertTrue(actual.cause is FileNotFoundException) 87 | } 88 | } 89 | } 90 | 91 | } -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/ktor/exception/DavException.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.ktor.exception 12 | 13 | import at.bitfire.dav4jvm.Error 14 | import io.ktor.client.statement.HttpResponse 15 | import io.ktor.utils.io.ByteReadChannel 16 | 17 | /** 18 | * Signals that an error occurred during a WebDAV-related operation. 19 | * 20 | * This could be a logical error like when a required ETag has not been received, but also an explicit HTTP error 21 | * (usually with a subclass of [HttpException], which in turn extends this class). 22 | * 23 | * Often, HTTP response bodies contain valuable information about the error in text format (for instance, a HTML page 24 | * that contains details about the error) and/or as `` XML elements. However, such response bodies 25 | * are sometimes very large. 26 | * 27 | * So, if possible and useful, a size-limited excerpt of the associated HTTP request and response can be 28 | * attached and subsequently included in application-level debug info or shown to the user. 29 | * 30 | * Note: [Exception] is serializable, so objects of this class must contain only serializable objects. 31 | * 32 | * @param statusCode status code of associated HTTP response 33 | * @param requestExcerpt cached excerpt of associated HTTP request body 34 | * @param responseExcerpt cached excerpt of associated HTTP response body 35 | * @param errors precondition/postcondition XML elements which have been found in the XML response 36 | */ 37 | open class DavException( 38 | message: String? = null, 39 | open val statusCode: Int? = null, 40 | val requestExcerpt: String? = null, 41 | val responseExcerpt: String? = null, 42 | val errors: List = emptyList(), 43 | cause: Throwable? = null 44 | ): Exception(message, cause) { 45 | 46 | companion object { 47 | 48 | /** 49 | * Creates a [DavException] from the request, response and errors of a given HTTP response. 50 | * 51 | * @param message optional exception message 52 | * @param cause optional exception cause 53 | * @param response response to extract status code and request/response excerpt from (if possible) 54 | * @param responseBodyChannel optional existing response body channel that can be used to read the response body 55 | */ 56 | suspend fun fromResponse( 57 | message: String, 58 | response: HttpResponse, 59 | responseBodyChannel: ByteReadChannel? = null, 60 | cause: Throwable? = null 61 | ): DavException { 62 | val responseInfo = HttpResponseInfo.fromResponse(response, responseBodyChannel) 63 | return DavException( 64 | message = message, 65 | cause = cause, 66 | statusCode = responseInfo.status.value, 67 | requestExcerpt = responseInfo.requestExcerpt, 68 | responseExcerpt = responseInfo.responseExcerpt, 69 | errors = responseInfo.errors 70 | ) 71 | } 72 | 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/HttpUtils.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm 12 | 13 | import at.bitfire.dav4jvm.HttpUtils.httpDateFormat 14 | import io.ktor.http.Url 15 | import okhttp3.HttpUrl 16 | import okhttp3.HttpUrl.Companion.toHttpUrl 17 | import java.time.Instant 18 | import java.time.LocalDateTime 19 | import java.time.ZoneOffset 20 | import java.time.ZonedDateTime 21 | import java.time.format.DateTimeFormatter 22 | import java.time.format.DateTimeParseException 23 | import java.util.Locale 24 | import java.util.logging.Logger 25 | 26 | object HttpUtils { 27 | 28 | /** 29 | * Preferred HTTP date/time format, see RFC 7231 7.1.1.1 IMF-fixdate 30 | */ 31 | private const val httpDateFormatStr = "EEE, dd MMM yyyy HH:mm:ss ZZZZ" 32 | private val httpDateFormat = DateTimeFormatter.ofPattern(httpDateFormatStr, Locale.US) 33 | 34 | private val logger 35 | get() = Logger.getLogger(javaClass.name) 36 | 37 | /** 38 | * Formats a date for use in HTTP headers using [httpDateFormat]. 39 | * 40 | * @param date date to be formatted 41 | * 42 | * @return date in HTTP-date format 43 | */ 44 | fun formatDate(date: Instant): String = 45 | ZonedDateTime.ofInstant(date, ZoneOffset.UTC).format(httpDateFormat) 46 | 47 | /** 48 | * Parses a HTTP-date according to RFC 7231 section 7.1.1.1. 49 | * 50 | * @param dateStr date-time formatted in one of the three accepted formats: 51 | * 52 | * - preferred format (`IMF-fixdate`) 53 | * - obsolete RFC 850 format 54 | * - ANSI C's `asctime()` format 55 | * 56 | * @return date-time, or null if date could not be parsed 57 | */ 58 | fun parseDate(dateStr: String): Instant? { 59 | val zonedFormats = arrayOf( 60 | // preferred format 61 | httpDateFormat, 62 | 63 | // obsolete RFC 850 format 64 | DateTimeFormatter.ofPattern("EEEE, dd-MMM-yy HH:mm:ss zzz", Locale.US), 65 | ) 66 | 67 | // try the two formats with zone info 68 | for (format in zonedFormats) 69 | try { 70 | return ZonedDateTime.parse(dateStr, format).toInstant() 71 | } catch (ignored: DateTimeParseException) { 72 | } 73 | 74 | // try ANSI C's asctime() format 75 | try { 76 | val formatC = DateTimeFormatter.ofPattern("EEE MMM ppd HH:mm:ss yyyy", Locale.US) 77 | val local = LocalDateTime.parse(dateStr, formatC) 78 | return local.atZone(ZoneOffset.UTC).toInstant() 79 | } catch (ignored: DateTimeParseException) { 80 | } 81 | 82 | // no success in parsing 83 | logger.warning("Couldn't parse HTTP date: $dateStr, ignoring") 84 | return null 85 | } 86 | 87 | 88 | // for migration between Ktor and okhttp 89 | 90 | /** 91 | * Converts an okhttp [HttpUrl] to a Ktor [Url]. 92 | */ 93 | fun HttpUrl.toKtorUrl() = Url(toString()) 94 | 95 | /** 96 | * Converts a Ktor [Url] to an okhttp [HttpUrl]. 97 | */ 98 | fun Url.toHttpUrl() = toString().toHttpUrl() 99 | 100 | } -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/webdav/CurrentUserPrivilegeSet.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.property.webdav 12 | 13 | import at.bitfire.dav4jvm.Property 14 | import at.bitfire.dav4jvm.PropertyFactory 15 | import at.bitfire.dav4jvm.XmlReader 16 | import at.bitfire.dav4jvm.XmlUtils.propertyName 17 | import org.xmlpull.v1.XmlPullParser 18 | 19 | data class CurrentUserPrivilegeSet( 20 | // not all privileges from RFC 3744 are implemented by now 21 | // feel free to add more if you need them for your project 22 | val mayRead: Boolean = false, 23 | val mayWriteProperties: Boolean = false, 24 | val mayWriteContent: Boolean = false, 25 | val mayBind: Boolean = false, 26 | val mayUnbind: Boolean = false 27 | ): Property { 28 | 29 | object Factory: PropertyFactory { 30 | 31 | override fun getName() = WebDAV.CurrentUserPrivilegeSet 32 | 33 | override fun create(parser: XmlPullParser): CurrentUserPrivilegeSet { 34 | // 35 | // 36 | var privs = CurrentUserPrivilegeSet() 37 | 38 | XmlReader(parser).processTag(WebDAV.Privilege) { 39 | val depth = parser.depth 40 | var eventType = parser.eventType 41 | while (!(eventType == XmlPullParser.END_TAG && parser.depth == depth)) { 42 | if (eventType == XmlPullParser.START_TAG && parser.depth == depth + 1) 43 | when (parser.propertyName()) { 44 | WebDAV.Read -> 45 | privs = privs.copy(mayRead = true) 46 | WebDAV.Write -> { 47 | privs = privs.copy( 48 | mayBind = true, 49 | mayUnbind = true, 50 | mayWriteProperties = true, 51 | mayWriteContent = true 52 | ) 53 | } 54 | WebDAV.WriteProperties -> 55 | privs = privs.copy(mayWriteProperties = true) 56 | WebDAV.WriteContent -> 57 | privs = privs.copy(mayWriteContent = true) 58 | WebDAV.Bind -> 59 | privs = privs.copy(mayBind = true) 60 | WebDAV.Unbind -> 61 | privs = privs.copy(mayUnbind = true) 62 | WebDAV.All -> { 63 | privs = privs.copy( 64 | mayRead = true, 65 | mayBind = true, 66 | mayUnbind = true, 67 | mayWriteProperties = true, 68 | mayWriteContent = true 69 | ) 70 | } 71 | } 72 | eventType = parser.next() 73 | } 74 | } 75 | 76 | return privs 77 | } 78 | 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/ktor/exception/ServiceUnavailableException.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.ktor.exception 12 | 13 | import at.bitfire.dav4jvm.HttpUtils 14 | import at.bitfire.dav4jvm.ktor.exception.ServiceUnavailableException.Companion.DELAY_UNTIL_DEFAULT 15 | import at.bitfire.dav4jvm.ktor.exception.ServiceUnavailableException.Companion.DELAY_UNTIL_MAX 16 | import at.bitfire.dav4jvm.ktor.exception.ServiceUnavailableException.Companion.DELAY_UNTIL_MIN 17 | import io.ktor.http.HttpStatusCode 18 | import java.time.Instant 19 | 20 | class ServiceUnavailableException internal constructor( 21 | responseInfo: HttpResponseInfo, 22 | 23 | /** unprocessed value of the `Retry-After` header */ 24 | val retryAfter: String? 25 | ): HttpException( 26 | status = responseInfo.status, 27 | requestExcerpt = responseInfo.requestExcerpt, 28 | responseExcerpt = responseInfo.responseExcerpt, 29 | errors = responseInfo.errors 30 | ) { 31 | 32 | init { 33 | if (responseInfo.status != HttpStatusCode.ServiceUnavailable) 34 | throw IllegalArgumentException("Status must be ${HttpStatusCode.ServiceUnavailable}") 35 | } 36 | 37 | /** 38 | * absolute time of [retryAfter] (if available) 39 | */ 40 | val retryAfterAbs: Instant? = 41 | if (retryAfter != null) { 42 | // Retry-After = "Retry-After" ":" ( HTTP-date | delta-seconds ) 43 | // HTTP-date = rfc1123-date | rfc850-date | asctime-date 44 | HttpUtils.parseDate(retryAfter) // parse as HTTP-date, if possible 45 | ?: parseAsSeconds(retryAfter) // not a HTTP-date, must be delta-seconds 46 | } else 47 | null 48 | 49 | private fun parseAsSeconds(retryAfter: String): Instant? { 50 | return try { 51 | val seconds = retryAfter.toLong() 52 | Instant.now().plusSeconds(seconds) 53 | } catch (_: NumberFormatException) { 54 | null 55 | } 56 | } 57 | 58 | /** 59 | * Returns appropriate sync retry delay in seconds, considering the servers suggestion 60 | * in [retryAfter] ([DELAY_UNTIL_DEFAULT] if no server suggestion). 61 | * 62 | * Takes current time into account to calculate intervals. Interval 63 | * will be restricted to values between [DELAY_UNTIL_MIN] and [DELAY_UNTIL_MAX]. 64 | * 65 | * @param start timestamp to calculate the delay from (default: now) 66 | * 67 | * @return until when to wait before sync can be retried 68 | */ 69 | fun getDelayUntil(start: Instant = Instant.now()): Instant { 70 | if (retryAfterAbs == null) 71 | return start.plusSeconds(DELAY_UNTIL_DEFAULT) 72 | 73 | // take server suggestion, but restrict to plausible min/max values 74 | return retryAfterAbs.coerceIn( 75 | minimumValue = start.plusSeconds(DELAY_UNTIL_MIN), 76 | maximumValue = start.plusSeconds(DELAY_UNTIL_MAX) 77 | ) 78 | } 79 | 80 | 81 | companion object { 82 | 83 | // default values for getDelayUntil 84 | const val DELAY_UNTIL_DEFAULT = 15 * 60L // 15 min 85 | const val DELAY_UNTIL_MIN = 1 * 60L // 1 min 86 | const val DELAY_UNTIL_MAX = 2 * 60 * 60L // 2 hours 87 | 88 | } 89 | 90 | } -------------------------------------------------------------------------------- /src/test/kotlin/at/bitfire/dav4jvm/ktor/exception/DavExceptionTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.ktor.exception 12 | 13 | import at.bitfire.dav4jvm.Error 14 | import at.bitfire.dav4jvm.Property 15 | import io.ktor.client.HttpClient 16 | import io.ktor.client.engine.mock.MockEngine 17 | import io.ktor.client.engine.mock.respond 18 | import io.ktor.client.request.get 19 | import io.ktor.http.ContentType 20 | import io.ktor.http.HttpHeaders 21 | import io.ktor.http.HttpStatusCode 22 | import io.ktor.http.Url 23 | import io.ktor.http.headersOf 24 | import kotlinx.coroutines.test.runTest 25 | import org.junit.Assert.assertEquals 26 | import org.junit.Assert.assertTrue 27 | import org.junit.Test 28 | import java.io.ByteArrayInputStream 29 | import java.io.ByteArrayOutputStream 30 | import java.io.FileNotFoundException 31 | import java.io.ObjectInputStream 32 | import java.io.ObjectOutputStream 33 | 34 | class DavExceptionTest { 35 | 36 | private val sampleUrl = Url("https://127.0.0.1/dav/") 37 | 38 | @Test 39 | fun fromResponse() = runTest { 40 | val mockEngine = MockEngine { 41 | respond( 42 | status = HttpStatusCode.OK, 43 | content = "Your Information", 44 | headers = headersOf(HttpHeaders.ContentType, ContentType.Text.Plain.toString()) 45 | ) 46 | } 47 | val httpClient = HttpClient(mockEngine) 48 | val response = httpClient.get(sampleUrl) 49 | val cause = Exception() 50 | val result = DavException.fromResponse("Unexpected response", response, cause = cause) 51 | 52 | assertEquals("Unexpected response", result.message) 53 | assertEquals(cause, result.cause) 54 | assertEquals(200, result.statusCode) 55 | assertEquals("GET $sampleUrl", result.requestExcerpt) 56 | assertEquals("Your Information", result.responseExcerpt) 57 | assertTrue(result.errors.isEmpty()) 58 | } 59 | 60 | @Test 61 | fun `is Java-serializable`() { 62 | val ex = DavException( 63 | message = "Some Error", 64 | statusCode = 500, 65 | requestExcerpt = "Request Body", 66 | responseExcerpt = "Response Body", 67 | errors = listOf( 68 | Error(Property.Name("Serialized", "Name")) 69 | ), 70 | cause = FileNotFoundException() 71 | ) 72 | 73 | // serialize (Java-style as in Serializable interface, not Kotlin serialization) 74 | val blob = ByteArrayOutputStream().use { baos -> 75 | ObjectOutputStream(baos).use { oos -> 76 | oos.writeObject(ex) 77 | } 78 | baos.toByteArray() 79 | } 80 | 81 | // deserialize 82 | ByteArrayInputStream(blob).use { bais -> 83 | ObjectInputStream(bais).use { ois -> 84 | val actual = ois.readObject() as DavException 85 | assertEquals(ex.message, actual.message) 86 | assertEquals(ex.statusCode, actual.statusCode) 87 | assertEquals(ex.requestExcerpt, actual.requestExcerpt) 88 | assertEquals(ex.responseExcerpt, actual.responseExcerpt) 89 | assertEquals(ex.errors, actual.errors) 90 | assertTrue(actual.cause is FileNotFoundException) 91 | } 92 | } 93 | } 94 | 95 | } -------------------------------------------------------------------------------- /src/test/kotlin/at/bitfire/dav4jvm/okhttp/UrlUtilsTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.okhttp 12 | 13 | import at.bitfire.dav4jvm.okhttp.UrlUtils 14 | import at.bitfire.dav4jvm.okhttp.equalsForWebDAV 15 | import okhttp3.HttpUrl.Companion.toHttpUrl 16 | import org.junit.Assert.assertEquals 17 | import org.junit.Assert.assertFalse 18 | import org.junit.Assert.assertNull 19 | import org.junit.Assert.assertTrue 20 | import org.junit.Test 21 | 22 | class UrlUtilsTest { 23 | 24 | @Test 25 | fun testHostToDomain() { 26 | assertNull(UrlUtils.hostToDomain(null)) 27 | assertEquals("", UrlUtils.hostToDomain(".")) 28 | assertEquals("com", UrlUtils.hostToDomain("com")) 29 | assertEquals("com", UrlUtils.hostToDomain("com.")) 30 | assertEquals("example.com", UrlUtils.hostToDomain("example.com")) 31 | assertEquals("example.com", UrlUtils.hostToDomain("example.com.")) 32 | assertEquals("example.com", UrlUtils.hostToDomain(".example.com")) 33 | assertEquals("example.com", UrlUtils.hostToDomain(".example.com.")) 34 | assertEquals("example.com", UrlUtils.hostToDomain("host.example.com")) 35 | assertEquals("example.com", UrlUtils.hostToDomain("host.example.com.")) 36 | assertEquals("example.com", UrlUtils.hostToDomain("sub.host.example.com")) 37 | assertEquals("example.com", UrlUtils.hostToDomain("sub.host.example.com.")) 38 | } 39 | 40 | @Test 41 | fun testOmitTrailingSlash() { 42 | assertEquals("http://host/resource".toHttpUrl(), UrlUtils.omitTrailingSlash("http://host/resource".toHttpUrl())) 43 | assertEquals("http://host/resource".toHttpUrl(), UrlUtils.omitTrailingSlash("http://host/resource/".toHttpUrl())) 44 | } 45 | 46 | @Test 47 | fun testWithTrailingSlash() { 48 | assertEquals("http://host/resource/".toHttpUrl(), UrlUtils.withTrailingSlash("http://host/resource".toHttpUrl())) 49 | assertEquals("http://host/resource/".toHttpUrl(), UrlUtils.withTrailingSlash("http://host/resource/".toHttpUrl())) 50 | } 51 | 52 | 53 | @Test 54 | fun testHttpUrl_EqualsForWebDAV() { 55 | assertTrue("http://host/resource".toHttpUrl().equalsForWebDAV("http://host/resource".toHttpUrl())) 56 | assertTrue("http://host:80/resource".toHttpUrl().equalsForWebDAV("http://host/resource".toHttpUrl())) 57 | assertTrue("https://HOST:443/resource".toHttpUrl().equalsForWebDAV("https://host/resource".toHttpUrl())) 58 | assertTrue("https://host:443/my@dav/".toHttpUrl().equalsForWebDAV("https://host/my%40dav/".toHttpUrl())) 59 | assertTrue("http://host/resource".toHttpUrl().equalsForWebDAV("http://host/resource#frag1".toHttpUrl())) 60 | 61 | assertFalse("http://host/resource".toHttpUrl().equalsForWebDAV("http://host/resource/".toHttpUrl())) 62 | assertFalse("http://host/resource".toHttpUrl().equalsForWebDAV("http://host:81/resource".toHttpUrl())) 63 | 64 | assertTrue("https://www.example.com/folder/[X]Y!.txt".toHttpUrl().equalsForWebDAV("https://www.example.com/folder/[X]Y!.txt".toHttpUrl())) 65 | assertTrue("https://www.example.com/folder/%5BX%5DY!.txt".toHttpUrl().equalsForWebDAV("https://www.example.com/folder/[X]Y!.txt".toHttpUrl())) 66 | assertTrue("https://www.example.com/folder/%5bX%5dY%21.txt".toHttpUrl().equalsForWebDAV("https://www.example.com/folder/[X]Y!.txt".toHttpUrl())) 67 | } 68 | 69 | } -------------------------------------------------------------------------------- /src/test/kotlin/at/bitfire/dav4jvm/ktor/UrlUtilsTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.ktor 12 | 13 | import io.ktor.http.Url 14 | import org.junit.Assert.assertEquals 15 | import org.junit.Assert.assertFalse 16 | import org.junit.Assert.assertNull 17 | import org.junit.Assert.assertTrue 18 | import org.junit.Test 19 | 20 | class UrlUtilsTest { 21 | 22 | @Test 23 | fun testHostToDomain() { 24 | assertNull(UrlUtils.hostToDomain(null)) 25 | assertEquals("", UrlUtils.hostToDomain(".")) 26 | assertEquals("com", UrlUtils.hostToDomain("com")) 27 | assertEquals("com", UrlUtils.hostToDomain("com.")) 28 | assertEquals("example.com", UrlUtils.hostToDomain("example.com")) 29 | assertEquals("example.com", UrlUtils.hostToDomain("example.com.")) 30 | assertEquals("example.com", UrlUtils.hostToDomain(".example.com")) 31 | assertEquals("example.com", UrlUtils.hostToDomain(".example.com.")) 32 | assertEquals("example.com", UrlUtils.hostToDomain("host.example.com")) 33 | assertEquals("example.com", UrlUtils.hostToDomain("host.example.com.")) 34 | assertEquals("example.com", UrlUtils.hostToDomain("sub.host.example.com")) 35 | assertEquals("example.com", UrlUtils.hostToDomain("sub.host.example.com.")) 36 | } 37 | 38 | @Test 39 | fun testOmitTrailingSlash() { 40 | assertEquals(Url("http://host/resource"), UrlUtils.omitTrailingSlash(Url("http://host/resource"))) 41 | assertEquals(Url("http://host/resource"), UrlUtils.omitTrailingSlash(Url("http://host/resource/"))) 42 | assertEquals(Url("http://host"), UrlUtils.omitTrailingSlash(Url("http://host"))) 43 | assertEquals(Url("http://host"), UrlUtils.omitTrailingSlash(Url("http://host/"))) 44 | 45 | } 46 | 47 | @Test 48 | fun testWithTrailingSlash() { 49 | assertEquals(Url("http://host/resource/"), UrlUtils.withTrailingSlash(Url("http://host/resource"))) 50 | assertEquals(Url("http://host/resource/"), UrlUtils.withTrailingSlash(Url("http://host/resource/"))) 51 | assertEquals(Url("http://host/"), UrlUtils.withTrailingSlash(Url("http://host"))) 52 | assertEquals(Url("http://host/"), UrlUtils.withTrailingSlash(Url("http://host/"))) 53 | } 54 | 55 | 56 | @Test 57 | fun testHttpUrl_EqualsForWebDAV() { 58 | assertTrue(Url("http://host/resource").equalsForWebDAV(Url("http://host/resource"))) 59 | assertTrue(Url("http://host:80/resource").equalsForWebDAV(Url("http://host/resource"))) 60 | assertTrue(Url("https://HOST:443/resource").equalsForWebDAV(Url("https://host/resource"))) 61 | assertTrue(Url("https://host:443/my@dav/").equalsForWebDAV(Url("https://host/my%40dav/"))) 62 | assertTrue(Url("http://host/resource").equalsForWebDAV(Url("http://host/resource#frag1"))) 63 | 64 | assertFalse(Url("http://host/resource").equalsForWebDAV(Url("http://host/resource/"))) 65 | assertFalse(Url("http://host/resource").equalsForWebDAV(Url("http://host:81/resource"))) 66 | 67 | assertTrue(Url("https://www.example.com/folder/[X]Y!.txt").equalsForWebDAV(Url("https://www.example.com/folder/[X]Y!.txt"))) 68 | assertTrue(Url("https://www.example.com/folder/%5BX%5DY!.txt").equalsForWebDAV(Url("https://www.example.com/folder/[X]Y!.txt"))) 69 | assertTrue(Url("https://www.example.com/folder/%5bX%5dY%21.txt").equalsForWebDAV(Url("https://www.example.com/folder/[X]Y!.txt"))) 70 | } 71 | 72 | } -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/webdav/WebDAV.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * SPDX-License-Identifier: MPL-2.0 9 | */ 10 | 11 | package at.bitfire.dav4jvm.property.webdav 12 | 13 | import at.bitfire.dav4jvm.Property 14 | 15 | /** 16 | * WebDAV XML namespace and elements (as defined in RFC 4918) 17 | */ 18 | object WebDAV { 19 | 20 | const val NS_WEBDAV = "DAV:" 21 | 22 | 23 | // WebDAV XML elements/properties (see Section 14 and 15 of RFC 4918) 24 | 25 | val Collection = Property.Name(NS_WEBDAV, "collection") 26 | val CreationDate = Property.Name(NS_WEBDAV, "creationdate") 27 | val Depth = Property.Name(NS_WEBDAV, "depth") 28 | val DisplayName = Property.Name(NS_WEBDAV, "displayname") 29 | val Error = Property.Name(NS_WEBDAV, "error") 30 | val GetContentLength = Property.Name(NS_WEBDAV, "getcontentlength") 31 | val GetContentType = Property.Name(NS_WEBDAV, "getcontenttype") 32 | val GetETag = Property.Name(NS_WEBDAV, "getetag") 33 | val GetLastModified = Property.Name(NS_WEBDAV, "getlastmodified") 34 | val Href = Property.Name(NS_WEBDAV, "href") 35 | val Location = Property.Name(NS_WEBDAV, "location") 36 | val MultiStatus = Property.Name(NS_WEBDAV, "multistatus") 37 | val Owner = Property.Name(NS_WEBDAV, "owner") 38 | val Principal = Property.Name(NS_WEBDAV, "principal") 39 | val Prop = Property.Name(NS_WEBDAV, "prop") 40 | val PropertyUpdate = Property.Name(NS_WEBDAV, "propertyupdate") 41 | val PropFind = Property.Name(NS_WEBDAV, "propfind") 42 | val PropStat = Property.Name(NS_WEBDAV, "propstat") 43 | val Remove = Property.Name(NS_WEBDAV, "remove") 44 | val ResourceType = Property.Name(NS_WEBDAV, "resourcetype") 45 | val Response = Property.Name(NS_WEBDAV, "response") 46 | val Set = Property.Name(NS_WEBDAV, "set") 47 | val Status = Property.Name(NS_WEBDAV, "status") 48 | 49 | 50 | // Versioning Extensions to WebDAV (RFC 3253) 51 | 52 | val Report = Property.Name(NS_WEBDAV, "report") 53 | val SupportedReportSet = Property.Name(NS_WEBDAV, "supported-report-set") 54 | val SupportedReport = Property.Name(NS_WEBDAV, "supported-report") 55 | 56 | 57 | // WebDAV ACL (RFC 3744) 58 | 59 | val All = Property.Name(NS_WEBDAV, "all") 60 | val Bind = Property.Name(NS_WEBDAV, "bind") 61 | val CurrentUserPrivilegeSet = Property.Name(NS_WEBDAV, "current-user-privilege-set") 62 | val GroupMembership = Property.Name(NS_WEBDAV, "group-membership") 63 | val NeedPrivileges = Property.Name(NS_WEBDAV, "need-privileges") 64 | val Privilege = Property.Name(NS_WEBDAV, "privilege") 65 | val Read = Property.Name(NS_WEBDAV, "read") 66 | val Unbind = Property.Name(NS_WEBDAV, "unbind") 67 | val Write = Property.Name(NS_WEBDAV, "write") 68 | val WriteContent = Property.Name(NS_WEBDAV, "write-content") 69 | val WriteProperties = Property.Name(NS_WEBDAV, "write-properties") 70 | 71 | 72 | // Quota and Size Properties for WebDAV (RFC 4331) 73 | 74 | val QuotaAvailableBytes = Property.Name(NS_WEBDAV, "quota-available-bytes") 75 | val QuotaUsedBytes = Property.Name(NS_WEBDAV, "quota-used-bytes") 76 | 77 | 78 | // WebDAV Current Principal Extension (RFC 5397) 79 | 80 | val CurrentUserPrincipal = Property.Name(NS_WEBDAV, "current-user-principal") 81 | 82 | 83 | // Using POST to Add Members (RFC 5995) 84 | 85 | val AddMember = Property.Name(NS_WEBDAV, "add-member") 86 | 87 | 88 | // Collection Synchronization (RFC 6578) 89 | 90 | val Limit = Property.Name(NS_WEBDAV, "limit") 91 | val NResults = Property.Name(NS_WEBDAV, "nresults") 92 | val SyncCollection = Property.Name(NS_WEBDAV, "sync-collection") 93 | val SyncLevel = Property.Name(NS_WEBDAV, "sync-level") 94 | val SyncToken = Property.Name(NS_WEBDAV, "sync-token") 95 | val ValidSyncToken = Property.Name(NS_WEBDAV, "valid-sync-token") 96 | 97 | } --------------------------------------------------------------------------------