A WebDAV (RFC 4918) client library written in Kotlin Multiplatform, sharing 100% of its protocol logic between Android, iOS, JVM and the browser (Kotlin/JS and Kotlin/Wasm). It is a library (no UI) meant to be embedded in other apps and projects.
- Core file operations: list directories (
PROPFIND), download (GET), upload (PUT), delete (DELETE), create collections (MKCOL), move/copy (MOVE/COPY), read and modify properties (PROPPATCH). - Locking: exclusive and shared write locks (
LOCK/UNLOCK) with lock refresh, lock discovery (getLocks),withLockauto-release, and lock tokens submitted via theIfheader on every write operation (RFC 4918 §7.3). - Conditional requests:
If-Match/If-None-Match(ETag) on uploads, downloads, moves, copies and deletes for optimistic concurrency and cache validation. - Advanced: byte-range downloads (resume support), recursive listing with bounded concurrency, HTTP redirects handled manually (bodies and methods survive 3xx).
- Capability probing:
capabilities()parses both theAllowand theDAVcompliance headers;execute()exposes the underlying request path for WebDAV extension methods (REPORT, SEARCH, ...). - Authentication: HTTP Basic, HTTP Digest (RFC 7616), and Bearer token (OAuth2) through Ktor's Auth plugin.
- Streaming: uploads and downloads are streamed; large files never sit in memory.
Progress callbacks report
(bytesTransferred, totalBytes). - Fault tolerant parsing: unknown properties/namespaces are preserved, malformed entries never break a whole listing.
| Target | Engine | Artifact |
|---|---|---|
| Android (minSdk 24) | OkHttp | com.phylaris.webdav.client AAR |
| iOS (arm64, simulator) | Darwin (NSURLSession) | WebDavClient XCFramework |
| JVM | CIO | JVM jar |
| Browser JS / Wasm | Js (browser fetch) | JS library / Wasm library |
The JVM target also serves as the test host: ./gradlew :jvmTest runs the shared
suite (XML parsing golden cases + protocol behavior via Ktor's MockEngine). The
same suite runs in the browser: ./gradlew :jsBrowserTest :wasmJsBrowserTest
(Chrome headless).
- Kotlin 2.4.x, Kotlin Multiplatform (Android / iOS / JVM / JS / Wasm targets)
- Ktor Client 3.5.x (
io.ktor:ktor-client-*) — note that Ktor 3.5 movedbodyAsText/bodyAsChanneltoio.ktor.client.statement, removed the WebDAVHttpMethodconstants andbuildHeaders, and switchedByteReadPackettokotlinx.io.Source - xmlutil 0.91.x (
io.github.pdvrieze.xmlutil) for multi-status XML parsing - kotlinx.serialization / kotlinx.coroutines
val client = WebDavClient.create(
baseUrl = "https://example.com/remote.php/dav/files/alice/",
auth = Auth.Basic(username = "alice", password = "secret"),
)let client = WebDavClientKt.WebDavClient.create(
baseUrl: "https://example.com/remote.php/dav/files/alice/",
auth: Auth.Basic(username: "alice", password: "secret")
)val dav = WebDavClient.create(baseUrl, auth = Auth.Basic("u", "p"))
// List a directory
val files: List<WebDavFile> = dav.list("docs")
// Download (streamed)
dav.downloadToChannel("docs/big.bin", sink) { transferred, total ->
println("$transferred / $total")
}
// Upload (streamed)
dav.uploadFromChannel("docs/backup.bin", channel, contentLength = size) { transferred, total ->
println("$transferred / $total")
}
// Mutations
dav.mkdir("new folder")
dav.move("a.txt", "b/a.txt")
dav.copy("docs", "docs-copy")
dav.delete("old.txt")
dav.setProperties("f.txt", set = mapOf(PropertyName.DISPLAYNAME to "renamed"))
// Locking (exclusive or shared), with automatic release
val lock = dav.lock("f.txt", owner = "my-app", timeout = 5.minutes)
dav.uploadBytes("f.txt", bytes, lockToken = lock.token) // writes carry the token
// dav.unlock("f.txt", lock.token)
// Or: lock for the duration of a block, unlock guaranteed in a finally block
val uploaded = dav.withLock("f.txt") { lock ->
dav.uploadBytes("f.txt", bytes, lockToken = lock.token)
}
// Refresh a lock, inspect active locks
val refreshed = dav.refreshLock("f.txt", lock.token, timeout = 30.minutes)
val locks = dav.getLocks("f.txt")
// Resume a download
dav.downloadToChannel("big.bin", sink, range = downloadedBytes..Long.MAX_VALUE)
// Probe server capabilities (Allow + DAV compliance classes)
val caps = dav.capabilities()
if (caps.supportsLocking) { /* ... */ }Read arbitrary (dead) properties returned by the server:
val favorite = file.properties[PropertyName("http://owncloud.org/ns", "favorite")]?.textWhen embedding the library in a web app, keep in mind:
- CORS: browsers enforce same-origin policy. The WebDAV server must answer CORS
preflight (
OPTIONS) requests and allow the custom methods (PROPFIND, MKCOL, LOCK, ...) and headers (Depth, Destination, Lock-Token, ...) the client sends. - User-Agent:
User-Agentis a forbidden header in browsers; setting it is silently ignored, but the browser always sends its own UA string, so servers that reject UA-less requests (e.g. fnos) keep working. - Memory: the JS/Wasm engine is fetch-based;
ReadChannelContentuploads may be buffered fully in memory, so very large uploads use more memory than on JVM.
All path arguments are logical paths relative to the client base URL, without a
leading slash, e.g. "docs/report 2026.txt". The library handles URL encoding,
decoding of server href values, and normalizes base URLs (trailing slash).
The library disables Ktor's automatic redirect handling and follows 3xx responses
itself: methods and request bodies are preserved on redirect (301/302/307/308), and
303 responses switch to GET. If you supply your own HttpClient, keep
followRedirects = false.
All failures derive from DavException:
HttpStatusExceptionand subclasses:UnauthorizedException(401),ForbiddenException(403),NotFoundException(404),ConflictException(409),PreconditionFailedException(412),LockedException(423), ...DavProtocolException: unparseable server responses (malformed XML, missing lock token, partial PROPPATCH failures).
./gradlew :jvmTest # run shared tests on the JVM host
./gradlew :jsBrowserTest # run shared tests in the browser (Kotlin/JS)
./gradlew :wasmJsBrowserTest # run shared tests in the browser (Kotlin/Wasm)
./gradlew :assembleDebug # build the Android AAR
./gradlew :linkDebugFrameworkIosArm64 # build the iOS framework (macOS only)The shared test suite covers XML parsing golden cases and protocol behavior using
Ktor's MockEngine (no live server needed).
WebDavClient.lock()now returnsLockInfo(withscope,typeanddepth);LockTokenis deprecated but retained.lock.token/lock.timeoutSecondskeep their names, so most call sites migrate without changes.execute()andensureSuccess()are public extension points; WebDAV methods without a dedicated wrapper (REPORT, SEARCH, ...) can be issued through them.
Licensed under the Apache License, Version 2.0.