WebDAVClient 2.7 released

I shipped WebDAVClient 2.7.0 to NuGet today. Most of the the changes fill in WebDAV protocol surface that the library never had, plus a few spec-compliance bug fixes that mattered the moment you pointed the client at a strict server.

Here’s the rundown.

WebDAV protocol coverage

The big theme this release. The library could already do the basic file/folder dance (PROPFIND/GET/PUT/MKCOL/MOVE/COPY/DELETE) but it stopped there – anything past §9.4 of RFC 4918 was a “use CustomHeaders and hope” situation. Version 2.7 closes most of those gaps.

  • LOCK / UNLOCK (RFC 4918 §9.10–9.11). New LockFile / LockFolder / UnlockFile / UnlockFolder / RefreshLock on IClient, returning a strongly-typed LockInfo (token, owner, type / scope, depth, timeout, lock-root). Tokens are normalized so callers can pass them either bare (opaquelocktoken:abc) or angle-bracket-wrapped — the form that comes back in the Lock-Token header just works.
  • PROPPATCH (RFC 4918 §9.2). New SetProperty(path, name, namespace, value) and RemoveProperty(path, name, namespace) for managing custom (dead) properties. Property names are validated as XML NCNames, the request body is built with XmlWriter so values are safely escaped, and the DAV: namespace is rejected client-side (those properties are protected). Per-property failures inside the 207 Multi-Status response surface as WebDAVException (or WebDAVConflictException for 409).
  • OPTIONS (RFC 4918 §9.1, RFC 9110 §9.3.7). New GetServerOptions(path, ct) returns a strongly-typed ServerOptions exposing the parsed DAV compliance classes (IsClass1 / IsClass2 / IsClass3, plus HasComplianceToken for non-numeric extensions like access-control or calendar-access) and the Allow methods (SupportsMethod). Use it as a preflight to confirm a server is actually WebDAV before issuing PROPFIND/LOCK, or to discover which methods the endpoint supports.
  • Granular PROPFIND (RFC 4918 §9.1). The historical <allprop/> body is unchanged, but new overloads on List / GetFolder / GetFile accept an IEnumerable<PropertyName> (sends <prop>), and new sibling methods ListPropertyNames / GetFolderPropertyNames / GetFilePropertyNames send <propname/>. Returned Item exposes three nullable collections: FoundProperties (status 200), NotFoundProperties (404 / 401 / 403 / 424) and AvailablePropertyNames (propname). Big bandwidth win on directories with hundreds of items.
  • If lock-token submission on PUT / DELETE / MOVE / COPY (RFC 4918 §10.4). Upload / UploadPartial / DeleteFile / DeleteFolder got an optional lockToken; MoveFile / MoveFolder got sourceLockToken and destinationLockToken; CopyFile / CopyFolder got destinationLockToken (COPY doesn’t modify the source per §7.5.1, so no source token). Without these, a server with locks rejects modifications with 423 Locked — which the client also surfaces as WebDAVException now.

Spec-compliance bug fixes

  • Overwrite header on COPY / MOVE (RFC 4918 §9.8.3 / §9.9.3). The client now always sends Overwrite: T so server behaviour is deterministic. MoveFile / MoveFolder / CopyFile / CopyFolder gained an optional overwrite parameter — pass false to send Overwrite: F and protect an existing destination (the server returns 412 Precondition Failed instead of silently clobbering). 204 No Content is also accepted as success now (returned when the destination already existed).
  • Depth: infinity on DELETE (RFC 4918 §9.6.1). Strict servers reject collection deletes without it. The header was just missing.

Authentication

  • Bearer / OAuth 2.0. The constructor only accepted ICredentials — fine for Basic / Windows / Digest, useless for Nextcloud, ownCloud, Box, SharePoint Online, or any Azure AD-fronted endpoint. There are now two new Client overloads:
    • Client(string bearerToken, ...) — static token.
    • Client(Func<CancellationToken, Task<string>> bearerTokenProvider, ...) — async refreshable provider for rotation flows (Azure Identity TokenCredential, MSAL, IdentityModel, custom OAuth 2.0 token stores).
      Both wire a new public WebDAVClient.Authentication.BearerTokenAuthenticationHandler (a DelegatingHandler) ahead of the existing HttpClientHandler, so the bearer header is injected on every outbound request — including the upload pipeline. A null / empty token from the provider intentionally omits the header rather than throwing, so transient “no token yet” states surface as a server 401 instead of a client exception.

Internal

  • Extracted the static helpers in Client.cs into focused helper classes under WebDAVClient.Helpers: LockTokenHeaderHelper (token normalisation + If / Lock-Token header logic), PropPatchRequestBuilder (property-name / namespace validation + body emission), and XmlEscape. No public-API change — but the responsibilities are now clearer at each call site, and each helper is directly unit-testable.

Tests

The unit-test project that landed in 2.6 paid off this release: every one of the items above shipped with regression coverage — 237 tests on net8.0 + net10.0 by the end (was 36 at the start of the branch, since most of the deep behaviour wasn’t testable before the helpers extraction).

NuGet: WebDAVClient 2.7.0 · Source: github.com/saguiitay/WebDAVClient

Should be a drop-in upgrade from previous versions – every new API is additive and the only signature changes on existing methods are new optional parameters with backward-compatible defaults (lockToken = null, overwrite = true).

The one thing worth double-checking: COPY / MOVE now actually send Overwrite: T instead of relying on each server’s house default, and DELETE now sends Depth: infinity. Both are what the spec says should have been happening all along – but if your code was relying on a specific server’s default conflict behavior, give it a quick smoke test.