webdav-rfc4918
A secure-by-default WebDAV client for Python, built on RFC 4918 and RFC 5689.
Filesystem-style API, the raw protocol when you need it, a dav command, and protection against known attacks.
Full documentation: webdav.readthedocs.io
webdav-rfc4918 is a WebDAV client for Python. WebDAV lets you read and
write files on a server over HTTP, like a network drive reachable by URL.
- Tested in CI on every commit against Nextcloud, Apache (mod_dav), nginx (dav-ext) and WsgiDAV. See Tested against four servers.
- Secure defaults: TLS verification, same-origin redirects, size limits on buffered responses. See Security.
- Works with pandas, dask and anything else built on fsspec. See fsspec.
Quick Start
Requires Python 3.11+. Built on requests.
pip install webdav-rfc4918
A single call, nothing to open or close:
import webdav
auth = ("user", "password") # HTTP Basic auth
webdav.mkdir("https://webdav.example.org/Photos/", auth=auth)
webdav.upload_file("Gorilla.jpg", "https://webdav.example.org/Photos/Gorilla.jpg", auth=auth)
webdav.ls("https://webdav.example.org/Photos/", auth=auth) # a list of Resource objects
Several calls to the same server: a FileSystem keeps the connection open.
with webdav.FileSystem("https://webdav.example.org", auth=auth) as fs:
fs.upload_file("Gorilla.jpg", "Photos/Gorilla.jpg", overwrite=True)
print(fs.ls("Photos"))
fs.download_file("Photos/Gorilla.jpg", "copy.jpg")
with fs.open("Photos/Gorilla.jpg", "rb") as f: # a file-like object
image_bytes = f.read()
Need the raw protocol: status codes, headers, the multi-status body? Use a
Session directly.
with webdav.Session("https://webdav.example.org", auth=auth) as session:
response = session.propfind("/Photos/", depth=1)
print(response.status_code, list(response.multistatus.responses))
A client certificate (mTLS) and a private CA:
with webdav.Session(
"https://webdav.example.org",
cert=("client.crt", "client.key"),
verify="ca-bundle.pem",
) as session:
session.propfind("/", depth=0)
Tested against four servers
Nextcloud, Apache mod_dav, and nginx dav-ext each get their own CI job,
because each reads the RFC differently, and each is documented separately.
WsgiDAV runs the rest of the test suite.
| Server | CI (main) | Tests | Docs |
|---|---|---|---|
| Nextcloud | 40+ | docs | |
Apache mod_dav |
100+ | docs | |
nginx + dav-ext |
25+ | docs | |
| WsgiDAV | rest of the suite | - |
The test suite runs on every commit: 1500+ tests with over 90% code coverage. It includes:
- fsspec's own conformance suite (130+ tests)
- 130+ tests that each check one clause of RFC 4918
- 70+ tests for the security defaults described below
Security
A WebDAV server, or a redirect to one, can be hostile. This is the complete list of defaults:
- TLS verification is on.
verifyisTrue,Falseor a CA bundle path;None,0or""(an unset environment variable) are aTypeError.verify=Falseemits and logs aTLSHardeningDisabledWarningthaturllib3.disable_warnings()does not silence. So does aTLSOptionswith a TLS version below 1.2 or with strict chain checking off. REQUESTS_CA_BUNDLEandCURL_CA_BUNDLEare ignored, so the environment cannot replace the CA you configured.~/.netrcis ignored too: withoutauth=, no credentials are sent.- A URL with credentials in it (
https://user:pw@host/) is refused. Passauth=instead. Why. - Credentials (
auth=, anAuthorizationorCookieheader,cookies=) over plainhttpto a host other than localhost are refused (InsecureTransport.ERROR; RFC 4918 section 20.1: Basic authentication MUST NOT be used unless the connection is secure);InsecureTransport.WARNsends them with anInsecureTransportWarning,ALLOWsilently. - Only same-origin redirects are followed (
RedirectPolicy.SAME_ORIGIN). Credentials and cookies are never sent to another origin, even a trusted one, and not on the way back to the origin the request started at either. - A redirect from
httpstohttpis never followed, under any policy. - Buffered responses, including multistatus XML, are size-capped, and so are redirect chains. Exact limits: Session reference.
- A request that is not streamed has a deadline for the whole exchange
(
max_response_time, default 300 s): connecting, every redirect hop, headers and body.timeoutonly limits each single read. - A streamed download (
stream=True,download_file) is bounded per read only. Neithermax_response_sizenormax_response_timeapplies to it. Acallback=that raises stops one that is too slow: Stopping a slow download. - XML from the server is parsed with the standard library's expat parser.
External entities are never resolved, and a document that declares an entity
at all is refused with
MalformedResponseError. How well expat itself holds up depends on the Python in use (pyexpat.EXPAT_VERSION; 2.7.2 or newer). - Credentials do not end up in exception messages, warnings or logs: the userinfo of a URL and the query of a signed URL are redacted.
download_filewrites only to the path you give, through a temporary file (private while it is written), and refuses a symlink at that path. A recursivegetorputdoes not follow a symlink out of the tree (follow_symlinks=Trueallows it).- Proxies from the environment (
HTTP_PROXY, ...) are used, as inrequests;trust_env=False/dav --no-proxyturns that off. A pickledSessionholds its credentials in clear text.
Report a vulnerability: SECURITY.md.
fsspec
fsspec is the storage interface
behind pandas, dask and most of the Python data ecosystem. This package adds
a WebDAV backend, so those tools can read and write a WebDAV server like any
other storage. It passes fsspec's own conformance suite (130+ tests).
pip install webdav-rfc4918[fsspec]
import fsspec
import pandas as pd
auth = ("user", "password")
df = pd.read_csv("webdavs://webdav.example.org/data.csv", storage_options={"auth": auth})
with fsspec.open("webdavs://webdav.example.org/Photos/Gorilla.jpg", auth=auth) as f:
f.read()
Installing the package registers the webdav and webdavs schemes with
fsspec. Paths start at the root of the server (/Photos/Gorilla.jpg). See
fsspec for URL schemes, credentials and path
semantics.
Locking
Lock a file while you edit it, so no one else can overwrite it (class 2
locking, RFC 4918 §7). The If header is handled for you:
import webdav
auth = ("user", "password")
with webdav.FileSystem("https://webdav.example.org", auth=auth) as fs:
with fs.locked("Documents/report.docx") as lock:
# writes to the locked path carry the lock token automatically
fs.upload_file("report.docx", "Documents/report.docx", overwrite=True)
fs.refresh_lock("Documents/report.docx", lock.token)
A lock times out, and nothing refreshes it for you: writes after that fail
with 412. See
Locking for the details.
Command line
The dav command is part of the package:
dav ls webdavs://webdav.example.org/Photos
dav get webdavs://webdav.example.org/report.pdf ./report.pdf
dav put ./report.pdf webdavs://webdav.example.org/report.pdf --overwrite
webdavs:// is WebDAV over HTTPS, webdav:// plain HTTP.
Also info, cat, mkdir, rm, mv and cp. Credentials via --user and
--password-stdin/--password-file (or a prompt on a terminal), or $WEBDAV_USER
and $WEBDAV_PASSWORD; --password and user:pass@host work too, but a command
line is visible in the process list. See the
CLI reference, or dav <command> --help.
More
The documentation has a quickstart, reference pages for sessions, TLS and redirects, and migration guides from webdav4, webdavclient3 and other WebDAV clients. Release history: CHANGELOG.md. Licensed under the MIT License.
Metadata
Release files for webdav-rfc4918 1.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| webdav_rfc4918-1.3.0.tar.gz | 436.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| webdav_rfc4918-1.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 621.0 kB
Release files / webdav_rfc4918-1.3.0.tar.gz
| Download URL | webdav_rfc4918-1.3.0.tar.gz |
|---|---|
| Size | 436.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0324f118703e5cf3a4377399e633d02156ba30526d324a74bfc3a72196b7867f
|
|
BLAKE2b-256 checksum How to use checksums |
1f44707b775d8bfe731f1e94e8d197cb595dd4410ff22165a893cd0d16bef48d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.
Transparency logRelease files / webdav_rfc4918-1.3.0-py3-none-any.whl
| Download URL | webdav_rfc4918-1.3.0-py3-none-any.whl |
|---|---|
| Size | 184.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0cf6c0b8723d670ca2f5bda1dbcacb9213fc3618ba4037dcaa9bdd4dd4e2f20b
|
|
BLAKE2b-256 checksum How to use checksums |
232ecc2f8cfa2986fd7660682c2faad89a0725f9c49e41fe421e0d014cb6f8d4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.
Transparency log