├── .github ├── FUNDING.yml └── workflows │ ├── build-kdoc.yml │ └── test.yml ├── .gitignore ├── .idea ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml └── copyright │ ├── MPL.xml │ └── profiles_settings.xml ├── AUTHORS ├── CHANGELOG.md ├── LICENSE ├── README.md ├── SECURITY.md ├── build.gradle.kts ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── src ├── main └── kotlin │ └── at │ └── bitfire │ └── dav4jvm │ ├── BasicDigestAuthHandler.kt │ ├── CallbackInterfaces.kt │ ├── DavAddressBook.kt │ ├── DavCalendar.kt │ ├── DavCollection.kt │ ├── DavResource.kt │ ├── Error.kt │ ├── HttpUtils.kt │ ├── PropStat.kt │ ├── Property.kt │ ├── PropertyFactory.kt │ ├── PropertyRegistry.kt │ ├── QuotedStringUtils.kt │ ├── Response.kt │ ├── UrlUtils.kt │ ├── XmlReader.kt │ ├── XmlUtils.kt │ ├── exception │ ├── ConflictException.kt │ ├── DavException.kt │ ├── ForbiddenException.kt │ ├── GoneException.kt │ ├── HttpException.kt │ ├── InvalidPropertyException.kt │ ├── NotFoundException.kt │ ├── PreconditionFailedException.kt │ ├── ServiceUnavailableException.kt │ └── UnauthorizedException.kt │ └── property │ ├── caldav │ ├── CalendarColor.kt │ ├── CalendarData.kt │ ├── CalendarDescription.kt │ ├── CalendarHomeSet.kt │ ├── CalendarProxyReadFor.kt │ ├── CalendarProxyWriteFor.kt │ ├── CalendarTimezone.kt │ ├── CalendarTimezoneId.kt │ ├── CalendarUserAddressSet.kt │ ├── GetCTag.kt │ ├── MaxResourceSize.kt │ ├── ScheduleTag.kt │ ├── Source.kt │ ├── SupportedCalendarComponentSet.kt │ ├── SupportedCalendarData.kt │ └── namespace.kt │ ├── carddav │ ├── AddressData.kt │ ├── AddressbookDescription.kt │ ├── AddressbookHomeSet.kt │ ├── MaxResourceSize.kt │ ├── SupportedAddressData.kt │ └── namespace.kt │ ├── push │ ├── AuthSecret.kt │ ├── ContentUpdate.kt │ ├── PropertyUpdate.kt │ ├── PushMessage.kt │ ├── PushRegister.kt │ ├── PushResource.kt │ ├── PushTransport.kt │ ├── PushTransports.kt │ ├── Subscription.kt │ ├── SubscriptionPublicKey.kt │ ├── SupportedTriggers.kt │ ├── Topic.kt │ ├── Trigger.kt │ ├── VapidPublicKey.kt │ ├── WebPush.kt │ ├── WebPushSubscription.kt │ └── namespace.kt │ └── webdav │ ├── AddMember.kt │ ├── CreationDate.kt │ ├── CurrentUserPrincipal.kt │ ├── CurrentUserPrivilegeSet.kt │ ├── Depth.kt │ ├── DisplayName.kt │ ├── GetContentLength.kt │ ├── GetContentType.kt │ ├── GetETag.kt │ ├── GetLastModified.kt │ ├── GroupMembership.kt │ ├── HrefListProperty.kt │ ├── Owner.kt │ ├── QuotaAvailableBytes.kt │ ├── QuotaUsedBytes.kt │ ├── ResourceType.kt │ ├── SupportedReportSet.kt │ ├── SyncLevel.kt │ ├── SyncToken.kt │ └── namespace.kt └── test └── kotlin └── at └── bitfire └── dav4jvm ├── BasicDigestAuthHandlerTest.kt ├── DavCalendarTest.kt ├── DavCollectionTest.kt ├── DavResourceTest.kt ├── ErrorTest.kt ├── HttpUtilsTest.kt ├── PropertyTest.kt ├── QuotedStringUtilsTest.kt ├── UrlUtilsTest.kt ├── XmlReaderTest.kt ├── exception ├── DavExceptionTest.kt ├── HttpExceptionTest.kt └── ServiceUnavailableExceptionTest.kt └── property ├── CalendarDescriptionTest.kt ├── GetETagTest.kt ├── OwnerTest.kt ├── PropertyTest.kt └── push └── WebPushTest.kt /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | 2 | github: bitfireAT 3 | custom: https://www.davx5.com/donate 4 | -------------------------------------------------------------------------------- /.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@v4 18 | - uses: actions/setup-java@v4 19 | with: 20 | distribution: temurin 21 | java-version: 17 22 | - uses: gradle/actions/setup-gradle@v3 23 | 24 | - name: Build KDoc 25 | run: ./gradlew --no-daemon dokkaHtml 26 | 27 | - uses: actions/upload-pages-artifact@v3 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 | -------------------------------------------------------------------------------- /.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@v4 9 | - uses: actions/setup-java@v4 10 | with: 11 | distribution: temurin 12 | java-version: 17 13 | - uses: gradle/actions/setup-gradle@v3 14 | 15 | - name: Check 16 | run: ./gradlew --no-daemon check 17 | 18 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/copyright/MPL.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 9 | 10 | 13 | 14 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | [![License](https://img.shields.io/github/license/bitfireAT/dav4jvm)](https://github.com/bitfireAT/dav4jvm/blob/main/LICENSE) 3 | [![Tests](https://github.com/bitfireAT/dav4jvm/actions/workflows/test.yml/badge.svg)](https://github.com/bitfireAT/dav4jvm/actions/workflows/test.yml) 4 | [![JitPack](https://img.shields.io/jitpack/v/github/bitfireAT/dav4jvm)](https://jitpack.io/#bitfireAT/dav4jvm) 5 | [![KDoc](https://img.shields.io/badge/documentation-KDoc-informational)](https://bitfireat.github.io/dav4jvm/) 6 | 7 | 8 | # dav4jvm 9 | 10 | dav4jvm is a WebDAV/CalDAV/CardDAV library for JVM (Java/Kotlin). It has 11 | been developed for [DAVx⁵](https://www.davx5.com) initially. 12 | 13 | Repository: https://github.com/bitfireAT/dav4jvm/ 14 | 15 | Generated KDoc: https://bitfireat.github.io/dav4jvm/ 16 | 17 | For questions, suggestions etc. use [Github discussions](https://github.com/bitfireAT/dav4jvm/discussions). 18 | We're happy about contributions, but please let us know in the discussions before. Then make the changes 19 | in your own repository and send a pull request. 20 | 21 | 22 | ## Installation 23 | 24 | You can use jitpack.io to include dav4jvm: 25 | 26 | allprojects { 27 | repositories { 28 | maven { url 'https://jitpack.io' } 29 | } 30 | } 31 | dependencies { 32 | implementation 'com.github.bitfireAT:dav4jvm:' // usually the latest commit ID from main branch 33 | //implementation 'com.github.bitfireAT:dav4jvm:main-SNAPSHOT' // use it only for testing because it doesn't generate reproducible builds 34 | } 35 | 36 | dav4jvm needs a working XmlPullParser (XPP). On Android, the system already comes with 37 | XPP and you don't need to include one; on other systems, you may need to 38 | import for instance `org.ogce:xpp3` to get dav4jvm to work. 39 | 40 | 41 | ## Usage 42 | 43 | First, you'll need to set up an OkHttp instance. Use `BasicDigestAuthHandler` to configure the credentials: 44 | 45 | val authHandler = BasicDigestAuthHandler( 46 | domain = null, // Optional, to only authenticate against hosts with this domain. 47 | username = "user1", 48 | password = "pass1" 49 | ) 50 | val okHttpClient = OkHttpClient.Builder() 51 | .followRedirects(false) 52 | .authenticator(authHandler) 53 | .addNetworkInterceptor(authHandler) 54 | .build() 55 | 56 | 57 | ### Files 58 | 59 | Here's an example to create and download a file: 60 | 61 | val location = "https://example.com/webdav/hello.txt".toHttpUrl() 62 | val davCollection = DavCollection(account.requireClient(), location) 63 | 64 | // Create a text file 65 | davCollection.put("World".toRequestBody(contentType = "text/plain".toMediaType()) { response -> 66 | // Upload successful! 67 | } 68 | 69 | // Download a text file 70 | davCollection.get(accept = "", headers = null) { response -> 71 | response.body?.string() 72 | // Download successful! 73 | } 74 | 75 | To list a folder's contents, you need to pass in which properties to fetch: 76 | 77 | val location = "https://example.com/webdav/".toHttpUrl() 78 | val davCollection = DavCollection(account.requireClient(), location) 79 | 80 | davCollection.propfind(depth = 1, DisplayName.NAME, GetLastModified.NAME) { response, relation -> 81 | // This callback will be called for every file in the folder. 82 | // Use `response.properties` to access the successfully retrieved properties. 83 | } 84 | 85 | ## Custom properties 86 | 87 | If you use custom WebDAV properties, register the corresponding factories with `PropertyRegistry.register()` 88 | before calling other dav4jvm methods. 89 | 90 | 91 | # Useful forks 92 | 93 | For specific use-cases, we have a list of forks that cover them: 94 | - Kotlin Multiplatform (maintained by [McDjuady](https://github.com/McDjuady)): https://github.com/McDjuady/dav4jvm 95 | 96 | 97 | ## Contact / License 98 | 99 | dav4jvm is licensed under [Mozilla Public License, v. 2.0](LICENSE). 100 | 101 | 102 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | import java.net.URL 2 | import org.jetbrains.dokka.gradle.DokkaTask 3 | 4 | repositories { 5 | mavenCentral() 6 | } 7 | 8 | group="com.github.bitfireAT" 9 | version=System.getenv("GIT_COMMIT") // set by jitpack.io 10 | 11 | plugins { 12 | alias(libs.plugins.kotlin.jvm) 13 | `maven-publish` 14 | 15 | alias(libs.plugins.dokka) 16 | } 17 | 18 | publishing { 19 | publications { 20 | create("maven") { 21 | from(components["java"]) 22 | } 23 | } 24 | 25 | repositories { 26 | maven { 27 | name = "dav4jvm" 28 | url = uri(layout.buildDirectory.dir("repo")) 29 | } 30 | } 31 | } 32 | 33 | tasks.withType().configureEach { 34 | dokkaSourceSets { 35 | named("main") { 36 | moduleName.set("dav4jvm") 37 | sourceLink { 38 | localDirectory.set(file("src/main/kotlin")) 39 | remoteUrl.set(URL("https://github.com/bitfireAT/dav4jvm/tree/main/src/main/kotlin/")) 40 | remoteLineSuffix.set("#L") 41 | } 42 | } 43 | } 44 | } 45 | 46 | dependencies { 47 | api(libs.okhttp) 48 | api(libs.xpp3) 49 | 50 | testImplementation(libs.junit4) 51 | testImplementation(libs.okhttp.mockwebserver) 52 | } -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | dokka = "1.9.20" 3 | junit4 = "4.13.2" 4 | kotlin = "2.1.20" 5 | okhttpVersion = "4.12.0" 6 | xpp3Version = "1.1.6" 7 | 8 | [libraries] 9 | junit4 = { module = "junit:junit", version.ref = "junit4" } 10 | okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttpVersion" } 11 | okhttp-mockwebserver = { module = "com.squareup.okhttp3:mockwebserver", version.ref = "okhttpVersion" } 12 | xpp3 = { module = "org.ogce:xpp3", version.ref = "xpp3Version" } 13 | 14 | [plugins] 15 | dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" } 16 | kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } 17 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfireAT/dav4jvm/8691192e28295d4a8438210573f2c58cb6aa00b2/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MSYS* | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /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/main/kotlin/at/bitfire/dav4jvm/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 12 | 13 | /** 14 | * Callback for the OPTIONS request. 15 | */ 16 | fun interface CapabilitiesCallback { 17 | fun onCapabilities(davCapabilities: Set, response: okhttp3.Response) 18 | } 19 | 20 | /** 21 | * Callback for 207 Multi-Status responses. 22 | */ 23 | fun interface MultiResponseCallback { 24 | /** 25 | * Called for every `` element in the `` body. For instance, 26 | * in response to a `PROPFIND` request, this callback will be called once for every found 27 | * member resource. 28 | * 29 | * Known collections have [response] `href` with trailing slash, see [at.bitfire.dav4jvm.Response.parse] for details. 30 | * 31 | * @param response the parsed response (including URL) 32 | * @param relation relation of the response to the called resource 33 | */ 34 | fun onResponse(response: Response, relation: Response.HrefRelation) 35 | } 36 | 37 | /** 38 | * Callback for HTTP responses. 39 | */ 40 | fun interface ResponseCallback { 41 | /** 42 | * Called for a HTTP response. Typically this is only called for successful/redirect 43 | * responses because HTTP errors throw an exception before this callback is called. 44 | */ 45 | fun onResponse(response: okhttp3.Response) 46 | } 47 | -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/DavAddressBook.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.XmlUtils.insertTag 14 | import at.bitfire.dav4jvm.exception.DavException 15 | import at.bitfire.dav4jvm.exception.HttpException 16 | import at.bitfire.dav4jvm.property.carddav.AddressData 17 | import at.bitfire.dav4jvm.property.carddav.NS_CARDDAV 18 | import at.bitfire.dav4jvm.property.webdav.GetContentType 19 | import at.bitfire.dav4jvm.property.webdav.GetETag 20 | import at.bitfire.dav4jvm.property.webdav.NS_WEBDAV 21 | import okhttp3.HttpUrl 22 | import okhttp3.MediaType.Companion.toMediaType 23 | import okhttp3.OkHttpClient 24 | import okhttp3.Request 25 | import okhttp3.RequestBody.Companion.toRequestBody 26 | import java.io.IOException 27 | import java.io.StringWriter 28 | import java.util.logging.Logger 29 | 30 | @Suppress("unused") 31 | class DavAddressBook @JvmOverloads constructor( 32 | httpClient: OkHttpClient, 33 | location: HttpUrl, 34 | logger: Logger = Logger.getLogger(DavAddressBook::javaClass.name) 35 | ): DavCollection(httpClient, location, logger) { 36 | 37 | companion object { 38 | val MIME_JCARD = "application/vcard+json".toMediaType() 39 | val MIME_VCARD3_UTF8 = "text/vcard;charset=utf-8".toMediaType() 40 | val MIME_VCARD4 = "text/vcard;version=4.0".toMediaType() 41 | 42 | val ADDRESSBOOK_QUERY = Property.Name(NS_CARDDAV, "addressbook-query") 43 | val ADDRESSBOOK_MULTIGET = Property.Name(NS_CARDDAV, "addressbook-multiget") 44 | val FILTER = Property.Name(NS_CARDDAV, "filter") 45 | } 46 | 47 | /** 48 | * Sends an addressbook-query REPORT request to the resource. 49 | * 50 | * @param callback called for every WebDAV response XML element in the result 51 | * 52 | * @return list of properties which have been received in the Multi-Status response, but 53 | * are not part of response XML elements 54 | * 55 | * @throws IOException on I/O error 56 | * @throws HttpException on HTTP error 57 | * @throws DavException on WebDAV error 58 | */ 59 | fun addressbookQuery(callback: MultiResponseCallback): List { 60 | /* 63 | 64 | */ 65 | val serializer = XmlUtils.newSerializer() 66 | val writer = StringWriter() 67 | serializer.setOutput(writer) 68 | serializer.startDocument("UTF-8", null) 69 | serializer.setPrefix("", NS_WEBDAV) 70 | serializer.setPrefix("CARD", NS_CARDDAV) 71 | serializer.insertTag(ADDRESSBOOK_QUERY) { 72 | insertTag(PROP) { 73 | insertTag(GetETag.NAME) 74 | } 75 | insertTag(FILTER) 76 | } 77 | serializer.endDocument() 78 | 79 | followRedirects { 80 | httpClient.newCall(Request.Builder() 81 | .url(location) 82 | .method("REPORT", writer.toString().toRequestBody(MIME_XML)) 83 | .header("Depth", "1") 84 | .build()).execute() 85 | }.use { response -> 86 | return processMultiStatus(response, callback) 87 | } 88 | } 89 | 90 | /** 91 | * Sends an addressbook-multiget REPORT request to the resource. 92 | * 93 | * @param urls list of vCard URLs to be requested 94 | * @param contentType MIME type of requested format; may be "text/vcard" for vCard or 95 | * "application/vcard+json" for jCard. *null*: don't request specific representation type 96 | * @param version vCard version subtype of the requested format. Should only be specified together with a [contentType] of "text/vcard". 97 | * Currently only useful value: "4.0" for vCard 4. *null*: don't request specific version 98 | * @param callback called for every WebDAV response XML element in the result 99 | * 100 | * @return list of properties which have been received in the Multi-Status response, but 101 | * are not part of response XML elements 102 | * 103 | * @throws IOException on I/O error 104 | * @throws HttpException on HTTP error 105 | * @throws DavException on WebDAV error 106 | */ 107 | fun multiget(urls: List, contentType: String? = null, version: String? = null, callback: MultiResponseCallback): List { 108 | /* 112 | */ 113 | val serializer = XmlUtils.newSerializer() 114 | val writer = StringWriter() 115 | serializer.setOutput(writer) 116 | serializer.startDocument("UTF-8", null) 117 | serializer.setPrefix("", NS_WEBDAV) 118 | serializer.setPrefix("CARD", NS_CARDDAV) 119 | serializer.insertTag(ADDRESSBOOK_MULTIGET) { 120 | insertTag(PROP) { 121 | insertTag(GetContentType.NAME) 122 | insertTag(GetETag.NAME) 123 | insertTag(AddressData.NAME) { 124 | if (contentType != null) 125 | attribute(null, AddressData.CONTENT_TYPE, contentType) 126 | if (version != null) 127 | attribute(null, AddressData.VERSION, version) 128 | } 129 | } 130 | for (url in urls) 131 | insertTag(HREF) { 132 | text(url.encodedPath) 133 | } 134 | } 135 | serializer.endDocument() 136 | 137 | followRedirects { 138 | httpClient.newCall(Request.Builder() 139 | .url(location) 140 | .method("REPORT", writer.toString().toRequestBody(MIME_XML)) 141 | .header("Depth", "0") // "The request MUST include a Depth: 0 header [...]" 142 | .build()).execute() 143 | }.use { 144 | return processMultiStatus(it, callback) 145 | } 146 | } 147 | 148 | } 149 | -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/DavCollection.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.XmlUtils.insertTag 14 | import at.bitfire.dav4jvm.exception.DavException 15 | import at.bitfire.dav4jvm.exception.HttpException 16 | import at.bitfire.dav4jvm.property.webdav.NS_WEBDAV 17 | import at.bitfire.dav4jvm.property.webdav.SyncToken 18 | import okhttp3.HttpUrl 19 | import okhttp3.OkHttpClient 20 | import okhttp3.Request 21 | import okhttp3.RequestBody.Companion.toRequestBody 22 | import java.io.StringWriter 23 | import java.util.logging.Logger 24 | 25 | /** 26 | * Represents a WebDAV collection. 27 | */ 28 | open class DavCollection @JvmOverloads constructor( 29 | httpClient: OkHttpClient, 30 | location: HttpUrl, 31 | logger: Logger = Logger.getLogger(DavCollection::class.java.name) 32 | ): DavResource(httpClient, location, logger) { 33 | 34 | companion object { 35 | val SYNC_COLLECTION = Property.Name(NS_WEBDAV, "sync-collection") 36 | val SYNC_LEVEL = Property.Name(NS_WEBDAV, "sync-level") 37 | val LIMIT = Property.Name(NS_WEBDAV, "limit") 38 | val NRESULTS = Property.Name(NS_WEBDAV, "nresults") 39 | } 40 | 41 | /** 42 | * Sends a REPORT sync-collection request. 43 | * 44 | * @param syncToken sync-token to be sent with the request 45 | * @param infiniteDepth sync-level to be sent with the request: false = "1", true = "infinite" 46 | * @param limit maximum number of results (may cause truncation) 47 | * @param properties WebDAV properties to be requested 48 | * @param callback called for every WebDAV response XML element in the result 49 | * 50 | * @return list of properties which have been received in the Multi-Status response, but 51 | * are not part of response XML elements (like `sync-token` which is returned as [SyncToken]) 52 | * 53 | * @throws java.io.IOException on I/O error 54 | * @throws HttpException on HTTP error 55 | * @throws DavException on WebDAV error 56 | */ 57 | fun reportChanges(syncToken: String?, infiniteDepth: Boolean, limit: Int?, vararg properties: Property.Name, callback: MultiResponseCallback): List { 58 | /* 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | */ 68 | val serializer = XmlUtils.newSerializer() 69 | val writer = StringWriter() 70 | serializer.setOutput(writer) 71 | serializer.startDocument("UTF-8", null) 72 | serializer.setPrefix("", NS_WEBDAV) 73 | serializer.insertTag(SYNC_COLLECTION) { 74 | insertTag(SyncToken.NAME) { 75 | if (syncToken != null) 76 | text(syncToken) 77 | } 78 | insertTag(SYNC_LEVEL) { 79 | text(if (infiniteDepth) "infinite" else "1") 80 | } 81 | if (limit != null) 82 | insertTag(LIMIT) { 83 | insertTag(NRESULTS) { 84 | text(limit.toString()) 85 | } 86 | } 87 | insertTag(PROP) { 88 | for (prop in properties) 89 | insertTag(prop) 90 | } 91 | } 92 | serializer.endDocument() 93 | 94 | followRedirects { 95 | httpClient.newCall(Request.Builder() 96 | .url(location) 97 | .method("REPORT", writer.toString().toRequestBody(MIME_XML)) 98 | .header("Depth", "0") 99 | .build()).execute() 100 | }.use { 101 | return processMultiStatus(it, callback) 102 | } 103 | } 104 | 105 | } -------------------------------------------------------------------------------- /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 at.bitfire.dav4jvm.property.webdav.NS_WEBDAV 14 | import org.xmlpull.v1.XmlPullParser 15 | import java.io.Serializable 16 | 17 | /** 18 | * Represents an XML precondition/postcondition error. Every error has a name, which is the XML element 19 | * name. Subclassed errors may have more specific information available. 20 | * 21 | * At the moment, there is no logic for subclassing errors. 22 | */ 23 | class Error( 24 | val name: Property.Name 25 | ): Serializable { 26 | 27 | companion object { 28 | 29 | val NAME = Property.Name(NS_WEBDAV, "error") 30 | 31 | fun parseError(parser: XmlPullParser): List { 32 | val names = mutableSetOf() 33 | 34 | val depth = parser.depth 35 | var eventType = parser.eventType 36 | while (!(eventType == XmlPullParser.END_TAG && parser.depth == depth)) { 37 | if (eventType == XmlPullParser.START_TAG && parser.depth == depth + 1) 38 | names += Property.Name(parser.namespace, parser.name) 39 | eventType = parser.next() 40 | } 41 | 42 | return names.map { Error(it) } 43 | } 44 | 45 | 46 | // some pre-defined errors 47 | 48 | val NEED_PRIVILEGES = Error(Property.Name(NS_WEBDAV, "need-privileges")) 49 | val VALID_SYNC_TOKEN = Error(Property.Name(NS_WEBDAV, "valid-sync-token")) 50 | 51 | } 52 | 53 | override fun equals(other: Any?) = 54 | (other is Error) && other.name == name 55 | 56 | override fun hashCode() = name.hashCode() 57 | 58 | } 59 | -------------------------------------------------------------------------------- /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 okhttp3.HttpUrl 15 | import okhttp3.Response 16 | import java.time.Instant 17 | import java.time.LocalDateTime 18 | import java.time.ZoneOffset 19 | import java.time.ZonedDateTime 20 | import java.time.format.DateTimeFormatter 21 | import java.time.format.DateTimeParseException 22 | import java.util.Locale 23 | import java.util.logging.Logger 24 | 25 | object HttpUtils { 26 | 27 | /** 28 | * Preferred HTTP date/time format, see RFC 7231 7.1.1.1 IMF-fixdate 29 | */ 30 | private const val httpDateFormatStr = "EEE, dd MMM yyyy HH:mm:ss ZZZZ" 31 | private val httpDateFormat = DateTimeFormatter.ofPattern(httpDateFormatStr, Locale.US) 32 | 33 | private val logger 34 | get() = Logger.getLogger(javaClass.name) 35 | 36 | 37 | /** 38 | * Gets the resource name (the last segment of the path) from an URL. 39 | * Empty if the resource is the base directory. 40 | * 41 | * * `dir` for `https://example.com/dir/` 42 | * * `file` for `https://example.com/file` 43 | * * `` for `https://example.com` or `https://example.com/` 44 | * 45 | * @return resource name 46 | */ 47 | fun fileName(url: HttpUrl): String { 48 | val pathSegments = url.pathSegments.dropLastWhile { it == "" } 49 | return pathSegments.lastOrNull() ?: "" 50 | } 51 | 52 | fun listHeader(response: Response, name: String): Array { 53 | val value = response.headers(name).joinToString(",") 54 | return value.split(',').filter { it.isNotEmpty() }.toTypedArray() 55 | } 56 | 57 | 58 | /** 59 | * Formats a date for use in HTTP headers using [httpDateFormat]. 60 | * 61 | * @param date date to be formatted 62 | * 63 | * @return date in HTTP-date format 64 | */ 65 | fun formatDate(date: Instant): String = 66 | ZonedDateTime.ofInstant(date, ZoneOffset.UTC).format(httpDateFormat) 67 | 68 | /** 69 | * Parses a HTTP-date according to RFC 7231 section 7.1.1.1. 70 | * 71 | * @param dateStr date-time formatted in one of the three accepted formats: 72 | * 73 | * - preferred format (`IMF-fixdate`) 74 | * - obsolete RFC 850 format 75 | * - ANSI C's `asctime()` format 76 | * 77 | * @return date-time, or null if date could not be parsed 78 | */ 79 | fun parseDate(dateStr: String): Instant? { 80 | val zonedFormats = arrayOf( 81 | // preferred format 82 | httpDateFormat, 83 | 84 | // obsolete RFC 850 format 85 | DateTimeFormatter.ofPattern("EEEE, dd-MMM-yy HH:mm:ss zzz", Locale.US), 86 | ) 87 | 88 | // try the two formats with zone info 89 | for (format in zonedFormats) 90 | try { 91 | return ZonedDateTime.parse(dateStr, format).toInstant() 92 | } catch (ignored: DateTimeParseException) { 93 | } 94 | 95 | // try ANSI C's asctime() format 96 | try { 97 | val formatC = DateTimeFormatter.ofPattern("EEE MMM ppd HH:mm:ss yyyy", Locale.US) 98 | val local = LocalDateTime.parse(dateStr, formatC) 99 | return local.atZone(ZoneOffset.UTC).toInstant() 100 | } catch (ignored: DateTimeParseException) { 101 | } 102 | 103 | // no success in parsing 104 | logger.warning("Couldn't parse HTTP date: $dateStr, ignoring") 105 | return null 106 | } 107 | 108 | } -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/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 12 | 13 | import at.bitfire.dav4jvm.Response.Companion.STATUS 14 | import at.bitfire.dav4jvm.XmlUtils.propertyName 15 | import at.bitfire.dav4jvm.property.webdav.NS_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 | @JvmField 36 | val NAME = Property.Name(NS_WEBDAV, "propstat") 37 | 38 | private val ASSUMING_OK = StatusLine(Protocol.HTTP_1_1, 200, "Assuming OK") 39 | private val INVALID_STATUS = StatusLine(Protocol.HTTP_1_1, 500, "Invalid status line") 40 | 41 | fun parse(parser: XmlPullParser): PropStat { 42 | val depth = parser.depth 43 | 44 | var status: StatusLine? = null 45 | val prop = LinkedList() 46 | 47 | var eventType = parser.eventType 48 | while (!(eventType == XmlPullParser.END_TAG && parser.depth == depth)) { 49 | if (eventType == XmlPullParser.START_TAG && parser.depth == depth + 1) 50 | when (parser.propertyName()) { 51 | DavResource.PROP -> 52 | prop.addAll(Property.parse(parser)) 53 | STATUS -> 54 | status = try { 55 | StatusLine.parse(parser.nextText()) 56 | } catch (e: ProtocolException) { 57 | // invalid status line, treat as 500 Internal Server Error 58 | INVALID_STATUS 59 | } 60 | } 61 | eventType = parser.next() 62 | } 63 | 64 | return PropStat(prop, status ?: ASSUMING_OK) 65 | } 66 | 67 | } 68 | 69 | 70 | fun isSuccess() = status.code/100 == 2 71 | 72 | } -------------------------------------------------------------------------------- /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 at.bitfire.dav4jvm.exception.InvalidPropertyException 14 | import org.xmlpull.v1.XmlPullParser 15 | import java.io.Serializable 16 | import java.util.LinkedList 17 | import java.util.logging.Level 18 | import java.util.logging.Logger 19 | 20 | /** 21 | * Represents a WebDAV property. 22 | * 23 | * Every [Property] must define a static field (use `@JvmStatic`) called `NAME` of type [Property.Name], 24 | * which will be accessed by reflection. 25 | * 26 | * Every [Property] should be a data class in order to be able to compare it against others, and convert to a useful 27 | * string for debugging. 28 | */ 29 | interface Property { 30 | 31 | data class Name( 32 | val namespace: String, 33 | val name: String 34 | ): Serializable { 35 | 36 | override fun toString() = "$namespace:$name" 37 | 38 | } 39 | 40 | companion object { 41 | 42 | fun parse(parser: XmlPullParser): List { 43 | val logger = Logger.getLogger(Property::javaClass.name) 44 | 45 | // 46 | val depth = parser.depth 47 | val properties = LinkedList() 48 | 49 | var eventType = parser.eventType 50 | while (!(eventType == XmlPullParser.END_TAG && parser.depth == depth)) { 51 | if (eventType == XmlPullParser.START_TAG && parser.depth == depth + 1) { 52 | val depthBeforeParsing = parser.depth 53 | val name = Name(parser.namespace, parser.name) 54 | 55 | try { 56 | val property = PropertyRegistry.create(name, parser) 57 | assert(parser.depth == depthBeforeParsing) 58 | 59 | if (property != null) { 60 | properties.add(property) 61 | } else 62 | logger.fine("Ignoring unknown property $name") 63 | } catch (e: InvalidPropertyException) { 64 | logger.log(Level.WARNING, "Ignoring invalid property", e) 65 | } 66 | } 67 | 68 | eventType = parser.next() 69 | } 70 | 71 | return properties 72 | } 73 | 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /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/PropertyRegistry.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.caldav.CalendarColor 14 | import at.bitfire.dav4jvm.property.caldav.CalendarData 15 | import at.bitfire.dav4jvm.property.caldav.CalendarDescription 16 | import at.bitfire.dav4jvm.property.caldav.CalendarHomeSet 17 | import at.bitfire.dav4jvm.property.caldav.CalendarProxyReadFor 18 | import at.bitfire.dav4jvm.property.caldav.CalendarProxyWriteFor 19 | import at.bitfire.dav4jvm.property.caldav.CalendarTimezone 20 | import at.bitfire.dav4jvm.property.caldav.CalendarTimezoneId 21 | import at.bitfire.dav4jvm.property.caldav.CalendarUserAddressSet 22 | import at.bitfire.dav4jvm.property.caldav.GetCTag 23 | import at.bitfire.dav4jvm.property.caldav.ScheduleTag 24 | import at.bitfire.dav4jvm.property.caldav.Source 25 | import at.bitfire.dav4jvm.property.caldav.SupportedCalendarComponentSet 26 | import at.bitfire.dav4jvm.property.caldav.SupportedCalendarData 27 | import at.bitfire.dav4jvm.property.carddav.AddressData 28 | import at.bitfire.dav4jvm.property.carddav.AddressbookDescription 29 | import at.bitfire.dav4jvm.property.carddav.AddressbookHomeSet 30 | import at.bitfire.dav4jvm.property.carddav.SupportedAddressData 31 | import at.bitfire.dav4jvm.property.push.PushMessage 32 | import at.bitfire.dav4jvm.property.push.PushRegister 33 | import at.bitfire.dav4jvm.property.push.PushTransports 34 | import at.bitfire.dav4jvm.property.push.Subscription 35 | import at.bitfire.dav4jvm.property.push.Topic 36 | import at.bitfire.dav4jvm.property.push.WebPushSubscription 37 | import at.bitfire.dav4jvm.property.webdav.AddMember 38 | import at.bitfire.dav4jvm.property.webdav.CreationDate 39 | import at.bitfire.dav4jvm.property.webdav.CurrentUserPrincipal 40 | import at.bitfire.dav4jvm.property.webdav.CurrentUserPrivilegeSet 41 | import at.bitfire.dav4jvm.property.webdav.DisplayName 42 | import at.bitfire.dav4jvm.property.webdav.GetContentLength 43 | import at.bitfire.dav4jvm.property.webdav.GetContentType 44 | import at.bitfire.dav4jvm.property.webdav.GetETag 45 | import at.bitfire.dav4jvm.property.webdav.GetLastModified 46 | import at.bitfire.dav4jvm.property.webdav.GroupMembership 47 | import at.bitfire.dav4jvm.property.webdav.Owner 48 | import at.bitfire.dav4jvm.property.webdav.QuotaAvailableBytes 49 | import at.bitfire.dav4jvm.property.webdav.QuotaUsedBytes 50 | import at.bitfire.dav4jvm.property.webdav.ResourceType 51 | import at.bitfire.dav4jvm.property.webdav.SupportedReportSet 52 | import at.bitfire.dav4jvm.property.webdav.SyncToken 53 | import org.xmlpull.v1.XmlPullParser 54 | import org.xmlpull.v1.XmlPullParserException 55 | import java.util.logging.Level 56 | import java.util.logging.Logger 57 | 58 | object PropertyRegistry { 59 | 60 | private val factories = mutableMapOf() 61 | private val logger 62 | get() = Logger.getLogger(javaClass.name) 63 | 64 | 65 | init { 66 | logger.info("Registering DAV property factories") 67 | registerDefaultFactories() 68 | } 69 | 70 | private fun registerDefaultFactories() { 71 | register(listOf( 72 | AddMember.Factory, 73 | AddressbookDescription.Factory, 74 | AddressbookHomeSet.Factory, 75 | AddressData.Factory, 76 | CalendarColor.Factory, 77 | CalendarData.Factory, 78 | CalendarDescription.Factory, 79 | CalendarHomeSet.Factory, 80 | CalendarProxyReadFor.Factory, 81 | CalendarProxyWriteFor.Factory, 82 | CalendarTimezone.Factory, 83 | CalendarTimezoneId.Factory, 84 | CalendarUserAddressSet.Factory, 85 | CreationDate.Factory, 86 | CurrentUserPrincipal.Factory, 87 | CurrentUserPrivilegeSet.Factory, 88 | DisplayName.Factory, 89 | GetContentLength.Factory, 90 | GetContentType.Factory, 91 | GetCTag.Factory, 92 | GetETag.Factory, 93 | GetLastModified.Factory, 94 | GroupMembership.Factory, 95 | at.bitfire.dav4jvm.property.caldav.MaxResourceSize.Factory, 96 | at.bitfire.dav4jvm.property.carddav.MaxResourceSize.Factory, 97 | Owner.Factory, 98 | PushMessage.Factory, 99 | PushRegister.Factory, 100 | PushTransports.Factory, 101 | QuotaAvailableBytes.Factory, 102 | QuotaUsedBytes.Factory, 103 | ResourceType.Factory, 104 | ScheduleTag.Factory, 105 | Source.Factory, 106 | Subscription.Factory, 107 | SupportedAddressData.Factory, 108 | SupportedCalendarComponentSet.Factory, 109 | SupportedCalendarData.Factory, 110 | SupportedReportSet.Factory, 111 | SyncToken.Factory, 112 | Topic.Factory, 113 | WebPushSubscription.Factory 114 | )) 115 | } 116 | 117 | 118 | /** 119 | * Registers a property factory, so that objects for all WebDAV properties which are handled 120 | * by this factory can be created. 121 | * 122 | * @param factory property factory to be registered 123 | */ 124 | fun register(factory: PropertyFactory) { 125 | logger.fine("Registering ${factory::class.java.name} for ${factory.getName()}") 126 | factories[factory.getName()] = factory 127 | } 128 | 129 | /** 130 | * Registers some property factories, so that objects for all WebDAV properties which are handled 131 | * by these factories can be created. 132 | 133 | * @param factories property factories to be registered 134 | */ 135 | fun register(factories: Iterable) { 136 | factories.forEach { 137 | register(it) 138 | } 139 | } 140 | 141 | fun create(name: Property.Name, parser: XmlPullParser) = 142 | try { 143 | factories[name]?.create(parser) 144 | } catch (e: XmlPullParserException) { 145 | logger.log(Level.WARNING, "Couldn't parse $name", e) 146 | null 147 | } 148 | 149 | } -------------------------------------------------------------------------------- /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/UrlUtils.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.UrlUtils.omitTrailingSlash 14 | import at.bitfire.dav4jvm.UrlUtils.withTrailingSlash 15 | import okhttp3.HttpUrl 16 | 17 | object UrlUtils { 18 | 19 | @Deprecated("Use equalsForWebDAV instead", ReplaceWith("url1.equalsForWebDAV(url2)")) 20 | fun equals(url1: HttpUrl, url2: HttpUrl) = url1.equalsForWebDAV(url2) 21 | 22 | /** 23 | * Gets the first-level domain name (without subdomains) from a host name. 24 | * Also removes trailing dots. 25 | * 26 | * @param host name (e.g. `www.example.com.`) 27 | * 28 | * @return domain name (e.g. `example.com`) 29 | */ 30 | fun hostToDomain(host: String?): String? { 31 | if (host == null) 32 | return null 33 | 34 | // remove optional dot at end 35 | val withoutTrailingDot = host.removeSuffix(".") 36 | 37 | // split into labels 38 | val labels = withoutTrailingDot.split('.') 39 | return if (labels.size >= 2) { 40 | labels[labels.size - 2] + "." + labels[labels.size - 1] 41 | } else 42 | withoutTrailingDot 43 | } 44 | 45 | /** 46 | * Ensures that a given URL doesn't have a trailing slash after member names. 47 | * If the path is the root path (`/`), the slash is preserved. 48 | * 49 | * @param url URL to process (e.g. 'http://host/test1/') 50 | * 51 | * @return URL without trailing slash (except when the path is the root path), e.g. `http://host/test1` 52 | */ 53 | fun omitTrailingSlash(url: HttpUrl): HttpUrl { 54 | val idxLast = url.pathSize - 1 55 | val hasTrailingSlash = url.pathSegments[idxLast] == "" 56 | 57 | return if (hasTrailingSlash) 58 | url.newBuilder().removePathSegment(idxLast).build() 59 | else 60 | url 61 | } 62 | 63 | /** 64 | * Ensures that a given URL has a trailing slash after member names. 65 | * 66 | * @param url URL to process (e.g. 'http://host/test1') 67 | * 68 | * @return URL with trailing slash, e.g. `http://host/test1/` 69 | */ 70 | fun withTrailingSlash(url: HttpUrl): HttpUrl { 71 | val idxLast = url.pathSize - 1 72 | val hasTrailingSlash = url.pathSegments[idxLast] == "" 73 | 74 | return if (hasTrailingSlash) 75 | url 76 | else 77 | url.newBuilder().addPathSegment("").build() 78 | } 79 | 80 | } 81 | 82 | /** 83 | * Compares two [HttpUrl]s in WebDAV context. If two URLs are considered *equal*, both 84 | * represent the same WebDAV resource. 85 | * 86 | * The fragment of an URL is ignored, e.g. `http://host:80/folder1` and `http://HOST/folder1#somefragment` 87 | * are considered to be equal. 88 | * 89 | * [HttpUrl] is less strict than [java.net.URI] and allows for instance (not encoded) square brackets in the path. 90 | * So this method tries to normalize the URI by converting it to a [java.net.URI] (encodes for instance square brackets) 91 | * and then comparing the scheme and scheme-specific part (without fragment). 92 | * 93 | * Attention: **This method does not deal with trailing slashes**, so if you want to compare collection URLs, 94 | * make sure they both (don't) have a trailing slash before calling this method, for instance 95 | * with [omitTrailingSlash] or [withTrailingSlash]. 96 | * 97 | * @param other the URL to compare the current object with 98 | * 99 | * @return whether the URLs are considered to represent the same WebDAV resource 100 | */ 101 | fun HttpUrl.equalsForWebDAV(other: HttpUrl): Boolean { 102 | // if okhttp thinks the two URLs are equal, they're in any case 103 | // (and it's a simple String comparison) 104 | if (this == other) 105 | return true 106 | 107 | // convert to java.net.URI (also corrects some mistakes and escapes for instance square brackets) 108 | val uri1 = toUri() 109 | val uri2 = other.toUri() 110 | 111 | // if the URIs are the same (ignoring scheme case and fragments), they're equal for us 112 | return uri1.scheme.equals(uri2.scheme, true) && uri1.schemeSpecificPart == uri2.schemeSpecificPart 113 | } -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/XmlReader.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.XmlUtils.propertyName 14 | import at.bitfire.dav4jvm.property.caldav.SupportedCalendarData.Companion.CONTENT_TYPE 15 | import at.bitfire.dav4jvm.property.caldav.SupportedCalendarData.Companion.VERSION 16 | import okhttp3.MediaType 17 | import okhttp3.MediaType.Companion.toMediaTypeOrNull 18 | import org.xmlpull.v1.XmlPullParser 19 | import org.xmlpull.v1.XmlPullParserException 20 | import java.io.IOException 21 | import java.time.Instant 22 | import java.util.logging.Level 23 | import java.util.logging.Logger 24 | 25 | /** 26 | * Reads/processes XML tags which are used for WebDAV. 27 | * 28 | * @param parser The parser to read from. 29 | */ 30 | class XmlReader( 31 | private val parser: XmlPullParser 32 | ) { 33 | 34 | // base processing 35 | 36 | /** 37 | * Reads child elements of the current element. Whenever a direct child with the given name is found, 38 | * [processor] is called for each one. 39 | */ 40 | @Throws(IOException::class, XmlPullParserException::class) 41 | fun processTag(name: Property.Name, processor: XmlReader.() -> Unit) { 42 | val depth = parser.depth 43 | var eventType = parser.eventType 44 | while (!((eventType == XmlPullParser.END_TAG || eventType == XmlPullParser.END_DOCUMENT) && parser.depth == depth)) { 45 | if (eventType == XmlPullParser.START_TAG && parser.depth == depth + 1 && parser.propertyName() == name) 46 | processor() 47 | eventType = parser.next() 48 | } 49 | } 50 | 51 | /** 52 | * Reads the inline text of the current element. 53 | * 54 | * For instance, if the parser is at the beginning of this XML: 55 | * 56 | * ``` 57 | * text 58 | * ``` 59 | * 60 | * this function will return "text". 61 | * 62 | * @return text or `null` if no text is found 63 | */ 64 | @Throws(IOException::class, XmlPullParserException::class) 65 | fun readText(): String? { 66 | var text: String? = null 67 | 68 | val depth = parser.depth 69 | var eventType = parser.eventType 70 | while (!(eventType == XmlPullParser.END_TAG && parser.depth == depth)) { 71 | if (eventType == XmlPullParser.TEXT && parser.depth == depth) 72 | text = parser.text 73 | eventType = parser.next() 74 | } 75 | 76 | return text 77 | } 78 | 79 | /** 80 | * Reads child elements of the current element. When a direct child with the given name is found, 81 | * its text is returned. 82 | * 83 | * @param name The name of the tag to read. 84 | * @return The text inside the tag, or `null` if the tag is not found. 85 | */ 86 | @Throws(IOException::class, XmlPullParserException::class) 87 | fun readTextProperty(name: Property.Name): String? { 88 | var result: String? = null 89 | 90 | val depth = parser.depth 91 | var eventType = parser.eventType 92 | while (!((eventType == XmlPullParser.END_TAG || eventType == XmlPullParser.END_DOCUMENT) && parser.depth == depth)) { 93 | if (eventType == XmlPullParser.START_TAG && parser.depth == depth + 1 && parser.propertyName() == name) 94 | result = parser.nextText() 95 | eventType = parser.next() 96 | } 97 | return result 98 | } 99 | 100 | /** 101 | * Reads child elements of the current element. Whenever a direct child with the given name is 102 | * found, its text is added to the given list. 103 | * 104 | * @param name The name of the tag to read. 105 | * @param list The list to add the text to. 106 | */ 107 | @Throws(IOException::class, XmlPullParserException::class) 108 | fun readTextPropertyList(name: Property.Name, list: MutableCollection) { 109 | val depth = parser.depth 110 | var eventType = parser.eventType 111 | while (!((eventType == XmlPullParser.END_TAG || eventType == XmlPullParser.END_DOCUMENT) && parser.depth == depth)) { 112 | if (eventType == XmlPullParser.START_TAG && parser.depth == depth + 1 && parser.propertyName() == name) 113 | list.add(parser.nextText()) 114 | eventType = parser.next() 115 | } 116 | } 117 | 118 | 119 | // extended processing (uses readText etc.) 120 | 121 | /** 122 | * Uses [readText] to read the tag's value (which is expected to be in _HTTP-date_ format), and converts 123 | * it into an [Instant] using [HttpUtils.parseDate]. 124 | * 125 | * If the conversion fails for any reason, null is returned, and a message is displayed in log. 126 | */ 127 | fun readHttpDate(): Instant? { 128 | return readText()?.let { rawDate -> 129 | val date = HttpUtils.parseDate(rawDate) 130 | if (date != null) 131 | date 132 | else { 133 | val logger = Logger.getLogger(javaClass.name) 134 | logger.warning("Couldn't parse HTTP-date") 135 | null 136 | } 137 | } 138 | } 139 | 140 | /** 141 | * Uses [readText] to read the tag's value (which is expected to be a number), and converts it 142 | * into a [Long] with [String.toLong]. 143 | * 144 | * If the conversion fails for any reason, null is returned, and a message is displayed in log. 145 | */ 146 | fun readLong(): Long? { 147 | return readText()?.let { valueStr -> 148 | try { 149 | valueStr.toLong() 150 | } catch(e: NumberFormatException) { 151 | val logger = Logger.getLogger(javaClass.name) 152 | logger.log(Level.WARNING, "Couldn't parse as Long: $valueStr", e) 153 | null 154 | } 155 | } 156 | } 157 | 158 | /** 159 | * Processes all the tags named [tagName], and sends every tag that has the [CONTENT_TYPE] 160 | * attribute with [onNewType]. 161 | * 162 | * @param tagName The name of the tag that contains the [CONTENT_TYPE] attribute value. 163 | * @param onNewType Called every time a new [MediaType] is found. 164 | */ 165 | fun readContentTypes(tagName: Property.Name, onNewType: (MediaType) -> Unit) { 166 | try { 167 | processTag(tagName) { 168 | parser.getAttributeValue(null, CONTENT_TYPE)?.let { contentType -> 169 | var type = contentType 170 | parser.getAttributeValue(null, VERSION)?.let { version -> type += "; version=$version" } 171 | type.toMediaTypeOrNull()?.let(onNewType) 172 | } 173 | } 174 | } catch(e: XmlPullParserException) { 175 | val logger = Logger.getLogger(javaClass.name) 176 | logger.log(Level.SEVERE, "Couldn't parse content types", e) 177 | } 178 | } 179 | 180 | } -------------------------------------------------------------------------------- /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 at.bitfire.dav4jvm.XmlUtils.FEATURE_RELAXED 14 | import at.bitfire.dav4jvm.exception.DavException 15 | import org.xmlpull.v1.XmlPullParser 16 | import org.xmlpull.v1.XmlPullParserException 17 | import org.xmlpull.v1.XmlPullParserFactory 18 | import org.xmlpull.v1.XmlSerializer 19 | 20 | object XmlUtils { 21 | 22 | /** 23 | * Requests the parser to be as lenient as possible when parsing invalid XML. 24 | * 25 | * See [https://www.xmlpull.org/](xmlpull.org) and specific implementations, for instance 26 | * [Android XML](https://developer.android.com/reference/android/util/Xml#FEATURE_RELAXED) 27 | */ 28 | private const val FEATURE_RELAXED = "http://xmlpull.org/v1/doc/features.html#relaxed" 29 | 30 | /** [XmlPullParserFactory] that is namespace-aware and does relaxed parsing */ 31 | private val relaxedFactory = 32 | XmlPullParserFactory.newInstance().apply { 33 | isNamespaceAware = true 34 | setFeature(FEATURE_RELAXED, true) 35 | } 36 | 37 | /** [XmlPullParserFactory] that is namespace-aware */ 38 | private val standardFactory: XmlPullParserFactory = 39 | XmlPullParserFactory.newInstance().apply { 40 | isNamespaceAware = true 41 | } 42 | 43 | /** 44 | * Creates a new [XmlPullParser]. 45 | * 46 | * First tries to create a namespace-aware parser that supports [FEATURE_RELAXED]. If that 47 | * fails, it falls back to a namespace-aware parser without relaxed parsing. 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 | ?: throw DavException("Couldn't create XML parser") 58 | 59 | fun newSerializer(): XmlSerializer = standardFactory.newSerializer() 60 | ?: throw DavException("Couldn't create XML serializer") 61 | 62 | 63 | fun XmlSerializer.insertTag(name: Property.Name, contentGenerator: XmlSerializer.() -> Unit = {}) { 64 | startTag(name.namespace, name.name) 65 | contentGenerator(this) 66 | endTag(name.namespace, name.name) 67 | } 68 | 69 | fun XmlPullParser.propertyName(): Property.Name { 70 | val propNs = namespace 71 | val propName = name 72 | if (propNs == null || propName == null) 73 | throw IllegalStateException("Current event must be START_TAG or END_TAG") 74 | return Property.Name(propNs, propName) 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/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.exception 12 | 13 | import okhttp3.Response 14 | import java.net.HttpURLConnection 15 | 16 | class ConflictException: HttpException { 17 | 18 | constructor(response: Response): super(response) 19 | constructor(message: String?): super(HttpURLConnection.HTTP_CONFLICT, message) 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/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.exception 12 | 13 | import at.bitfire.dav4jvm.Error 14 | import at.bitfire.dav4jvm.XmlUtils 15 | import at.bitfire.dav4jvm.XmlUtils.propertyName 16 | import at.bitfire.dav4jvm.exception.DavException.Companion.MAX_EXCERPT_SIZE 17 | import okhttp3.MediaType 18 | import okhttp3.Response 19 | import okio.Buffer 20 | import org.xmlpull.v1.XmlPullParser 21 | import org.xmlpull.v1.XmlPullParserException 22 | import java.io.ByteArrayOutputStream 23 | import java.io.IOException 24 | import java.io.Serializable 25 | import java.lang.Long.min 26 | import java.util.logging.Level 27 | import java.util.logging.Logger 28 | 29 | /** 30 | * Signals that an error occurred during a WebDAV-related operation. 31 | * 32 | * This could be a logical error like when a required ETag has not been 33 | * received, but also an explicit HTTP error. 34 | */ 35 | open class DavException @JvmOverloads constructor( 36 | message: String, 37 | ex: Throwable? = null, 38 | 39 | /** 40 | * An associated HTTP [Response]. Will be closed after evaluation. 41 | */ 42 | httpResponse: Response? = null 43 | ): Exception(message, ex), Serializable { 44 | 45 | companion object { 46 | 47 | const val MAX_EXCERPT_SIZE = 10*1024 // don't dump more than 20 kB 48 | 49 | fun isPlainText(type: MediaType) = 50 | type.type == "text" || 51 | (type.type == "application" && type.subtype in arrayOf("html", "xml")) 52 | 53 | } 54 | 55 | private val logger 56 | get() = Logger.getLogger(javaClass.name) 57 | 58 | var request: String? = null 59 | 60 | /** 61 | * Body excerpt of [request] (up to [MAX_EXCERPT_SIZE] characters). Only available 62 | * if the HTTP request body was textual content and could be read again. 63 | */ 64 | var requestBody: String? = null 65 | private set 66 | 67 | val response: String? 68 | 69 | /** 70 | * Body excerpt of [response] (up to [MAX_EXCERPT_SIZE] characters). Only available 71 | * if the HTTP response body was textual content. 72 | */ 73 | var responseBody: String? = null 74 | private set 75 | 76 | /** 77 | * Precondition/postcondition XML elements which have been found in the XML response. 78 | */ 79 | var errors: List = listOf() 80 | private set 81 | 82 | 83 | init { 84 | if (httpResponse != null) { 85 | response = httpResponse.toString() 86 | 87 | try { 88 | request = httpResponse.request.toString() 89 | 90 | httpResponse.request.body?.let { body -> 91 | body.contentType()?.let { type -> 92 | if (isPlainText(type)) { 93 | val buffer = Buffer() 94 | body.writeTo(buffer) 95 | 96 | val baos = ByteArrayOutputStream() 97 | buffer.writeTo(baos, min(buffer.size, MAX_EXCERPT_SIZE.toLong())) 98 | 99 | requestBody = baos.toString(type.charset(Charsets.UTF_8)!!.name()) 100 | } 101 | } 102 | } 103 | } catch (e: Exception) { 104 | logger.log(Level.WARNING, "Couldn't read HTTP request", e) 105 | requestBody = "Couldn't read HTTP request: ${e.message}" 106 | } 107 | 108 | try { 109 | // save response body excerpt 110 | if (httpResponse.body?.source() != null) { 111 | // response body has a source 112 | 113 | httpResponse.peekBody(MAX_EXCERPT_SIZE.toLong()).let { body -> 114 | body.contentType()?.let { mimeType -> 115 | if (isPlainText(mimeType)) 116 | responseBody = body.string() 117 | } 118 | } 119 | 120 | httpResponse.body?.use { body -> 121 | body.contentType()?.let { 122 | if (it.type in arrayOf("application", "text") && it.subtype == "xml") { 123 | // look for precondition/postcondition XML elements 124 | try { 125 | val parser = XmlUtils.newPullParser() 126 | parser.setInput(body.charStream()) 127 | 128 | var eventType = parser.eventType 129 | while (eventType != XmlPullParser.END_DOCUMENT) { 130 | if (eventType == XmlPullParser.START_TAG && parser.depth == 1) 131 | if (parser.propertyName() == Error.NAME) 132 | errors = Error.parseError(parser) 133 | eventType = parser.next() 134 | } 135 | } catch (e: XmlPullParserException) { 136 | logger.log(Level.WARNING, "Couldn't parse XML response", e) 137 | } 138 | } 139 | } 140 | } 141 | } 142 | } catch (e: IOException) { 143 | logger.log(Level.WARNING, "Couldn't read HTTP response", e) 144 | responseBody = "Couldn't read HTTP response: ${e.message}" 145 | } finally { 146 | httpResponse.body?.close() 147 | } 148 | } else 149 | response = null 150 | } 151 | 152 | } -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/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.exception 12 | 13 | import okhttp3.Response 14 | import java.net.HttpURLConnection 15 | 16 | class ForbiddenException: HttpException { 17 | 18 | constructor(response: Response): super(response) 19 | constructor(message: String?): super(HttpURLConnection.HTTP_FORBIDDEN, message) 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/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.exception 12 | 13 | import okhttp3.Response 14 | import java.net.HttpURLConnection 15 | 16 | class GoneException: HttpException { 17 | 18 | constructor(response: Response): super(response) 19 | constructor(message: String?): super(HttpURLConnection.HTTP_GONE, message) 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/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.exception 12 | 13 | import okhttp3.Response 14 | 15 | /** 16 | * Signals that a HTTP error was sent by the server. 17 | */ 18 | open class HttpException: DavException { 19 | 20 | var code: Int 21 | 22 | constructor(response: Response): super( 23 | "HTTP ${response.code} ${response.message}", 24 | httpResponse = response 25 | ) { 26 | code = response.code 27 | } 28 | 29 | constructor(code: Int, message: String?): super("HTTP $code $message") { 30 | this.code = code 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/exception/InvalidPropertyException.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.exception 12 | 13 | /** 14 | * Represents an invalid XML (WebDAV) property. This is for instance thrown 15 | * when parsing something like `...` 16 | * because a text value would be expected. 17 | */ 18 | class InvalidPropertyException(message: String): Exception(message) 19 | -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/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.exception 12 | 13 | import okhttp3.Response 14 | import java.net.HttpURLConnection 15 | 16 | class NotFoundException: HttpException { 17 | 18 | constructor(response: Response): super(response) 19 | constructor(message: String?): super(HttpURLConnection.HTTP_NOT_FOUND, message) 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/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.exception 12 | 13 | import okhttp3.Response 14 | import java.net.HttpURLConnection 15 | 16 | class PreconditionFailedException: HttpException { 17 | 18 | constructor(response: Response): super(response) 19 | constructor(message: String?): super(HttpURLConnection.HTTP_PRECON_FAILED, message) 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/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.exception 12 | 13 | import at.bitfire.dav4jvm.HttpUtils 14 | import at.bitfire.dav4jvm.exception.ServiceUnavailableException.Companion.DELAY_UNTIL_DEFAULT 15 | import at.bitfire.dav4jvm.exception.ServiceUnavailableException.Companion.DELAY_UNTIL_MAX 16 | import at.bitfire.dav4jvm.exception.ServiceUnavailableException.Companion.DELAY_UNTIL_MIN 17 | import okhttp3.Response 18 | import java.net.HttpURLConnection 19 | import java.time.Instant 20 | import java.util.logging.Level 21 | import java.util.logging.Logger 22 | 23 | class ServiceUnavailableException : HttpException { 24 | 25 | private val logger 26 | get() = Logger.getLogger(javaClass.name) 27 | 28 | val retryAfter: Instant? 29 | 30 | constructor(message: String?) : super(HttpURLConnection.HTTP_UNAVAILABLE, message) { 31 | retryAfter = null 32 | } 33 | 34 | constructor(response: Response) : super(response) { 35 | // Retry-After = "Retry-After" ":" ( HTTP-date | delta-seconds ) 36 | // HTTP-date = rfc1123-date | rfc850-date | asctime-date 37 | 38 | var retryAfterValue: Instant? = null 39 | response.header("Retry-After")?.let { after -> 40 | retryAfterValue = HttpUtils.parseDate(after) ?: 41 | // not a HTTP-date, must be delta-seconds 42 | try { 43 | val seconds = after.toLong() 44 | Instant.now().plusSeconds(seconds) 45 | } catch (e: NumberFormatException) { 46 | logger.log(Level.WARNING, "Received Retry-After which was not a HTTP-date nor delta-seconds: $after", e) 47 | null 48 | } 49 | } 50 | 51 | retryAfter = retryAfterValue 52 | } 53 | 54 | 55 | /** 56 | * Returns appropriate sync retry delay in seconds, considering the servers suggestion 57 | * in [retryAfter] ([DELAY_UNTIL_DEFAULT] if no server suggestion). 58 | * 59 | * Takes current time into account to calculate intervals. Interval 60 | * will be restricted to values between [DELAY_UNTIL_MIN] and [DELAY_UNTIL_MAX]. 61 | * 62 | * @param start timestamp to calculate the delay from (default: now) 63 | * 64 | * @return until when to wait before sync can be retried 65 | */ 66 | fun getDelayUntil(start: Instant = Instant.now()): Instant { 67 | if (retryAfter == null) 68 | return start.plusSeconds(DELAY_UNTIL_DEFAULT) 69 | 70 | // take server suggestion, but restrict to plausible min/max values 71 | return retryAfter.coerceIn( 72 | minimumValue = start.plusSeconds(DELAY_UNTIL_MIN), 73 | maximumValue = start.plusSeconds(DELAY_UNTIL_MAX) 74 | ) 75 | } 76 | 77 | 78 | companion object { 79 | 80 | // default values for getDelayUntil 81 | const val DELAY_UNTIL_DEFAULT = 15 * 60L // 15 min 82 | const val DELAY_UNTIL_MIN = 1 * 60L // 1 min 83 | const val DELAY_UNTIL_MAX = 2 * 60 * 60L // 2 hours 84 | 85 | } 86 | 87 | } -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/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.exception 12 | 13 | import okhttp3.Response 14 | import java.net.HttpURLConnection 15 | 16 | class UnauthorizedException: HttpException { 17 | 18 | constructor(response: Response): super(response) 19 | constructor(message: String?): super(HttpURLConnection.HTTP_UNAUTHORIZED, message) 20 | 21 | } 22 | -------------------------------------------------------------------------------- /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 | @JvmField 27 | val NAME = Property.Name(NS_APPLE_ICAL, "calendar-color") 28 | 29 | private val PATTERN = Pattern.compile("#?(\\p{XDigit}{6})(\\p{XDigit}{2})?")!! 30 | 31 | /** 32 | * Converts a WebDAV color from one of these formats: 33 | * #RRGGBB (alpha = 0xFF) 34 | * RRGGBB (alpha = 0xFF) 35 | * #RRGGBBAA 36 | * RRGGBBAA 37 | * to an [Int] with alpha. 38 | */ 39 | @Throws(IllegalArgumentException::class) 40 | fun parseARGBColor(davColor: String): Int { 41 | val m = PATTERN.matcher(davColor) 42 | if (m.find()) { 43 | val color_rgb = Integer.parseInt(m.group(1), 16) 44 | val color_alpha = m.group(2)?.let { Integer.parseInt(m.group(2), 16) and 0xFF } ?: 0xFF 45 | return (color_alpha shl 24) or color_rgb 46 | } else 47 | throw IllegalArgumentException("Couldn't parse color value: $davColor") 48 | } 49 | } 50 | 51 | 52 | object Factory: PropertyFactory { 53 | 54 | override fun getName() = NAME 55 | 56 | override fun create(parser: XmlPullParser): CalendarColor { 57 | XmlReader(parser).readText()?.let { 58 | try { 59 | return CalendarColor(parseARGBColor(it)) 60 | } catch (e: IllegalArgumentException) { 61 | val logger = Logger.getLogger(javaClass.name) 62 | logger.log(Level.WARNING, "Couldn't parse color, ignoring", e) 63 | } 64 | } 65 | return CalendarColor(null) 66 | } 67 | 68 | } 69 | 70 | } -------------------------------------------------------------------------------- /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 | @JvmField 24 | val NAME = Property.Name(NS_CALDAV, "calendar-data") 25 | 26 | // attributes 27 | const val CONTENT_TYPE = "content-type" 28 | const val VERSION = "version" 29 | } 30 | 31 | 32 | object Factory: PropertyFactory { 33 | 34 | override fun getName() = NAME 35 | 36 | override fun create(parser: XmlPullParser) = 37 | // 38 | CalendarData(XmlReader(parser).readText()) 39 | 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /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 | companion object { 23 | @JvmField 24 | val NAME = Property.Name(NS_CALDAV, "calendar-description") 25 | } 26 | 27 | 28 | object Factory: PropertyFactory { 29 | 30 | override fun getName() = NAME 31 | 32 | override fun create(parser: XmlPullParser) = 33 | // 34 | CalendarDescription(XmlReader(parser).readText()) 35 | 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /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 14 | import at.bitfire.dav4jvm.property.webdav.HrefListProperty 15 | import org.xmlpull.v1.XmlPullParser 16 | 17 | data class CalendarHomeSet( 18 | override val hrefs: List = emptyList() 19 | ): HrefListProperty(hrefs) { 20 | 21 | companion object { 22 | 23 | @JvmField 24 | val NAME = Property.Name(NS_CALDAV, "calendar-home-set") 25 | 26 | } 27 | 28 | 29 | object Factory: HrefListProperty.Factory() { 30 | 31 | override fun getName() = NAME 32 | 33 | override fun create(parser: XmlPullParser) = create(parser, ::CalendarHomeSet) 34 | 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /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 14 | import at.bitfire.dav4jvm.property.webdav.HrefListProperty 15 | import org.xmlpull.v1.XmlPullParser 16 | 17 | data class CalendarProxyReadFor( 18 | override val hrefs: List = emptyList() 19 | ): HrefListProperty(hrefs) { 20 | 21 | companion object { 22 | @JvmField 23 | val NAME = Property.Name(NS_CALENDARSERVER, "calendar-proxy-read-for") 24 | } 25 | 26 | 27 | object Factory: HrefListProperty.Factory() { 28 | 29 | override fun getName() = NAME 30 | 31 | override fun create(parser: XmlPullParser) = create(parser, ::CalendarProxyReadFor) 32 | 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /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 14 | import at.bitfire.dav4jvm.property.webdav.HrefListProperty 15 | import org.xmlpull.v1.XmlPullParser 16 | 17 | data class CalendarProxyWriteFor( 18 | override val hrefs: List = emptyList() 19 | ): HrefListProperty(hrefs) { 20 | 21 | companion object { 22 | @JvmField 23 | val NAME = Property.Name(NS_CALENDARSERVER, "calendar-proxy-write-for") 24 | } 25 | 26 | 27 | object Factory: HrefListProperty.Factory() { 28 | 29 | override fun getName() = NAME 30 | 31 | override fun create(parser: XmlPullParser) = create(parser, ::CalendarProxyWriteFor) 32 | 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /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 | companion object { 23 | @JvmField 24 | val NAME = Property.Name(NS_CALDAV, "calendar-timezone") 25 | } 26 | 27 | 28 | object Factory: PropertyFactory { 29 | 30 | override fun getName() = NAME 31 | 32 | override fun create(parser: XmlPullParser) = 33 | // 34 | CalendarTimezone(XmlReader(parser).readText()) 35 | 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /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 | companion object { 23 | @JvmField 24 | val NAME = Property.Name(NS_CALDAV, "calendar-timezone-id") 25 | } 26 | 27 | 28 | object Factory: PropertyFactory { 29 | 30 | override fun getName() = NAME 31 | 32 | override fun create(parser: XmlPullParser) = 33 | // 34 | CalendarTimezoneId(XmlReader(parser).readText()) 35 | 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /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 14 | import at.bitfire.dav4jvm.property.webdav.HrefListProperty 15 | import org.xmlpull.v1.XmlPullParser 16 | 17 | data class CalendarUserAddressSet( 18 | override val hrefs: List = emptyList() 19 | ): HrefListProperty(hrefs) { 20 | 21 | companion object { 22 | @JvmField 23 | val NAME = Property.Name(NS_CALDAV, "calendar-user-address-set") 24 | } 25 | 26 | 27 | object Factory: HrefListProperty.Factory() { 28 | 29 | override fun getName() = NAME 30 | 31 | override fun create(parser: XmlPullParser) = create(parser, ::CalendarUserAddressSet) 32 | 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /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 | companion object { 23 | @JvmField 24 | val NAME = Property.Name(NS_CALENDARSERVER, "getctag") 25 | } 26 | 27 | 28 | object Factory: PropertyFactory { 29 | 30 | override fun getName() = NAME 31 | 32 | override fun create(parser: XmlPullParser) = GetCTag(XmlReader(parser).readText()) 33 | 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /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 | companion object { 22 | @JvmField 23 | val NAME = Property.Name(NS_CALDAV, "max-resource-size") 24 | } 25 | 26 | object Factory: PropertyFactory { 27 | override fun getName() = NAME 28 | 29 | override fun create(parser: XmlPullParser) = 30 | MaxResourceSize(XmlReader(parser).readLong()) 31 | } 32 | } -------------------------------------------------------------------------------- /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 okhttp3.Response 18 | import org.xmlpull.v1.XmlPullParser 19 | 20 | data class ScheduleTag( 21 | val rawScheduleTag: String? 22 | ): Property { 23 | 24 | companion object { 25 | 26 | @JvmField 27 | val NAME = Property.Name(NS_CALDAV, "schedule-tag") 28 | 29 | fun fromResponse(response: Response) = 30 | response.header("Schedule-Tag")?.let { ScheduleTag(it) } 31 | 32 | } 33 | 34 | /* Value: opaque-tag 35 | opaque-tag = quoted-string 36 | */ 37 | val scheduleTag: String? = rawScheduleTag?.let { QuotedStringUtils.decodeQuotedString(it) } 38 | 39 | override fun toString() = scheduleTag ?: "(null)" 40 | 41 | 42 | object Factory: PropertyFactory { 43 | 44 | override fun getName() = NAME 45 | 46 | override fun create(parser: XmlPullParser) = ScheduleTag(XmlReader(parser).readText()) 47 | 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /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 14 | import at.bitfire.dav4jvm.property.webdav.HrefListProperty 15 | import org.xmlpull.v1.XmlPullParser 16 | 17 | class Source( 18 | override val hrefs: List = emptyList() 19 | ): HrefListProperty(hrefs) { 20 | 21 | companion object { 22 | 23 | @JvmField 24 | val NAME = Property.Name(NS_CALENDARSERVER, "source") 25 | 26 | } 27 | 28 | 29 | object Factory: HrefListProperty.Factory() { 30 | 31 | override fun getName() = NAME 32 | 33 | override fun create(parser: XmlPullParser) = create(parser, ::Source) 34 | 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /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 | companion object { 25 | 26 | @JvmField 27 | val NAME = Property.Name(NS_CALDAV, "supported-calendar-component-set") 28 | 29 | val ALLCOMP = Property.Name(NS_CALDAV, "allcomp") 30 | val COMP = Property.Name(NS_CALDAV, "comp") 31 | 32 | } 33 | 34 | 35 | object Factory: PropertyFactory { 36 | 37 | override fun getName() = NAME 38 | 39 | override fun create(parser: XmlPullParser): SupportedCalendarComponentSet { 40 | /* 41 | 42 | 43 | */ 44 | var components = SupportedCalendarComponentSet( 45 | supportsEvents = false, 46 | supportsTasks = false, 47 | supportsJournal = false 48 | ) 49 | 50 | val depth = parser.depth 51 | var eventType = parser.eventType 52 | while (!(eventType == XmlPullParser.END_TAG && parser.depth == depth)) { 53 | if (eventType == XmlPullParser.START_TAG && parser.depth == depth + 1) { 54 | when (parser.propertyName()) { 55 | ALLCOMP -> { 56 | components = SupportedCalendarComponentSet( 57 | supportsEvents = true, 58 | supportsTasks = true, 59 | supportsJournal = true 60 | ) 61 | } 62 | COMP -> 63 | when (parser.getAttributeValue(null, "name")?.uppercase()) { 64 | "VEVENT" -> components = components.copy(supportsEvents = true) 65 | "VTODO" -> components = components.copy(supportsTasks = true) 66 | "VJOURNAL" -> components = components.copy(supportsJournal = true) 67 | } 68 | } 69 | } 70 | eventType = parser.next() 71 | } 72 | 73 | return components 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /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 okhttp3.MediaType 17 | import org.xmlpull.v1.XmlPullParser 18 | 19 | data class SupportedCalendarData( 20 | val types: Set = emptySet() 21 | ): Property { 22 | 23 | companion object { 24 | 25 | @JvmField 26 | val NAME = Property.Name(NS_CALDAV, "supported-calendar-data") 27 | 28 | val CALENDAR_DATA_TYPE = Property.Name(NS_CALDAV, "calendar-data") 29 | const val CONTENT_TYPE = "content-type" 30 | const val VERSION = "version" 31 | 32 | } 33 | 34 | fun hasJCal() = types.any { "application".equals(it.type, true) && "calendar+json".equals(it.subtype, true) } 35 | 36 | 37 | object Factory: PropertyFactory { 38 | 39 | override fun getName() = NAME 40 | 41 | override fun create(parser: XmlPullParser): SupportedCalendarData { 42 | val supportedTypes = mutableSetOf() 43 | 44 | XmlReader(parser).readContentTypes(CALENDAR_DATA_TYPE, supportedTypes::add) 45 | 46 | return SupportedCalendarData(supportedTypes) 47 | } 48 | 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/caldav/namespace.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 | /** 14 | * CalDAV XML namespace (defined in RFC 4791) 15 | */ 16 | const val NS_CALDAV = "urn:ietf:params:xml:ns:caldav" 17 | 18 | const val NS_APPLE_ICAL = "http://apple.com/ns/ical/" 19 | const val NS_CALENDARSERVER = "http://calendarserver.org/ns/" 20 | -------------------------------------------------------------------------------- /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 | @JvmField 25 | val NAME = Property.Name(NS_CARDDAV, "address-data") 26 | 27 | // attributes 28 | const val CONTENT_TYPE = "content-type" 29 | const val VERSION = "version" 30 | 31 | } 32 | 33 | 34 | object Factory: PropertyFactory { 35 | 36 | override fun getName() = NAME 37 | 38 | override fun create(parser: XmlPullParser) = 39 | // 40 | AddressData(XmlReader(parser).readText()) 41 | 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /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 | companion object { 23 | @JvmField 24 | val NAME = Property.Name(NS_CARDDAV, "addressbook-description") 25 | } 26 | 27 | object Factory: PropertyFactory { 28 | 29 | override fun getName() = NAME 30 | 31 | override fun create(parser: XmlPullParser) = 32 | // 33 | AddressbookDescription(XmlReader(parser).readText()) 34 | 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /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 14 | import at.bitfire.dav4jvm.property.webdav.HrefListProperty 15 | import org.xmlpull.v1.XmlPullParser 16 | 17 | class AddressbookHomeSet( 18 | override val hrefs: List = emptyList() 19 | ): HrefListProperty(hrefs) { 20 | 21 | companion object { 22 | 23 | @JvmField 24 | val NAME = Property.Name(NS_CARDDAV, "addressbook-home-set") 25 | 26 | } 27 | 28 | 29 | object Factory: HrefListProperty.Factory() { 30 | 31 | override fun getName() = NAME 32 | 33 | override fun create(parser: XmlPullParser) = create(parser, ::AddressbookHomeSet) 34 | 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /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 | companion object { 23 | 24 | @JvmField 25 | val NAME = Property.Name(NS_CARDDAV, "max-resource-size") 26 | 27 | } 28 | 29 | object Factory: PropertyFactory { 30 | 31 | override fun getName() = NAME 32 | 33 | override fun create(parser: XmlPullParser) = 34 | MaxResourceSize(XmlReader(parser).readLong()) 35 | 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /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 okhttp3.MediaType 17 | import org.xmlpull.v1.XmlPullParser 18 | 19 | class SupportedAddressData( 20 | val types: Set = emptySet() 21 | ): Property { 22 | 23 | companion object { 24 | 25 | @JvmField 26 | val NAME = Property.Name(NS_CARDDAV, "supported-address-data") 27 | 28 | val ADDRESS_DATA_TYPE = Property.Name(NS_CARDDAV, "address-data-type") 29 | const val CONTENT_TYPE = "content-type" 30 | const val VERSION = "version" 31 | 32 | } 33 | 34 | fun hasVCard4() = types.any { "text/vcard; version=4.0".equals(it.toString(), true) } 35 | fun hasJCard() = types.any { "application".equals(it.type, true) && "vcard+json".equals(it.subtype, true) } 36 | 37 | override fun toString() = "[${types.joinToString(", ")}]" 38 | 39 | 40 | object Factory: PropertyFactory { 41 | 42 | override fun getName() = NAME 43 | 44 | override fun create(parser: XmlPullParser): SupportedAddressData { 45 | val supportedTypes = mutableSetOf() 46 | 47 | XmlReader(parser).readContentTypes(ADDRESS_DATA_TYPE, supportedTypes::add) 48 | 49 | return SupportedAddressData(supportedTypes) 50 | } 51 | 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/carddav/namespace.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 | /** 14 | * CardDAV XML namespace (defined in RFC 6352) 15 | */ 16 | const val NS_CARDDAV = "urn:ietf:params:xml:ns:carddav" 17 | -------------------------------------------------------------------------------- /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 a [NS_WEBDAV_PUSH]`: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 | companion object { 28 | 29 | @JvmField 30 | val NAME = Property.Name(NS_WEBDAV_PUSH, "auth-secret") 31 | 32 | } 33 | 34 | 35 | object Factory: PropertyFactory { 36 | 37 | override fun getName() = NAME 38 | 39 | override fun create(parser: XmlPullParser): AuthSecret = 40 | AuthSecret(XmlReader(parser).readText()) 41 | 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /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.SyncLevel 18 | import at.bitfire.dav4jvm.property.webdav.SyncToken 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 | companion object { 32 | 33 | @JvmField 34 | val NAME = Property.Name(NS_WEBDAV_PUSH, "content-update") 35 | 36 | } 37 | 38 | 39 | object Factory: PropertyFactory { 40 | 41 | override fun getName() = NAME 42 | 43 | override fun create(parser: XmlPullParser): ContentUpdate { 44 | var contentUpdate = ContentUpdate() 45 | 46 | val depth = parser.depth 47 | var eventType = parser.eventType 48 | while (!(eventType == XmlPullParser.END_TAG && parser.depth == depth)) { 49 | if (eventType == XmlPullParser.START_TAG && parser.depth == depth + 1) { 50 | when (parser.propertyName()) { 51 | SyncLevel.NAME -> contentUpdate = contentUpdate.copy( 52 | depth = Depth.Factory.create(parser) 53 | ) 54 | SyncToken.NAME -> contentUpdate = contentUpdate.copy( 55 | syncToken = SyncToken.Factory.create(parser) 56 | ) 57 | } 58 | } 59 | eventType = parser.next() 60 | } 61 | 62 | return contentUpdate 63 | } 64 | 65 | } 66 | 67 | } -------------------------------------------------------------------------------- /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 org.xmlpull.v1.XmlPullParser 18 | 19 | /** 20 | * Represents a [NS_WEBDAV_PUSH]`:property-update` property. 21 | * 22 | * Experimental! See https://github.com/bitfireAT/webdav-push/ 23 | */ 24 | data class PropertyUpdate( 25 | val syncLevel: SyncLevel? = null, 26 | ): Property { 27 | 28 | companion object { 29 | 30 | @JvmField 31 | val NAME = Property.Name(NS_WEBDAV_PUSH, "property-update") 32 | 33 | } 34 | 35 | 36 | object Factory: PropertyFactory { 37 | 38 | override fun getName() = NAME 39 | 40 | override fun create(parser: XmlPullParser): PropertyUpdate { 41 | var propertyUpdate = PropertyUpdate() 42 | 43 | val depth = parser.depth 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 | SyncLevel.NAME -> propertyUpdate = propertyUpdate.copy( 49 | syncLevel = SyncLevel.Factory.create(parser) 50 | ) 51 | } 52 | } 53 | eventType = parser.next() 54 | } 55 | return propertyUpdate 56 | } 57 | 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /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 | companion object { 30 | 31 | @JvmField 32 | val NAME = Property.Name(NS_WEBDAV_PUSH, "push-message") 33 | 34 | } 35 | 36 | 37 | object Factory: PropertyFactory { 38 | 39 | override fun getName() = NAME 40 | 41 | override fun create(parser: XmlPullParser): PushMessage { 42 | var message = PushMessage() 43 | 44 | val depth = parser.depth 45 | var eventType = parser.eventType 46 | while (!(eventType == XmlPullParser.END_TAG && parser.depth == depth)) { 47 | if (eventType == XmlPullParser.START_TAG && parser.depth == depth + 1) { 48 | when (parser.propertyName()) { 49 | Topic.NAME -> message = message.copy( 50 | topic = Topic.Factory.create(parser) 51 | ) 52 | ContentUpdate.NAME -> message = message.copy( 53 | contentUpdate = ContentUpdate.Factory.create(parser) 54 | ) 55 | PropertyUpdate.NAME -> message = message.copy( 56 | propertyUpdate = PropertyUpdate.Factory.create(parser) 57 | ) 58 | } 59 | } 60 | eventType = parser.next() 61 | } 62 | 63 | return message 64 | } 65 | 66 | } 67 | 68 | } -------------------------------------------------------------------------------- /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 | companion object { 33 | 34 | @JvmField 35 | val NAME = Property.Name(NS_WEBDAV_PUSH, "push-register") 36 | 37 | val EXPIRES = Property.Name(NS_WEBDAV_PUSH, "expires") 38 | 39 | } 40 | 41 | 42 | object Factory: PropertyFactory { 43 | 44 | override fun getName() = NAME 45 | 46 | override fun create(parser: XmlPullParser): PushRegister { 47 | var register = PushRegister() 48 | 49 | val depth = parser.depth 50 | var eventType = parser.eventType 51 | while (!(eventType == XmlPullParser.END_TAG && parser.depth == depth)) { 52 | if (eventType == XmlPullParser.START_TAG && parser.depth == depth + 1) 53 | when (parser.propertyName()) { 54 | EXPIRES -> 55 | register = register.copy( 56 | expires = XmlReader(parser).readText()?.let { 57 | HttpUtils.parseDate(it) 58 | } 59 | ) 60 | Subscription.NAME -> 61 | register = register.copy( 62 | subscription = Subscription.Factory.create(parser) 63 | ) 64 | Trigger.NAME -> 65 | register = register.copy( 66 | trigger = Trigger.Factory.create(parser) 67 | ) 68 | } 69 | eventType = parser.next() 70 | } 71 | 72 | return register 73 | } 74 | 75 | } 76 | 77 | } -------------------------------------------------------------------------------- /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 | companion object { 30 | 31 | @JvmField 32 | val NAME = Property.Name(NS_WEBDAV_PUSH, "push-resource") 33 | 34 | } 35 | 36 | 37 | object Factory: PropertyFactory { 38 | 39 | override fun getName() = NAME 40 | 41 | override fun create(parser: XmlPullParser): PushResource = 42 | PushResource( 43 | uri = XmlReader(parser).readText()?.let { uri -> 44 | try { 45 | URI(uri) 46 | } catch (_: URISyntaxException) { 47 | null 48 | } 49 | } 50 | ) 51 | 52 | } 53 | 54 | } -------------------------------------------------------------------------------- /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/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 | companion object { 28 | @JvmField 29 | val NAME = Property.Name(NS_WEBDAV_PUSH, "transports") 30 | } 31 | 32 | fun hasWebPush() = transports.any { it is WebPush } 33 | 34 | 35 | object Factory: PropertyFactory { 36 | 37 | override fun getName() = NAME 38 | 39 | override fun create(parser: XmlPullParser): PushTransports { 40 | val transports = mutableListOf() 41 | val depth = parser.depth 42 | var eventType = parser.eventType 43 | while (!(eventType == XmlPullParser.END_TAG && parser.depth == depth)) { 44 | if (eventType == XmlPullParser.START_TAG && parser.depth == depth + 1) { 45 | when (parser.propertyName()) { 46 | WebPush.NAME -> transports += WebPush.Factory.create(parser) 47 | } 48 | } 49 | eventType = parser.next() 50 | } 51 | return PushTransports(transports.toSet()) 52 | } 53 | 54 | } 55 | 56 | } -------------------------------------------------------------------------------- /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 | companion object { 28 | 29 | @JvmField 30 | val NAME = Property.Name(NS_WEBDAV_PUSH, "subscription") 31 | 32 | } 33 | 34 | 35 | object Factory: PropertyFactory { 36 | 37 | override fun getName() = NAME 38 | 39 | override fun create(parser: XmlPullParser): Subscription { 40 | // currently we only support WebPushSubscription 41 | var webPushSubscription: WebPushSubscription? = null 42 | 43 | XmlReader(parser).processTag(WebPushSubscription.NAME) { 44 | webPushSubscription = WebPushSubscription.Factory.create(parser) 45 | } 46 | 47 | return Subscription(webPushSubscription) 48 | } 49 | 50 | } 51 | 52 | } -------------------------------------------------------------------------------- /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 | companion object { 29 | 30 | @JvmField 31 | val NAME = Property.Name(NS_WEBDAV_PUSH, "subscription-public-key") 32 | 33 | } 34 | 35 | 36 | object Factory : PropertyFactory { 37 | 38 | override fun getName() = NAME 39 | 40 | override fun create(parser: XmlPullParser): SubscriptionPublicKey { 41 | return SubscriptionPublicKey( 42 | type = parser.getAttributeValue(null, "type"), 43 | key = XmlReader(parser).readText() 44 | ) 45 | } 46 | 47 | } 48 | 49 | } -------------------------------------------------------------------------------- /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 | companion object { 29 | 30 | @JvmField 31 | val NAME = Property.Name(NS_WEBDAV_PUSH, "supported-triggers") 32 | 33 | } 34 | 35 | 36 | object Factory: PropertyFactory { 37 | 38 | override fun getName() = NAME 39 | 40 | override fun create(parser: XmlPullParser): SupportedTriggers { 41 | var supportedTriggers = SupportedTriggers() 42 | 43 | val depth = parser.depth 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 | ContentUpdate.NAME -> supportedTriggers = supportedTriggers.copy( 49 | contentUpdate = ContentUpdate.Factory.create(parser) 50 | ) 51 | PropertyUpdate.NAME -> supportedTriggers = supportedTriggers.copy( 52 | propertyUpdate = PropertyUpdate.Factory.create(parser) 53 | ) 54 | } 55 | } 56 | eventType = parser.next() 57 | } 58 | 59 | return supportedTriggers 60 | } 61 | 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /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 | companion object { 28 | 29 | @JvmField 30 | val NAME = Property.Name(NS_WEBDAV_PUSH, "topic") 31 | 32 | } 33 | 34 | 35 | object Factory: PropertyFactory { 36 | 37 | override fun getName() = NAME 38 | 39 | override fun create(parser: XmlPullParser): Topic = 40 | Topic(XmlReader(parser).readText()) 41 | 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /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 | companion object { 24 | 25 | @JvmField 26 | val NAME = Property.Name(NS_WEBDAV_PUSH, "trigger") 27 | 28 | } 29 | 30 | 31 | object Factory : PropertyFactory { 32 | 33 | override fun getName() = NAME 34 | 35 | override fun create(parser: XmlPullParser): Trigger { 36 | var trigger = Trigger() 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 | ContentUpdate.NAME -> trigger = trigger.copy( 44 | contentUpdate = ContentUpdate.Factory.create(parser) 45 | ) 46 | PropertyUpdate.NAME -> trigger = trigger.copy( 47 | propertyUpdate = PropertyUpdate.Factory.create(parser) 48 | ) 49 | } 50 | } 51 | eventType = parser.next() 52 | } 53 | 54 | return trigger 55 | } 56 | 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /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 | companion object { 29 | 30 | @JvmField 31 | val NAME = Property.Name(NS_WEBDAV_PUSH, "vapid-public-key") 32 | 33 | } 34 | 35 | 36 | object Factory : PropertyFactory { 37 | 38 | override fun getName() = NAME 39 | 40 | override fun create(parser: XmlPullParser): VapidPublicKey { 41 | return VapidPublicKey( 42 | type = parser.getAttributeValue(null, "type"), 43 | key = XmlReader(parser).readText() 44 | ) 45 | } 46 | 47 | } 48 | 49 | } -------------------------------------------------------------------------------- /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 org.xmlpull.v1.XmlPullParser 16 | 17 | /** 18 | * Represents a [NS_WEBDAV_PUSH]`:web-push` property. 19 | * 20 | * Experimental! See https://github.com/bitfireAT/webdav-push/ 21 | */ 22 | data class WebPush( 23 | val vapidPublicKey: VapidPublicKey? = null 24 | ) : PushTransport { 25 | 26 | companion object { 27 | @JvmField 28 | val NAME = Property.Name(NS_WEBDAV_PUSH, "web-push") 29 | } 30 | 31 | 32 | object Factory : PropertyFactory { 33 | 34 | override fun getName(): Property.Name = NAME 35 | 36 | override fun create(parser: XmlPullParser): WebPush { 37 | var vapidPublicKey: VapidPublicKey? = null 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.namespace == NS_WEBDAV_PUSH) { 42 | when (parser.name) { 43 | VapidPublicKey.NAME.name -> vapidPublicKey = VapidPublicKey.Factory.create(parser) 44 | } 45 | } 46 | eventType = parser.next() 47 | } 48 | return WebPush(vapidPublicKey) 49 | } 50 | 51 | } 52 | 53 | } -------------------------------------------------------------------------------- /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 | companion object { 30 | 31 | @JvmField 32 | val NAME = Property.Name(NS_WEBDAV_PUSH, "web-push-subscription") 33 | 34 | } 35 | 36 | 37 | object Factory: PropertyFactory { 38 | 39 | override fun getName() = NAME 40 | 41 | override fun create(parser: XmlPullParser): WebPushSubscription { 42 | var subscription = WebPushSubscription() 43 | 44 | val depth = parser.depth 45 | var eventType = parser.eventType 46 | while (!(eventType == XmlPullParser.END_TAG && parser.depth == depth)) { 47 | if (eventType == XmlPullParser.START_TAG && parser.depth == depth + 1) { 48 | when (parser.propertyName()) { 49 | PushResource.NAME -> subscription = subscription.copy(pushResource = PushResource.Factory.create(parser)) 50 | SubscriptionPublicKey.NAME -> subscription = subscription.copy(subscriptionPublicKey = SubscriptionPublicKey.Factory.create(parser)) 51 | AuthSecret.NAME -> subscription = subscription.copy(authSecret = AuthSecret.Factory.create(parser)) 52 | } 53 | } 54 | eventType = parser.next() 55 | } 56 | 57 | return subscription 58 | } 59 | 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/push/namespace.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 | /** 14 | * XML namespace of WebDAV-Push (draft), see: 15 | * https://github.com/bitfireAT/webdav-push/ 16 | * 17 | * Experimental! 18 | */ 19 | const val NS_WEBDAV_PUSH = "https://bitfire.at/webdav-push" 20 | -------------------------------------------------------------------------------- /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.DavResource 14 | import at.bitfire.dav4jvm.Property 15 | import at.bitfire.dav4jvm.PropertyFactory 16 | import at.bitfire.dav4jvm.XmlReader 17 | import org.xmlpull.v1.XmlPullParser 18 | 19 | /** 20 | * Defined in RFC 5995 3.2.1 DAV:add-member Property (Protected). 21 | */ 22 | data class AddMember( 23 | val href: String? 24 | ): Property { 25 | 26 | companion object { 27 | 28 | @JvmField 29 | val NAME = Property.Name(NS_WEBDAV, "add-member") 30 | 31 | } 32 | 33 | object Factory: PropertyFactory { 34 | 35 | override fun getName() = NAME 36 | 37 | override fun create(parser: XmlPullParser) = AddMember(XmlReader(parser).readTextProperty(DavResource.HREF)) 38 | 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /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 | companion object { 23 | 24 | @JvmField 25 | val NAME = Property.Name(NS_WEBDAV, "creationdate") 26 | 27 | } 28 | 29 | object Factory: PropertyFactory { 30 | 31 | override fun getName() = NAME 32 | 33 | override fun create(parser: XmlPullParser) = 34 | CreationDate(XmlReader(parser).readText()) 35 | 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /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.DavResource 14 | import at.bitfire.dav4jvm.Property 15 | import at.bitfire.dav4jvm.PropertyFactory 16 | import at.bitfire.dav4jvm.XmlReader 17 | import org.xmlpull.v1.XmlPullParser 18 | 19 | // see RFC 5397: WebDAV Current Principal Extension 20 | 21 | data class CurrentUserPrincipal( 22 | val href: String? 23 | ): Property { 24 | 25 | companion object { 26 | 27 | @JvmField 28 | val NAME = Property.Name(NS_WEBDAV, "current-user-principal") 29 | 30 | } 31 | 32 | 33 | object Factory: PropertyFactory { 34 | 35 | override fun getName() = NAME 36 | 37 | override fun create(parser: XmlPullParser): CurrentUserPrincipal { 38 | // 39 | var href: String? = null 40 | XmlReader(parser).processTag(DavResource.HREF) { 41 | href = readText() 42 | } 43 | return CurrentUserPrincipal(href) 44 | } 45 | 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /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 | companion object { 30 | 31 | @JvmField 32 | val NAME = Property.Name(NS_WEBDAV, "current-user-privilege-set") 33 | 34 | val PRIVILEGE = Property.Name(NS_WEBDAV, "privilege") 35 | val READ = Property.Name(NS_WEBDAV, "read") 36 | val WRITE = Property.Name(NS_WEBDAV, "write") 37 | val WRITE_PROPERTIES = Property.Name(NS_WEBDAV, "write-properties") 38 | val WRITE_CONTENT = Property.Name(NS_WEBDAV, "write-content") 39 | val BIND = Property.Name(NS_WEBDAV, "bind") 40 | val UNBIND = Property.Name(NS_WEBDAV, "unbind") 41 | val ALL = Property.Name(NS_WEBDAV, "all") 42 | 43 | } 44 | 45 | 46 | object Factory: PropertyFactory { 47 | 48 | override fun getName() = NAME 49 | 50 | override fun create(parser: XmlPullParser): CurrentUserPrivilegeSet { 51 | // 52 | // 53 | var privs = CurrentUserPrivilegeSet() 54 | 55 | XmlReader(parser).processTag(PRIVILEGE) { 56 | val depth = parser.depth 57 | var eventType = parser.eventType 58 | while (!(eventType == XmlPullParser.END_TAG && parser.depth == depth)) { 59 | if (eventType == XmlPullParser.START_TAG && parser.depth == depth + 1) 60 | when (parser.propertyName()) { 61 | READ -> 62 | privs = privs.copy(mayRead = true) 63 | WRITE -> { 64 | privs = privs.copy( 65 | mayBind = true, 66 | mayUnbind = true, 67 | mayWriteProperties = true, 68 | mayWriteContent = true 69 | ) 70 | } 71 | WRITE_PROPERTIES -> 72 | privs = privs.copy(mayWriteProperties = true) 73 | WRITE_CONTENT -> 74 | privs = privs.copy(mayWriteContent = true) 75 | BIND -> 76 | privs = privs.copy(mayBind = true) 77 | UNBIND -> 78 | privs = privs.copy(mayUnbind = true) 79 | ALL -> { 80 | privs = privs.copy( 81 | mayRead = true, 82 | mayBind = true, 83 | mayUnbind = true, 84 | mayWriteProperties = true, 85 | mayWriteContent = true 86 | ) 87 | } 88 | } 89 | eventType = parser.next() 90 | } 91 | } 92 | 93 | return privs 94 | } 95 | 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /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 | 28 | @JvmField 29 | val NAME = Property.Name(NS_WEBDAV, "depth") 30 | 31 | const val INFINITY = Int.MAX_VALUE 32 | 33 | } 34 | 35 | 36 | object Factory: PropertyFactory { 37 | 38 | override fun getName() = NAME 39 | 40 | override fun create(parser: XmlPullParser): Depth { 41 | val text = XmlReader(parser).readText() 42 | val level = if (text.equals("infinity", true)) 43 | INFINITY 44 | else 45 | text?.toIntOrNull() 46 | return Depth(level) 47 | } 48 | 49 | } 50 | 51 | } -------------------------------------------------------------------------------- /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 | companion object { 23 | 24 | @JvmField 25 | val NAME = Property.Name(NS_WEBDAV, "displayname") 26 | 27 | } 28 | 29 | 30 | object Factory: PropertyFactory { 31 | 32 | override fun getName() = NAME 33 | 34 | override fun create(parser: XmlPullParser) = 35 | // 36 | DisplayName(XmlReader(parser).readText()) 37 | 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /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 | companion object { 23 | 24 | @JvmField 25 | val NAME = Property.Name(NS_WEBDAV, "getcontentlength") 26 | 27 | } 28 | 29 | object Factory: PropertyFactory { 30 | 31 | override fun getName() = NAME 32 | 33 | override fun create(parser: XmlPullParser) = 34 | GetContentLength(XmlReader(parser).readLong()) 35 | 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /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 okhttp3.MediaType 17 | import okhttp3.MediaType.Companion.toMediaTypeOrNull 18 | import org.xmlpull.v1.XmlPullParser 19 | 20 | data class GetContentType( 21 | val type: MediaType? 22 | ): Property { 23 | 24 | companion object { 25 | 26 | @JvmField 27 | val NAME = Property.Name(NS_WEBDAV, "getcontenttype") 28 | 29 | } 30 | 31 | 32 | object Factory: PropertyFactory { 33 | 34 | override fun getName() = NAME 35 | 36 | override fun create(parser: XmlPullParser) = 37 | // 38 | GetContentType(XmlReader(parser).readText()?.toMediaTypeOrNull()) 39 | 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /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 okhttp3.Response 18 | import org.xmlpull.v1.XmlPullParser 19 | 20 | /** 21 | * The GetETag property. 22 | * 23 | * Can also be used to parse ETags from HTTP responses – just pass the raw ETag 24 | * header value to the constructor and then use [eTag] and [weak]. 25 | */ 26 | data class GetETag( 27 | val rawETag: String? 28 | ): Property { 29 | 30 | companion object { 31 | @JvmField 32 | val NAME = Property.Name(NS_WEBDAV, "getetag") 33 | 34 | fun fromResponse(response: Response) = 35 | response.header("ETag")?.let { GetETag(it) } 36 | } 37 | 38 | /** 39 | * The parsed ETag value, excluding the weakness indicator and the quotes. 40 | */ 41 | val eTag: String? 42 | 43 | /** 44 | * Whether the ETag is weak. 45 | */ 46 | var weak: Boolean 47 | 48 | init { 49 | /* entity-tag = [ weak ] opaque-tag 50 | weak = "W/" 51 | opaque-tag = quoted-string 52 | */ 53 | 54 | if (rawETag != null) { 55 | val tag: String? 56 | // remove trailing "W/" 57 | if (rawETag.startsWith("W/")) { 58 | // entity tag is weak 59 | tag = rawETag.substring(2) 60 | weak = true 61 | } else { 62 | tag = rawETag 63 | weak = false 64 | } 65 | eTag = QuotedStringUtils.decodeQuotedString(tag) 66 | } else { 67 | eTag = null 68 | weak = false 69 | } 70 | } 71 | 72 | override fun equals(other: Any?): Boolean { 73 | if (other !is GetETag) 74 | return false 75 | return eTag == other.eTag && weak == other.weak 76 | } 77 | 78 | override fun hashCode(): Int { 79 | return eTag.hashCode() xor weak.hashCode() 80 | } 81 | 82 | 83 | object Factory: PropertyFactory { 84 | 85 | override fun getName() = NAME 86 | 87 | override fun create(parser: XmlPullParser): GetETag = 88 | GetETag(XmlReader(parser).readText()) 89 | 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /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 | companion object { 24 | 25 | @JvmField 26 | val NAME = Property.Name(NS_WEBDAV, "getlastmodified") 27 | 28 | } 29 | 30 | 31 | object Factory: PropertyFactory { 32 | 33 | override fun getName() = NAME 34 | 35 | override fun create(parser: XmlPullParser): GetLastModified { 36 | // 37 | return GetLastModified( 38 | XmlReader(parser).readHttpDate() 39 | ) 40 | } 41 | 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /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 14 | import org.xmlpull.v1.XmlPullParser 15 | 16 | class GroupMembership( 17 | override val hrefs: List 18 | ): HrefListProperty(hrefs) { 19 | 20 | companion object { 21 | 22 | @JvmField 23 | val NAME = Property.Name(NS_WEBDAV, "group-membership") 24 | 25 | } 26 | 27 | 28 | object Factory: HrefListProperty.Factory() { 29 | 30 | override fun getName() = NAME 31 | 32 | override fun create(parser: XmlPullParser) = create(parser, ::GroupMembership) 33 | 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/webdav/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.webdav 12 | 13 | import at.bitfire.dav4jvm.DavResource 14 | import at.bitfire.dav4jvm.Property 15 | import at.bitfire.dav4jvm.PropertyFactory 16 | import at.bitfire.dav4jvm.XmlReader 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 | val href get() = hrefs.firstOrNull() 29 | 30 | 31 | abstract class Factory : PropertyFactory { 32 | 33 | @Deprecated("hrefs is no longer mutable.", level = DeprecationLevel.ERROR) 34 | fun create(parser: XmlPullParser, list: HrefListProperty): HrefListProperty { 35 | val hrefs = list.hrefs.toMutableList() 36 | XmlReader(parser).readTextPropertyList(DavResource.HREF, hrefs) 37 | return list 38 | } 39 | 40 | fun create( 41 | parser: XmlPullParser, 42 | constructor: (hrefs: List 43 | ) -> PropertyType): PropertyType { 44 | val hrefs = mutableListOf() 45 | XmlReader(parser).readTextPropertyList(DavResource.HREF, hrefs) 46 | return constructor(hrefs) 47 | } 48 | 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /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 org.xmlpull.v1.XmlPullParser 15 | 16 | data class Owner( 17 | override val hrefs: List 18 | ): HrefListProperty(hrefs) { 19 | 20 | companion object { 21 | 22 | @JvmField 23 | val NAME = Property.Name(NS_WEBDAV, "owner") 24 | 25 | } 26 | 27 | 28 | object Factory: HrefListProperty.Factory() { 29 | 30 | override fun getName() = NAME 31 | 32 | override fun create(parser: XmlPullParser) = create(parser, ::Owner) 33 | 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /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 | companion object { 23 | 24 | @JvmField 25 | val NAME = Property.Name(NS_WEBDAV, "quota-available-bytes") 26 | 27 | } 28 | 29 | object Factory: PropertyFactory { 30 | 31 | override fun getName() = NAME 32 | 33 | override fun create(parser: XmlPullParser) = 34 | QuotaAvailableBytes(XmlReader(parser).readLong()) 35 | 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /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 | companion object { 23 | 24 | @JvmField 25 | val NAME = Property.Name(NS_WEBDAV, "quota-used-bytes") 26 | 27 | } 28 | 29 | object Factory: PropertyFactory { 30 | 31 | override fun getName() = NAME 32 | 33 | override fun create(parser: XmlPullParser) = 34 | QuotaUsedBytes(XmlReader(parser).readLong()) 35 | 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /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.NS_CALDAV 16 | import at.bitfire.dav4jvm.property.caldav.NS_CALENDARSERVER 17 | import at.bitfire.dav4jvm.property.carddav.NS_CARDDAV 18 | import org.xmlpull.v1.XmlPullParser 19 | 20 | class ResourceType( 21 | val types: Set = emptySet() 22 | ): Property { 23 | 24 | companion object { 25 | 26 | @JvmField 27 | val NAME = Property.Name(NS_WEBDAV, "resourcetype") 28 | 29 | val COLLECTION = Property.Name(NS_WEBDAV, "collection") // WebDAV 30 | val PRINCIPAL = Property.Name(NS_WEBDAV, "principal") // WebDAV ACL 31 | val ADDRESSBOOK = Property.Name(NS_CARDDAV, "addressbook") // CardDAV 32 | val CALENDAR = Property.Name(NS_CALDAV, "calendar") // CalDAV 33 | 34 | // CalendarServer extensions 35 | val CALENDAR_PROXY_READ = Property.Name(NS_CALENDARSERVER, "calendar-proxy-read") // CalDAV Proxy 36 | val CALENDAR_PROXY_WRITE = Property.Name(NS_CALENDARSERVER, "calendar-proxy-write") // CalDAV Proxy 37 | val SUBSCRIBED = Property.Name(NS_CALENDARSERVER, "subscribed") 38 | 39 | } 40 | 41 | 42 | object Factory: PropertyFactory { 43 | 44 | override fun getName() = NAME 45 | 46 | override fun create(parser: XmlPullParser): ResourceType { 47 | val types = mutableSetOf() 48 | 49 | val depth = parser.depth 50 | var eventType = parser.eventType 51 | while (!(eventType == XmlPullParser.END_TAG && parser.depth == depth)) { 52 | if (eventType == XmlPullParser.START_TAG && parser.depth == depth + 1) { 53 | // use static objects to allow types.contains() 54 | var typeName = Property.Name(parser.namespace, parser.name) 55 | when (typeName) { // if equals(), replace by our instance 56 | COLLECTION -> typeName = COLLECTION 57 | PRINCIPAL -> typeName = PRINCIPAL 58 | ADDRESSBOOK -> typeName = ADDRESSBOOK 59 | CALENDAR -> typeName = CALENDAR 60 | CALENDAR_PROXY_READ -> typeName = CALENDAR_PROXY_READ 61 | CALENDAR_PROXY_WRITE -> typeName = CALENDAR_PROXY_WRITE 62 | SUBSCRIBED -> typeName = SUBSCRIBED 63 | } 64 | types.add(typeName) 65 | } 66 | eventType = parser.next() 67 | } 68 | assert(parser.depth == depth) 69 | 70 | return ResourceType(types) 71 | } 72 | 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /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 | companion object { 23 | 24 | @JvmField 25 | val NAME = Property.Name(NS_WEBDAV, "supported-report-set") 26 | 27 | val SUPPORTED_REPORT = Property.Name(NS_WEBDAV, "supported-report") 28 | val REPORT = Property.Name(NS_WEBDAV, "report") 29 | 30 | const val SYNC_COLLECTION = "DAV:sync-collection" // collection synchronization (RFC 6578) 31 | 32 | } 33 | 34 | 35 | object Factory: PropertyFactory { 36 | 37 | override fun getName() = NAME 38 | 39 | override fun create(parser: XmlPullParser): SupportedReportSet { 40 | /* 41 | 42 | 43 | */ 44 | 45 | val reports = mutableSetOf() 46 | XmlReader(parser).processTag(SUPPORTED_REPORT) { 47 | processTag(REPORT) { 48 | parser.nextTag() 49 | if (parser.eventType == XmlPullParser.TEXT) 50 | reports += parser.text 51 | else if (parser.eventType == XmlPullParser.START_TAG) 52 | reports += "${parser.namespace}${parser.name}" 53 | } 54 | } 55 | return SupportedReportSet(reports) 56 | } 57 | 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /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 | companion object { 27 | 28 | @JvmField 29 | val NAME = Property.Name(NS_WEBDAV, "sync-level") 30 | 31 | } 32 | 33 | 34 | object Factory: PropertyFactory { 35 | 36 | override fun getName() = NAME 37 | 38 | override fun create(parser: XmlPullParser): SyncLevel { 39 | val text = XmlReader(parser).readText() 40 | val level = if (text == "infinite") Int.MAX_VALUE else text?.toIntOrNull() 41 | return SyncLevel(level) 42 | } 43 | 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /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 | companion object { 23 | 24 | @JvmField 25 | val NAME = Property.Name(NS_WEBDAV, "sync-token") 26 | 27 | } 28 | 29 | 30 | object Factory: PropertyFactory { 31 | 32 | override fun getName() = NAME 33 | 34 | override fun create(parser: XmlPullParser) = 35 | // 36 | SyncToken(XmlReader(parser).readText()) 37 | 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/kotlin/at/bitfire/dav4jvm/property/webdav/namespace.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 | /** 14 | * WebDAV XML namespace (defined in RFC 4918) 15 | */ 16 | const val NS_WEBDAV = "DAV:" 17 | -------------------------------------------------------------------------------- /src/test/kotlin/at/bitfire/dav4jvm/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 12 | 13 | import okhttp3.OkHttpClient 14 | import okhttp3.mockwebserver.MockResponse 15 | import okhttp3.mockwebserver.MockWebServer 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.shutdown() 37 | } 38 | 39 | 40 | @Test 41 | fun calendarQuery_formatStartEnd() { 42 | val cal = DavCalendar(httpClient, mockServer.url("/")) 43 | mockServer.enqueue(MockResponse().setResponseCode(207).setBody("")) 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.readUtf8()) 61 | } 62 | 63 | } -------------------------------------------------------------------------------- /src/test/kotlin/at/bitfire/dav4jvm/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 12 | 13 | import org.junit.Assert.assertTrue 14 | import org.junit.Test 15 | 16 | class ErrorTest { 17 | 18 | @Test 19 | fun testEquals() { 20 | val errors = listOf(Error(Property.Name("DAV:", "valid-sync-token"))) 21 | assertTrue(errors.contains(Error.VALID_SYNC_TOKEN)) 22 | } 23 | 24 | } -------------------------------------------------------------------------------- /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 okhttp3.HttpUrl.Companion.toHttpUrl 14 | import org.junit.Assert.assertEquals 15 | import org.junit.Assert.assertNull 16 | import org.junit.Test 17 | import java.time.Instant 18 | import java.time.LocalDate 19 | import java.time.LocalTime 20 | import java.time.ZoneOffset 21 | import java.time.ZonedDateTime 22 | import java.time.format.DateTimeFormatter 23 | import java.util.Calendar 24 | import java.util.Locale 25 | import java.util.TimeZone 26 | import java.util.logging.Logger 27 | 28 | class HttpUtilsTest { 29 | 30 | @Test 31 | fun fileName() { 32 | assertEquals("", HttpUtils.fileName("https://example.com".toHttpUrl())) 33 | assertEquals("", HttpUtils.fileName("https://example.com/".toHttpUrl())) 34 | assertEquals("file1", HttpUtils.fileName("https://example.com/file1".toHttpUrl())) 35 | assertEquals("dir1", HttpUtils.fileName("https://example.com/dir1/".toHttpUrl())) 36 | assertEquals("file2", HttpUtils.fileName("https://example.com/dir1/file2".toHttpUrl())) 37 | assertEquals("dir2", HttpUtils.fileName("https://example.com/dir1/dir2/".toHttpUrl())) 38 | } 39 | 40 | @Test 41 | fun formatDate() { 42 | val cal = Calendar.getInstance(TimeZone.getTimeZone("GMT")) 43 | cal.set(2023, 4, 11, 17, 26, 35) 44 | cal.timeZone = TimeZone.getTimeZone("UTC") 45 | assertEquals("Sun, 06 Nov 1994 08:49:37 GMT", HttpUtils.formatDate( 46 | ZonedDateTime.of( 47 | LocalDate.of(1994, 11, 6), 48 | LocalTime.of(8, 49, 37), 49 | ZoneOffset.UTC 50 | ).toInstant() 51 | )) 52 | } 53 | 54 | 55 | @Test 56 | fun parseDate_IMF_FixDate() { 57 | // RFC 7231 IMF-fixdate (preferred format) 58 | assertEquals(Instant.ofEpochSecond(784111777), HttpUtils.parseDate("Sun, 06 Nov 1994 08:49:37 GMT")) 59 | } 60 | 61 | @Test 62 | fun parseDate_RFC850_1994() { 63 | // obsolete RFC 850 format – fails when run after year 2000 because 06 Nov 2094 (!) is not a Sunday 64 | assertNull(HttpUtils.parseDate("Sun, 06-Nov-94 08:49:37 GMT")) 65 | } 66 | 67 | @Test 68 | fun parseDate_RFC850_2004_CEST() { 69 | // obsolete RFC 850 format with European time zone 70 | assertEquals(Instant.ofEpochSecond(1689317377), HttpUtils.parseDate("Friday, 14-Jul-23 08:49:37 CEST")) 71 | } 72 | 73 | @Test 74 | fun parseDate_RFC850_2004_GMT() { 75 | // obsolete RFC 850 format 76 | assertEquals(Instant.ofEpochSecond(1689324577), HttpUtils.parseDate("Friday, 14-Jul-23 08:49:37 GMT")) 77 | } 78 | 79 | @Test 80 | fun parseDate_ANSI_C() { 81 | // ANSI C's asctime() format 82 | val logger = Logger.getLogger(javaClass.name) 83 | logger.info("Expected date: " + DateTimeFormatter.ofPattern("EEE MMM ppd HH:mm:ss yyyy", Locale.US).format(ZonedDateTime.now())) 84 | 85 | assertEquals(Instant.ofEpochSecond(784111777), HttpUtils.parseDate("Sun Nov 6 08:49:37 1994")) 86 | } 87 | 88 | } -------------------------------------------------------------------------------- /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.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.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/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 12 | 13 | import org.junit.Assert.assertEquals 14 | import org.junit.Test 15 | 16 | class QuotedStringUtilsTest { 17 | 18 | @Test 19 | fun testAsQuotedString() { 20 | assertEquals("\"\"", QuotedStringUtils.asQuotedString("")) 21 | assertEquals("\"\\\"\"", QuotedStringUtils.asQuotedString("\"")) 22 | assertEquals("\"\\\\\"", QuotedStringUtils.asQuotedString("\\")) 23 | } 24 | 25 | fun testDecodeQuotedString() { 26 | assertEquals("\"", QuotedStringUtils.decodeQuotedString("\"")) 27 | assertEquals("\\", QuotedStringUtils.decodeQuotedString("\"\\\"")) 28 | assertEquals("\"test", QuotedStringUtils.decodeQuotedString("\"test")) 29 | assertEquals("test", QuotedStringUtils.decodeQuotedString("test")) 30 | assertEquals("", QuotedStringUtils.decodeQuotedString("\"\"")) 31 | assertEquals("test", QuotedStringUtils.decodeQuotedString("\"test\"")) 32 | assertEquals("test\\", QuotedStringUtils.decodeQuotedString("\"test\\\"")) 33 | assertEquals("test", QuotedStringUtils.decodeQuotedString("\"t\\e\\st\"")) 34 | assertEquals("12\"34", QuotedStringUtils.decodeQuotedString("\"12\\\"34\"")) 35 | assertEquals("1234\"", QuotedStringUtils.decodeQuotedString("\"1234\\\"\"")) 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/test/kotlin/at/bitfire/dav4jvm/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 12 | 13 | import okhttp3.HttpUrl.Companion.toHttpUrl 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("http://host/resource".toHttpUrl(), UrlUtils.omitTrailingSlash("http://host/resource".toHttpUrl())) 41 | assertEquals("http://host/resource".toHttpUrl(), UrlUtils.omitTrailingSlash("http://host/resource/".toHttpUrl())) 42 | } 43 | 44 | @Test 45 | fun testWithTrailingSlash() { 46 | assertEquals("http://host/resource/".toHttpUrl(), UrlUtils.withTrailingSlash("http://host/resource".toHttpUrl())) 47 | assertEquals("http://host/resource/".toHttpUrl(), UrlUtils.withTrailingSlash("http://host/resource/".toHttpUrl())) 48 | } 49 | 50 | 51 | @Test 52 | fun testHttpUrl_EqualsForWebDAV() { 53 | assertTrue("http://host/resource".toHttpUrl().equalsForWebDAV("http://host/resource".toHttpUrl())) 54 | assertTrue("http://host:80/resource".toHttpUrl().equalsForWebDAV("http://host/resource".toHttpUrl())) 55 | assertTrue("https://HOST:443/resource".toHttpUrl().equalsForWebDAV("https://host/resource".toHttpUrl())) 56 | assertTrue("https://host:443/my@dav/".toHttpUrl().equalsForWebDAV("https://host/my%40dav/".toHttpUrl())) 57 | assertTrue("http://host/resource".toHttpUrl().equalsForWebDAV("http://host/resource#frag1".toHttpUrl())) 58 | 59 | assertFalse("http://host/resource".toHttpUrl().equalsForWebDAV("http://host/resource/".toHttpUrl())) 60 | assertFalse("http://host/resource".toHttpUrl().equalsForWebDAV("http://host:81/resource".toHttpUrl())) 61 | 62 | assertTrue("https://www.example.com/folder/[X]Y!.txt".toHttpUrl().equalsForWebDAV("https://www.example.com/folder/[X]Y!.txt".toHttpUrl())) 63 | assertTrue("https://www.example.com/folder/%5BX%5DY!.txt".toHttpUrl().equalsForWebDAV("https://www.example.com/folder/[X]Y!.txt".toHttpUrl())) 64 | assertTrue("https://www.example.com/folder/%5bX%5dY%21.txt".toHttpUrl().equalsForWebDAV("https://www.example.com/folder/[X]Y!.txt".toHttpUrl())) 65 | } 66 | 67 | } -------------------------------------------------------------------------------- /src/test/kotlin/at/bitfire/dav4jvm/XmlReaderTest.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 okhttp3.MediaType 14 | import okhttp3.MediaType.Companion.toMediaType 15 | import org.junit.Assert.assertEquals 16 | import org.junit.Assert.assertNull 17 | import org.junit.Assert.assertTrue 18 | import org.junit.Test 19 | import org.xmlpull.v1.XmlPullParser 20 | import java.io.StringReader 21 | import java.time.Instant 22 | 23 | class XmlReaderTest { 24 | 25 | @Test 26 | fun testProcessTag_Root() { 27 | val parser = XmlUtils.newPullParser() 28 | parser.setInput(StringReader("")) 29 | // now on START_DOCUMENT [0] 30 | 31 | var processed = false 32 | XmlReader(parser).processTag(Property.Name("", "test")) { 33 | processed = true 34 | } 35 | assertTrue(processed) 36 | } 37 | 38 | @Test 39 | fun testProcessTag_Depth1() { 40 | val parser = XmlUtils.newPullParser() 41 | parser.setInput(StringReader("")) 42 | parser.next() // now on START_TAG 43 | 44 | var processed = false 45 | XmlReader(parser).processTag(Property.Name("", "test")) { 46 | processed = true 47 | } 48 | assertTrue(processed) 49 | } 50 | 51 | 52 | @Test 53 | fun testReadText() { 54 | val parser = XmlUtils.newPullParser() 55 | parser.setInput(StringReader("Test 1Test 2")) 56 | parser.next() 57 | parser.next() // now on START_TAG 58 | val reader = XmlReader(parser) 59 | 60 | assertEquals("Test 1", reader.readText()) 61 | assertEquals(XmlPullParser.END_TAG, parser.eventType) 62 | parser.next() 63 | 64 | assertEquals("Test 2", reader.readText()) 65 | assertEquals(XmlPullParser.END_TAG, parser.eventType) 66 | } 67 | 68 | @Test 69 | fun testReadText_CDATA() { 70 | val parser = XmlUtils.newPullParser() 71 | parser.setInput(StringReader("Test 2]]>")) 72 | parser.next() // now on START_TAG 73 | 74 | assertEquals("Test 1Test 2", XmlReader(parser).readText()) 75 | assertEquals(XmlPullParser.END_TAG, parser.eventType) 76 | } 77 | 78 | @Test 79 | fun testReadText_PropertyRoot() { 80 | val parser = XmlUtils.newPullParser() 81 | parser.setInput(StringReader("Test 1Test 2")) 82 | parser.next() // now on START_TAG 83 | 84 | val entries = mutableListOf() 85 | XmlReader(parser).readTextPropertyList(Property.Name("", "entry"), entries) 86 | assertEquals("Test 1", entries[0]) 87 | assertEquals("Test 2", entries[1]) 88 | 89 | parser.next() // END_TAG 90 | assertEquals(XmlPullParser.END_DOCUMENT, parser.eventType) 91 | } 92 | 93 | 94 | @Test 95 | fun testReadTextPropertyList_Depth1() { 96 | val parser = XmlUtils.newPullParser() 97 | parser.setInput(StringReader("Test 1Test 2")) 98 | parser.next() // now on START_TAG [1] 99 | 100 | val entries = mutableListOf() 101 | XmlReader(parser).readTextPropertyList(Property.Name("", "entry"), entries) 102 | assertEquals("Test 1", entries[0]) 103 | assertEquals("Test 2", entries[1]) 104 | assertEquals(XmlPullParser.END_TAG, parser.eventType) 105 | assertEquals("test", parser.name) 106 | } 107 | 108 | 109 | @Test 110 | fun testReadLong() { 111 | val parser = XmlUtils.newPullParser() 112 | parser.setInput(StringReader("12a")) 113 | parser.next() 114 | parser.next() // now on START_TAG 115 | val reader = XmlReader(parser) 116 | 117 | assertEquals(1L, reader.readLong()) 118 | assertEquals(XmlPullParser.END_TAG, parser.eventType) 119 | parser.next() 120 | 121 | assertEquals(2L, reader.readLong()) 122 | assertEquals(XmlPullParser.END_TAG, parser.eventType) 123 | parser.next() 124 | 125 | assertNull(reader.readLong()) 126 | assertEquals(XmlPullParser.END_TAG, parser.eventType) 127 | } 128 | 129 | 130 | @Test 131 | fun testReadHttpDate() { 132 | val parser = XmlUtils.newPullParser() 133 | parser.setInput(StringReader("Sun, 06 Nov 1994 08:49:37 GMTSun, 06 Nov 1994 08:49:37 GMTinvalid")) 134 | parser.next() 135 | parser.next() // now on START_TAG 136 | val reader = XmlReader(parser) 137 | 138 | assertEquals(Instant.ofEpochSecond(784111777), reader.readHttpDate()) 139 | assertEquals(XmlPullParser.END_TAG, parser.eventType) 140 | parser.next() 141 | 142 | assertEquals(Instant.ofEpochSecond(784111777), reader.readHttpDate()) 143 | assertEquals(XmlPullParser.END_TAG, parser.eventType) 144 | parser.next() 145 | 146 | assertNull(reader.readHttpDate()) 147 | assertEquals(XmlPullParser.END_TAG, parser.eventType) 148 | } 149 | 150 | 151 | @Test 152 | fun testReadContentTypes() { 153 | val parser = XmlUtils.newPullParser() 154 | parser.setInput(StringReader("text{}")) 155 | parser.next() 156 | val reader = XmlReader(parser) 157 | 158 | val types = mutableListOf() 159 | reader.readContentTypes(Property.Name("", "test"), types::add) 160 | assertEquals(2, types.size) 161 | assertEquals("text/plain".toMediaType(), types[0]) 162 | assertEquals("application/json".toMediaType(), types[1]) 163 | } 164 | 165 | } -------------------------------------------------------------------------------- /src/test/kotlin/at/bitfire/dav4jvm/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.exception 12 | 13 | import at.bitfire.dav4jvm.DavResource 14 | import at.bitfire.dav4jvm.Property 15 | import at.bitfire.dav4jvm.property.webdav.NS_WEBDAV 16 | import at.bitfire.dav4jvm.property.webdav.ResourceType 17 | import okhttp3.MediaType.Companion.toMediaType 18 | import okhttp3.OkHttpClient 19 | import okhttp3.Protocol 20 | import okhttp3.Request 21 | import okhttp3.RequestBody.Companion.toRequestBody 22 | import okhttp3.Response 23 | import okhttp3.mockwebserver.MockResponse 24 | import okhttp3.mockwebserver.MockWebServer 25 | import org.junit.After 26 | import org.junit.Assert.assertEquals 27 | import org.junit.Assert.assertNull 28 | import org.junit.Assert.assertTrue 29 | import org.junit.Assert.fail 30 | import org.junit.Before 31 | import org.junit.Test 32 | import java.io.ByteArrayInputStream 33 | import java.io.ByteArrayOutputStream 34 | import java.io.ObjectInputStream 35 | import java.io.ObjectOutputStream 36 | 37 | class DavExceptionTest { 38 | 39 | private val httpClient = OkHttpClient.Builder() 40 | .followRedirects(false) 41 | .build() 42 | private val mockServer = MockWebServer() 43 | private fun sampleUrl() = mockServer.url("/dav/") 44 | 45 | @Before 46 | fun startServer() = mockServer.start() 47 | 48 | @After 49 | fun stopServer() = mockServer.shutdown() 50 | 51 | 52 | /** 53 | * Test truncation of a too large plain text request in [DavException]. 54 | */ 55 | @Test 56 | fun testRequestLargeTextError() { 57 | val url = sampleUrl() 58 | val dav = DavResource(httpClient, url) 59 | 60 | val builder = StringBuilder() 61 | builder.append(CharArray(DavException.MAX_EXCERPT_SIZE+100) { '*' }) 62 | val body = builder.toString() 63 | 64 | val e = DavException("Error with large request body", null, Response.Builder() 65 | .request(Request.Builder() 66 | .url("http://example.com") 67 | .post(body.toRequestBody("text/plain".toMediaType())) 68 | .build()) 69 | .protocol(Protocol.HTTP_1_1) 70 | .code(204) 71 | .message("No Content") 72 | .build()) 73 | 74 | assertTrue(e.errors.isEmpty()) 75 | assertEquals( 76 | body.substring(0, DavException.MAX_EXCERPT_SIZE), 77 | e.requestBody 78 | ) 79 | } 80 | 81 | /** 82 | * Test a large HTML response which has a multi-octet UTF-8 character 83 | * exactly at the cut-off position. 84 | */ 85 | @Test 86 | fun testResponseLargeTextError() { 87 | val url = sampleUrl() 88 | val dav = DavResource(httpClient, url) 89 | 90 | val builder = StringBuilder() 91 | builder.append(CharArray(DavException.MAX_EXCERPT_SIZE-1) { '*' }) 92 | builder.append("\u03C0") // Pi 93 | val body = builder.toString() 94 | 95 | mockServer.enqueue(MockResponse() 96 | .setResponseCode(404) 97 | .setHeader("Content-Type", "text/html") 98 | .setBody(body)) 99 | try { 100 | dav.propfind(0, ResourceType.NAME) { _, _ -> } 101 | fail("Expected HttpException") 102 | } catch (e: HttpException) { 103 | assertEquals(e.code, 404) 104 | assertTrue(e.errors.isEmpty()) 105 | assertEquals( 106 | body.substring(0, DavException.MAX_EXCERPT_SIZE-1), 107 | e.responseBody!!.substring(0, DavException.MAX_EXCERPT_SIZE-1) 108 | ) 109 | } 110 | } 111 | 112 | @Test 113 | fun testResponseNonTextError() { 114 | val url = sampleUrl() 115 | val dav = DavResource(httpClient, url) 116 | 117 | mockServer.enqueue(MockResponse() 118 | .setResponseCode(403) 119 | .setHeader("Content-Type", "application/octet-stream") 120 | .setBody("12345")) 121 | try { 122 | dav.propfind(0, ResourceType.NAME) { _, _ -> } 123 | fail("Expected HttpException") 124 | } catch (e: HttpException) { 125 | assertEquals(e.code, 403) 126 | assertTrue(e.errors.isEmpty()) 127 | assertNull(e.responseBody) 128 | } 129 | } 130 | 131 | @Test 132 | fun testSerialization() { 133 | val url = sampleUrl() 134 | val dav = DavResource(httpClient, url) 135 | 136 | mockServer.enqueue(MockResponse() 137 | .setResponseCode(500) 138 | .setHeader("Content-Type", "text/plain") 139 | .setBody("12345")) 140 | try { 141 | dav.propfind(0, ResourceType.NAME) { _, _ -> } 142 | fail("Expected DavException") 143 | } catch (e: DavException) { 144 | val baos = ByteArrayOutputStream() 145 | val oos = ObjectOutputStream(baos) 146 | oos.writeObject(e) 147 | 148 | val ois = ObjectInputStream(ByteArrayInputStream(baos.toByteArray())) 149 | val e2 = ois.readObject() as HttpException 150 | assertEquals(500, e2.code) 151 | assertTrue(e2.responseBody!!.contains("12345")) 152 | } 153 | } 154 | 155 | /** 156 | * Test precondition XML element (sample from RFC 4918 16) 157 | */ 158 | @Test 159 | fun testXmlError() { 160 | val url = sampleUrl() 161 | val dav = DavResource(httpClient, url) 162 | 163 | val body = "\n" + 164 | "\n" + 165 | " \n" + 166 | " /workspace/webdav/\n" + 167 | " \n" + 168 | "\n" 169 | mockServer.enqueue(MockResponse() 170 | .setResponseCode(423) 171 | .setHeader("Content-Type", "application/xml; charset=\"utf-8\"") 172 | .setBody(body)) 173 | try { 174 | dav.propfind(0, ResourceType.NAME) { _, _ -> } 175 | fail("Expected HttpException") 176 | } catch (e: HttpException) { 177 | assertEquals(e.code, 423) 178 | assertTrue(e.errors.any { it.name == Property.Name(NS_WEBDAV, "lock-token-submitted") }) 179 | assertEquals(body, e.responseBody) 180 | } 181 | } 182 | 183 | } -------------------------------------------------------------------------------- /src/test/kotlin/at/bitfire/dav4jvm/exception/HttpExceptionTest.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.exception 12 | 13 | import okhttp3.MediaType.Companion.toMediaType 14 | import okhttp3.Protocol 15 | import okhttp3.Request 16 | import okhttp3.RequestBody.Companion.toRequestBody 17 | import okhttp3.Response 18 | import okhttp3.ResponseBody.Companion.toResponseBody 19 | import org.junit.Assert.assertTrue 20 | import org.junit.Test 21 | 22 | class HttpExceptionTest { 23 | 24 | private val responseMessage = "Unknown error" 25 | 26 | @Test 27 | fun testHttpFormatting() { 28 | val request = Request.Builder() 29 | .post("REQUEST\nBODY".toRequestBody("text/something".toMediaType())) 30 | .url("http://example.com") 31 | .build() 32 | 33 | val response = Response.Builder() 34 | .request(request) 35 | .protocol(Protocol.HTTP_1_1) 36 | .code(500) 37 | .message(responseMessage) 38 | .body("SERVER\r\nRESPONSE".toResponseBody("text/something-other".toMediaType())) 39 | .build() 40 | val e = HttpException(response) 41 | assertTrue(e.message!!.contains("500")) 42 | assertTrue(e.message!!.contains(responseMessage)) 43 | assertTrue(e.requestBody!!.contains("REQUEST\nBODY")) 44 | assertTrue(e.responseBody!!.contains("SERVER\r\nRESPONSE")) 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/test/kotlin/at/bitfire/dav4jvm/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.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(Request.Builder() 27 | .url("http://www.example.com") 28 | .get() 29 | .build()) 30 | .protocol(Protocol.HTTP_1_1) 31 | .code(503).message("Try later") 32 | .build() 33 | 34 | @Test 35 | fun testRetryAfter_NoTime() { 36 | val e = ServiceUnavailableException(response503) 37 | assertNull(e.retryAfter) 38 | } 39 | 40 | @Test 41 | fun testRetryAfter_Seconds() { 42 | val response = response503.newBuilder() 43 | .header("Retry-After", "120") 44 | .build() 45 | val e = ServiceUnavailableException(response) 46 | assertNotNull(e.retryAfter) 47 | assertTrue(withinTimeRange(e.retryAfter!!, 120)) 48 | } 49 | 50 | @Test 51 | fun testRetryAfter_Date() { 52 | val after30min = Instant.now().plusSeconds(30*60) 53 | val response = response503.newBuilder() 54 | .header("Retry-After", HttpUtils.formatDate(after30min)) 55 | .build() 56 | val e = ServiceUnavailableException(response) 57 | assertNotNull(e.retryAfter) 58 | assertTrue(withinTimeRange(e.retryAfter!!, 30*60)) 59 | } 60 | 61 | 62 | private fun withinTimeRange(d: Instant, seconds: Long) = 63 | d.isBefore( 64 | Instant.now() 65 | .plusSeconds(seconds) 66 | .plusSeconds(5) // tolerance for test running 67 | ) 68 | 69 | } -------------------------------------------------------------------------------- /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/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/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.Test 17 | 18 | class OwnerTest: PropertyTest() { 19 | 20 | @Test 21 | fun testOwner_PlainText() { 22 | val results = parseProperty("https://example.com") 23 | val owner = results.first() as Owner 24 | assertNull(owner.href) 25 | } 26 | 27 | @Test 28 | fun testOwner_PlainTextAndHref() { 29 | val results = parseProperty("Principal Name. mailto:owner@example.com (test)") 30 | val owner = results.first() as Owner 31 | assertEquals("mailto:owner@example.com", owner.href) 32 | } 33 | 34 | @Test 35 | fun testOwner_Href() { 36 | val results = parseProperty("https://example.com") 37 | val owner = results.first() as Owner 38 | assertEquals("https://example.com", owner.href) 39 | } 40 | 41 | } -------------------------------------------------------------------------------- /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/test/kotlin/at/bitfire/dav4jvm/property/push/WebPushTest.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.PropertyTest 14 | import org.junit.Assert.assertEquals 15 | import org.junit.Assert.assertNotNull 16 | import org.junit.Assert.assertTrue 17 | import org.junit.Test 18 | import java.time.Instant 19 | 20 | class WebPushTest: PropertyTest() { 21 | 22 | @Test 23 | fun testPushRegister() { 24 | val results = parseProperty( 25 | "" + 26 | " " + 27 | " \n" + 28 | " https://up.example.net/yohd4yai5Phiz1wi\n" + 29 | " BCVxsr7N_eNgVRqvHtD0zTZsEc6-VV-JvLexhqUzORcxaOzi6-AYWXvTBHm4bjyPjs7Vd8pZGH6SRpkNtoIAiw4\n" + 30 | " BTBZMqHH6r4Tts7J_aSIgg" + 31 | " \n" + 32 | " " + 33 | " Wed, 20 Dec 2023 10:03:31 GMT" + 34 | "") 35 | val result = results.first() as PushRegister 36 | assertEquals(Instant.ofEpochSecond(1703066611), result.expires) 37 | val subscription = result.subscription?.webPushSubscription 38 | assertEquals("https://up.example.net/yohd4yai5Phiz1wi", subscription?.pushResource?.uri?.toString()) 39 | assertEquals("BTBZMqHH6r4Tts7J_aSIgg", subscription?.authSecret?.secret) 40 | 41 | val publicKey = subscription?.subscriptionPublicKey 42 | assertEquals("p256dh", publicKey?.type) 43 | assertEquals("BCVxsr7N_eNgVRqvHtD0zTZsEc6-VV-JvLexhqUzORcxaOzi6-AYWXvTBHm4bjyPjs7Vd8pZGH6SRpkNtoIAiw4", publicKey?.key) 44 | } 45 | 46 | @Test 47 | fun testServiceDetection() { 48 | val results = parseProperty( 49 | "" + 50 | " " + 51 | " " + 52 | " BCVxsr7N_eNgVRqvHtD0zTZsEc6-VV-JvLexhqUzORcxaOzi6-AYWXvTBHm4bjyPjs7Vd8pZGH6SRpkNtoIAiw4" + 53 | " " + 54 | "" + 55 | "SomeTopic") 56 | val result = results.first() as PushTransports 57 | 58 | assertEquals(setOf( 59 | // something else is ignored because it's not a recognized transport 60 | WebPush( 61 | VapidPublicKey( 62 | type = "p256dh", 63 | key = "BCVxsr7N_eNgVRqvHtD0zTZsEc6-VV-JvLexhqUzORcxaOzi6-AYWXvTBHm4bjyPjs7Vd8pZGH6SRpkNtoIAiw4" 64 | ) 65 | ) 66 | ), result.transports) 67 | assertTrue(result.hasWebPush()) 68 | 69 | assertEquals("SomeTopic", (results[1] as Topic).topic) 70 | } 71 | 72 | @Test 73 | fun testMessage() { 74 | val results = parseProperty( 75 | """ 76 | 77 | O7M1nQ7cKkKTKsoS_j6Z3w 78 | 79 | http://example.com/sync/10 80 | 81 | 82 | 83 | """.trimIndent() 84 | ) 85 | val message = results.first() as PushMessage 86 | 87 | val topic = message.topic 88 | assertNotNull(topic) 89 | assertEquals("O7M1nQ7cKkKTKsoS_j6Z3w", topic?.topic) 90 | 91 | val contentUpdate = message.contentUpdate 92 | assertNotNull(contentUpdate) 93 | val syncToken = contentUpdate?.syncToken 94 | assertNotNull(syncToken) 95 | assertEquals("http://example.com/sync/10", syncToken?.token) 96 | 97 | val propertyUpdate = message.propertyUpdate 98 | assertNotNull(propertyUpdate) 99 | } 100 | 101 | } --------------------------------------------------------------------------------