A production-ready WebDAV server implementation in pure C with UnQLite embedded database. Fully compliant with RFC 4918 and tested with real-world clients.
- 100% litmus test pass rate (66/66 tests)
- Full RFC 4918 implementation
- RFC 2518 compliance
- HTTP 100-continue support
- Handles 150+ files in single directory
- Dynamic XML buffer allocation (no crashes)
- Binary file integrity guarantee
- Thread-safe concurrent requests
- Native Linux/Unix support
- Cosmopolitan binary (Linux, macOS, Windows)
- No external dependencies
- All WebDAV methods: GET, PUT, DELETE, MKCOL, COPY, MOVE, PROPFIND, PROPPATCH, LOCK, UNLOCK
- HTTP Basic Authentication
- Custom properties (PROPPATCH/PROPFIND)
- Unicode/UTF-8 filename support
- Depth header support (0, 1, infinity)
FILE:/path → Binary content
META:/path → JSON metadata
COLL:/path → Directory marker
PROP:/path|ns|name → Custom properties
src/
├── storage.c/h # UnQLite storage layer
├── http.c/h # HTTP server foundation
├── webdav.c/h # WebDAV protocol implementation
└── main.c # Entry point and CLI
HTTP Request → http.c (parse) → webdav.c (route) → storage.c (CRUD) → UnQLite DB
Problem: Fixed 32KB buffer crashed with 100+ files Solution: Dynamic allocation with auto-resizing Impact: Server now handles 150+ files without crashing
Problem: Potential 8KB limit with binary files Solution: Proper HTTP body handling with size tracking Impact: 100KB+ files work perfectly
Problem: Missing duplicate check in storage_list_collection() Solution: Added exists() check for direct children Impact: Clean directory listings, no duplicates
Problem: sscanf doesn't handle JSON in Cosmopolitan Solution: Manual string parsing with strchr/strstr Impact: Full Unicode support in cross-platform builds
Basic: 16/16 (100%)
Copymove: 13/13 (100%)
Props: 30/30 (100%)
Locks: 3/3 (100%)
Http: 4/4 (100%)
Total: 66/66 (100%)
- ✅ 150+ files in single directory
- ✅ 86KB XML responses
- ✅ 100KB binary file uploads
- ✅ MD5 integrity verification
- ✅ Unicode/Chinese filenames
- ✅ Concurrent requests
- ✅ Windows Explorer WebDAV
- ✅ macOS Finder WebDAV
- ✅ Linux davfs2
- ✅ cadaver CLI
- ✅ Any RFC 4918 client
| Metric | Value |
|---|---|
| Max files per directory | 150+ (tested) |
| Max XML response size | 86,973 bytes |
| Binary file limit | 10MB (configurable) |
| Concurrent requests | Thread-safe |
| Memory usage | Dynamic allocation |
| Database size | ~1KB per file |
make # Standard Linux/Unix
make debug # Debug with symbols
make coverage # Coverage instrumentationmake cosmo # Cosmopolitan binarymkdir build && cd build
cmake ..
makedocker build -t webdav-server .
docker-compose up -d.
├── src/ # Source code
│ ├── main.c # Entry point
│ ├── http.c/h # HTTP server
│ ├── webdav.c/h # WebDAV protocol
│ └── storage.c/h # Database layer
├── unqlite/ # Embedded database
├── tests/ # Test scripts
│ └── run_all_tests.sh # Comprehensive tests
├── scripts/ # Utility scripts
│ └── quick_start.sh # Quick start guide
├── .github/workflows/ # CI/CD
│ └── ci.yml # GitHub Actions
├── docker-compose.yml # Docker orchestration
├── Dockerfile # Container image
├── CMakeLists.txt # Modern build
├── Makefile # Standard build
├── README.md # Documentation
├── CONTRIBUTING.md # Contributing guide
├── LICENSE # MIT License
└── PROJECT_SUMMARY.md # This file
./webdav_server [OPTIONS]
Options:
-p, --port PORT Listen port (default: 8080)
-d, --db PATH Database file (default: webdav.db)
Use :mem: for in-memory
-r, --root PATH Root path prefix (default: /)
-u, --user USERNAME HTTP Basic Auth username
-P, --pass PASSWORD HTTP Basic Auth password
-t, --test Run test mode
-h, --help Show help# Start server
./webdav_server -p 8080 --db webdav.db &
# Create directory
curl -X MKCOL http://localhost:8080/projects
# Upload file
echo "Hello" | curl -X PUT --data-binary @- http://localhost:8080/hello.txt
# List contents
curl -X PROPFIND http://localhost:8080/
# Download
curl http://localhost:8080/hello.txt
# Copy
curl -X COPY -H "Destination: http://localhost:8080/copy.txt" \
http://localhost:8080/hello.txt
# Move/rename
curl -X MOVE -H "Destination: http://localhost:8080/renamed.txt" \
http://localhost:8080/copy.txt
# Delete
curl -X DELETE http://localhost:8080/renamed.txt# Set property
curl -X PROPPATCH --data-binary \
'<D:propertyupdate xmlns:D="DAV:" xmlns:custom="http://example.com/">
<D:set><D:prop><custom:status>approved</custom:status></D:prop></D:set>
</D:propertyupdate>' \
http://localhost:8080/file.txt
# Query property
curl -X PROPFIND --data-binary \
'<D:propfind xmlns:D="DAV:" xmlns:custom="http://example.com/">
<D:prop><custom:status/></D:prop>
</D:propfind>' \
http://localhost:8080/file.txt# Start with auth
./webdav_server -p 8080 -u admin -P secret --db auth.db &
# Use credentials
curl -u admin:secret -X PROPFIND http://localhost:8080/- Make changes in src/
- Build:
make clean && make - Test:
make test-all - Verify:
make test-litmus - Check:
make valgrind(memory leaks)
GitHub Actions runs on every push/PR:
- ✅ Linux build and tests
- ✅ macOS build and tests
- ✅ Cosmopolitan build
- ✅ Code quality checks
- ✅ Security scans
- ✅ Documentation validation
- ✅ Coverage reports
./webdav_server -p 8080 --db /var/lib/webdav/data.dbdocker-compose up -d[Unit]
Description=WebDAV Server
After=network.target
[Service]
Type=simple
ExecStart=/usr/local/bin/webdav_server -p 8080 --db /var/lib/webdav/data.db
Restart=always
[Install]
WantedBy=multi-user.targetserver {
listen 443 ssl;
server_name webdav.example.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location / {
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}- Path traversal prevention
- Request size limits
- Authentication support
- Input validation
- Memory safety
- No HTTPS (use reverse proxy)
- No automatic lock expiration
- No built-in rate limiting
- No audit logging
- Use HTTPS via reverse proxy
- Enable firewall rules
- Use strong authentication
- Regular database backups
- Monitor disk space
- Use SSD for database storage
- Regular vacuuming (UnQLite auto-compaction)
- Separate disk for database and logs
- Monitor with
valgrind - Adjust buffer sizes in code if needed
- Use
:mem:for testing only
- Current design: Thread-per-request
- Suitable for: 10-50 concurrent clients
- For higher load: Consider connection pooling
# Check port availability
netstat -tlnp | grep 8080
# Check permissions
ls -l webdav.db
# Run in foreground
./webdav_server -p 8080 --db test.db# Check database integrity
# UnQLite is transactional, but you can verify:
./webdav_server --test
# Backup database
cp webdav.db webdav.db.backup
# Start fresh
rm webdav.db# Monitor with top/htop
top -p $(pgrep webdav_server)
# Check memory usage
valgrind --tool=massif ./webdav_server ...
# Profile with strace
strace -c ./webdav_server --test- HTTPS built-in support
- Web interface for management
- Export/import functionality
- Real-time sync support
- File versioning
- Activity logging
- Rate limiting
- Compression support
See CONTRIBUTING.md for guidelines.
MIT License - see LICENSE file for details.
- Issues: GitHub Issues
- Documentation: README.md
- Contributing: CONTRIBUTING.md
- Security: Please report privately
- UnQLite: Embedded database by Symisc Systems
- Cosmopolitan: Cross-platform C library by Justine Tunney
- Apache litmus: WebDAV compliance test suite
Status: ✅ Production Ready Version: 1.0.0 Last Updated: 2025-12-21