webdav_plus 1.2.1 copy "webdav_plus: ^1.2.1" to clipboard
webdav_plus: ^1.2.1 copied to clipboard

A feature-rich WebDAV client library for Dart and Flutter with support for locking, ACL, versioning, search, and more.

WebDAV #

Dart Flutter License

WebDAV Plus is a feature-rich WebDAV client library for Dart and Flutter. Compared to other Dart WebDAV libraries, it offers more comprehensive protocol support with modern Dart idioms, including locking, ACL, versioning, search, and synchronization capabilities.

Why WebDAV Plus? #

Feature WebDAV Plus Other Dart Libraries
Core WebDAV (RFC 4918) ✅ Supported ✅ Partial
Locking (RFC 4918) ✅ Supported ❌ Limited
ACL (RFC 3744) ✅ Supported ❌ None
Versioning (RFC 3253) ✅ Supported ❌ None
Search (RFC 5323) ✅ Basic ❌ None
Sync (RFC 6578) ✅ Basic ❌ None
Quota (RFC 4331) ✅ Supported ❌ None
Streaming Support ✅ Yes ❌ Limited
Custom Auth Handlers ✅ Extensible ❌ Basic only

Features #

WebDAV Protocol Support #

  • Core Operations: PROPFIND, PROPPATCH, MKCOL, DELETE, PUT, GET, COPY, MOVE
  • Locking: Exclusive/shared locks, lock refresh, lock discovery
  • Properties: Full property management with custom namespace support

Flexible Authentication System #

  • HTTP Basic Authentication with preemptive mode
  • Domain authentication (NTLM-style username format)
  • Extensible auth handler interface - easily integrate custom authentication schemes (OAuth, Kerberos, etc.)

Advanced Resource Management #

  • ACL (Access Control Lists): Read/modify permissions, principal management
  • Quota: Query storage usage and availability
  • Versioning (DeltaV): Check-in, check-out, version history, baselines
  • Search: DASL-based content and property search
  • Sync Collection: Efficient incremental synchronization

Performance Optimized #

  • Streaming downloads: Memory-efficient large file handling
  • Streaming uploads: Upload from streams with progress tracking
  • Compression: Built-in gzip/deflate support
  • Connection reuse: HTTP client connection pooling

Robust Error Handling #

  • Hierarchical exception system with specific error types
  • Detailed error messages with HTTP status codes
  • Automatic retry support for transient failures

Installation #

Add to your pubspec.yaml:

dependencies:
  webdav_plus: ^1.2.0

Then run:

dart pub get

Quick Start #

Basic Operations #

For a complete CRUD example, see example/simple.dart.

import 'package:webdav_plus/webdav_plus.dart';

void main() async {
  // Create a client with credentials and base URL
  final client = WebdavClient.withCredentials(
    'username',
    'password',
    baseUrl: 'https://webdav.example.com/',
  );
  
  // List directory contents (paths are relative to baseUrl)
  List<DavResource> resources = await client.list('/');
  for (final resource in resources) {
    print('${resource.isDirectory ? "[DIR]" : "[FILE]"} ${resource.name}');
  }
  
  // Upload a file
  await client.put(
    '/hello.txt',
    Uint8List.fromList(utf8.encode('Hello, WebDAV!')),
  );
  
  // Download a file
  Uint8List content = await client.get('/hello.txt');
  print(utf8.decode(content));
  
  // Create a directory
  await client.createDirectory('/new-folder/');
  
  // Move/Copy resources
  await client.move('/hello.txt', '/new-folder/hello.txt');
  await client.copy('/new-folder/hello.txt', '/backup.txt');
  
  // Delete a resource
  await client.delete('/backup.txt');
  
  // Check if resource exists
  bool exists = await client.exists('/new-folder/');
  
  // Clean up
  client.shutdown();
}

Creating Clients #

// Basic client (no authentication)
final client = WebdavClient();

// With base URL only
final client = WebdavClient(baseUrl: 'https://webdav.example.com/');

// With credentials (challenge-response mode)
final client = WebdavClient.withCredentials('user', 'pass');

// With credentials and base URL
final client = WebdavClient.withCredentials(
  'user',
  'pass',
  baseUrl: 'https://webdav.example.com/',
);

// With preemptive authentication (sends credentials immediately)
final client = WebdavClient.withCredentials('user', 'pass', isPreemptive: true);

// With compression enabled
final client = WebdavClient.withCompression();

// Fully configured
final client = WebdavClient.configured(
  baseUrl: 'https://webdav.example.com/',
  username: 'user',
  password: 'pass',
  isPreemptive: true,
  compression: true,
);

// Set or change base URL at runtime
client.setBaseUrl('https://another-server.com/dav/');

Advanced Usage #

Streaming Operations (Large Files) #

For memory-efficient handling of large files:

⚠️ Important: Streaming uploads (putFileStream, putStream) require preemptive authentication. The stream can only be read once, so if the server returns 401, it cannot be retried. Use isPreemptive: true when creating the client.

// Client must use preemptive auth for streaming uploads
final client = WebdavClient.withCredentials(
  'user', 'pass',
  baseUrl: 'https://webdav.example.com/',
  isPreemptive: true, // Required for streaming uploads!
);

// Streaming download - returns Stream<List<int>>
Stream<List<int>> stream = await client.getStream(url);

// Download directly to file with progress
await client.downloadToFile(
  url,
  '/path/to/local/file.zip',
  onProgress: (received, total) {
    double progress = total > 0 ? (received / total * 100) : 0;
    print('Download progress: ${progress.toStringAsFixed(1)}%');
  },
);

// Streaming upload from file with progress
// contentType is optional - auto-detected from file extension
await client.putFileStream(
  url,
  File('/path/to/local/large-file.zip'),
  onProgress: (sent, total) {
    print('Upload progress: ${(sent / total * 100).toStringAsFixed(1)}%');
  },
);

// Or specify contentType explicitly if needed
await client.putFileStream(
  url,
  File('/path/to/file'),
  contentType: 'application/octet-stream',
);

File Locking #

// Acquire an exclusive lock
String lockToken = await client.lock(url);

try {
  // Perform operations while holding the lock
  await client.put(url, newContent);
  
  // Refresh the lock (extend timeout)
  await client.refreshLock(url, lockToken, '3600'); // 1 hour
} finally {
  // Always release the lock
  await client.unlock(url, lockToken);
}

// Check if a resource is locked
bool isLocked = await client.isLocked(url);

// Discover existing locks
List<DavResource> lockedResources = await client.discoverLocks(url);

Access Control Lists (ACL) #

// Get current ACL
DavAcl acl = await client.getAcl(url);
for (final ace in acl.aces) {
  print('Principal: ${ace.principal}');
  print('Privileges: ${ace.grantedPrivileges}');
}

// Check current user's privileges
List<String> privileges = await client.getCurrentUserPrivileges(url);
print('Your privileges: $privileges');

// Validate if user has specific privilege
bool canWrite = await client.hasPrivilege(url, 'write');

Version Control (DeltaV) #

// Put a resource under version control
await client.versionControl(url);

// Check out for editing
await client.checkout(url);

// Make modifications...
await client.put(url, updatedContent);

// Check in to create a new version
await client.checkin(url);

// Get version history
List<DavResource> versions = await client.versionsList(url);
for (final version in versions) {
  print('Version: ${version.path} - ${version.modified}');
}

// Undo checkout
await client.uncheckout(url);

Quota Information #

DavQuota quota = await client.getQuota(url);
print('Used: ${quota.quotaUsedBytes} bytes');
print('Available: ${quota.quotaAvailableBytes} bytes');
// Search for files by content
List<DavResource> results = await client.search(
  baseUrl,
  'contentcontains',
  'important document',
);

// Search by property
List<DavResource> results = await client.search(
  baseUrl,
  'displayname',
  'report',
);

Sync Collection #

// Initial sync (get all resources)
(List<DavResource> resources, String syncToken) = 
    await client.syncCollection(url, null);

// Store syncToken for later...

// Incremental sync (get only changes since last sync)
(List<DavResource> changes, String newToken) = 
    await client.syncCollection(url, syncToken);

Authentication #

Preemptive vs Challenge-Response #

Challenge-Response (default):

  • Client sends request without credentials
  • Server responds with 401 and WWW-Authenticate header
  • Client retries with Authorization header
  • More secure but requires extra round-trip

Preemptive:

  • Client sends credentials with first request
  • Faster (no extra round-trip)
  • Required for streaming uploads (stream can't be replayed)
// Preemptive authentication
client.setCredentials('user', 'pass', isPreemptive: true);

Domain Authentication #

For Windows/NTLM environments:

client.setCredentialsWithDomain(
  'username',
  'password',
  'DOMAIN',
  'WORKSTATION',
  isPreemptive: true,
);

Custom Authentication Handler #

Implement the AuthenticationHandler interface for custom auth schemes:

class MyOAuthHandler implements AuthenticationHandler {
  final String accessToken;
  
  MyOAuthHandler(this.accessToken);
  
  @override
  String get schemeName => 'Bearer';
  
  @override
  Future<Map<String, String>> authenticate(
    http.BaseRequest request,
    http.StreamedResponse? response,
  ) async {
    return {'Authorization': 'Bearer $accessToken'};
  }
  
  @override
  bool canHandle(String scheme) => scheme.toLowerCase() == 'bearer';
  
  @override
  bool get requiresChallenge => false;
}

// Use the custom handler
client.setAuthenticationHandler(MyOAuthHandler(token), isPreemptive: true);

Error Handling #

try {
  await client.get(url);
} on WebDAVNotFoundException catch (e) {
  print('Resource not found: ${e.message}');
} on WebDAVAuthenticationException catch (e) {
  print('Authentication failed: ${e.message}');
} on WebDAVForbiddenException catch (e) {
  print('Access forbidden: ${e.message}');
} on WebDAVConflictException catch (e) {
  print('Conflict (e.g., locked resource): ${e.message}');
} on WebDAVLockedException catch (e) {
  print('Resource is locked: ${e.message}');
} on WebDAVInsufficientStorageException catch (e) {
  print('Storage quota exceeded: ${e.message}');
} on WebDAVNetworkException catch (e) {
  print('Network error: ${e.message}');
} on WebDAVException catch (e) {
  print('WebDAV error: ${e.message} (HTTP ${e.statusCode})');
}

API Reference #

Core Methods #

Method Description
list(url) List directory contents
listWithDepth(url, depth) List with specific depth (0, 1, infinity)
get(url) Download resource content
getStream(url) Download as stream (memory-efficient)
put(url, data) Upload content
putStream(url, stream, length) Upload from stream
delete(url) Delete resource
createDirectory(url) Create collection (directory)
move(src, dest) Move/rename resource
copy(src, dest) Copy resource
exists(url) Check if resource exists

Locking Methods #

Method Description
lock(url) Acquire exclusive lock
unlock(url, token) Release lock
refreshLock(url, token, timeout) Extend lock timeout
isLocked(url) Check if locked
discoverLocks(url) Find all locks

ACL Methods #

Method Description
getAcl(url) Get access control list
setAcl(url, aces) Set access control list
getCurrentUserPrivileges(url) Get current user's privileges
hasPrivilege(url, privilege) Check specific privilege

Version Control Methods #

Method Description
versionControl(url) Put under version control
checkout(url) Check out for editing
checkin(url) Check in new version
uncheckout(url) Cancel checkout
versionsList(url) Get version history

Compatibility #

  • Dart SDK: >= 3.8.0
  • Flutter: All platforms (iOS, Android, Web, Desktop)
  • Standards: RFC 4918, RFC 3744, RFC 3253, RFC 5323, RFC 6578, RFC 4331

License #

MIT License - see LICENSE for details.

0
likes
130
points
183
downloads

Publisher

unverified uploader

Weekly Downloads

A feature-rich WebDAV client library for Dart and Flutter with support for locking, ACL, versioning, search, and more.

Repository (GitHub)
View/report issues

Topics

#webdav #http #file-transfer #cloud-storage #flutter

Documentation

Documentation
API reference

License

MIT (license)

Dependencies

http, meta, xml

More

Packages that depend on webdav_plus