Attention

You are looking at outdated documentation for version 2.x. A newer version is available.

logo WsgiDAV Documentation

A generic and extendable WebDAV server written in Python and based on WSGI.

Project:https://github.com/mar10/wsgidav/
Version:2.4, Date: May 19, 2019

Build Status PyPI Version License Documentation Status

Attention

You are looking at outdated documentation for version 2.x. A newer version is available.

Installation

This section describes, how a WsgiDAV server is installed.

See also

If you plan to contribute to the WsgiDAV project, check out Development for details on how to install in development mode.

Preconditions

WsgiDAV server was tested with these operating systems (among others):

  • Linux (Ubuntu 13)
  • Mac OS X 10.9
  • Windows (Windows 10, 8, 7, Vista, XP)

WsgiDAV requires

  • Python 2.7 or 3.4+
  • Optionally lxml (will fall back to xml)

Unix / Linux

Releases are hosted on PyPI and can be installed using pip:

$ pip install --upgrade wsgidav

Or install the latest (potentially unstable) development version directly from GitHub:

$ pip install git+https://github.com/mar10/wsgidav.git

In order to run the WsgiDAV server from the command line, we also need a WSGI server such as Cheroot:

$ pip install cheroot

The following examples were tested on Ubuntu 13.04. Install lxml (optional):

$ sudo apt-get install python-lxml

If everything is cool, this should work now:

$ wsgidav --version -v
WsgiDAV/2.4.0 Python/3.6.1 Darwin-17.5.0-x86_64-i386-64bit
$ wsgidav --help

Windows

Install the preconditions if neccessary. Basically the same as for Unix / Linux

Note

MS Windows users that only need the command line interface may prefer the MSI installer.

Attention

You are looking at outdated documentation for version 2.x. A newer version is available.

User Guide

This section describes how to use the WsgiDAV library.
(Read Development to learn how to contribute to this project.)

Attention

You are looking at outdated documentation for version 2.x. A newer version is available.

Command Line Interface

This section describes how to use WsgiDAV from the command line.

The WsgiDAV server was tested with these platforms

  • Mac OS X 10.9 - 10.13
  • Ubuntu 13 - 16
  • Windows (Win 7 - 10, Vista, XP)

To serve the /tmp folder as WebDAV / share, simply run:

$ wsgidav --host=0.0.0.0 --port=80 --root=/tmp
WARNING: share '/' will allow anonymous access.
Running WsgiDAV/2.3.1 Cheroot/6.0.0 Python/3.6.1
Serving on http://127.0.0.1:8080 ...

Warning

By default, WsgiDAV will publish the folder for anonymous access. Read Configuration how to set up authentication.

CLI Options

Use the --help or -h argument to get help:

$ wsgidav --help
usage: wsgidav [-h] [-p PORT] [-H HOST] [-r ROOT_PATH]
               [--server {cheroot,cherrypy-wsgiserver,ext-wsgiutils,flup-fcgi,flup-fcgi-fork,paste,wsgiref}]
               [--ssl-adapter {builtin,pyopenssl}] [-v] [-q] [-c CONFIG_FILE]
               [--no-config] [-V]

Run a WEBDAV server to share file system folders.

Examples:

  Share filesystem folder '/temp':
    wsgidav --port=80 --host=0.0.0.0 --root=/temp

  Run using a specific configuration file:
    wsgidav --port=80 --host=0.0.0.0 --config=~/wsgidav.conf

  If no config file is specified, the application will look for a file named
  'wsgidav.conf' in the current directory.
  See
    http://wsgidav.readthedocs.io/en/latest/run-configure.html
  for some explanation of the configuration file format.


optional arguments:
  -h, --help            show this help message and exit
  -p PORT, --port PORT  port to serve on (default: 8080)
  -H HOST, --host HOST  host to serve from (default: localhost). 'localhost' is only accessible from the local computer. Use 0.0.0.0 to make your application public
  -r ROOT_PATH, --root ROOT_PATH
                        path to a file system folder to publish as share '/'.
  --server {cheroot,cherrypy-wsgiserver,ext-wsgiutils,flup-fcgi,flup-fcgi-fork,paste,wsgiref}
                        type of pre-installed WSGI server to use (default: cheroot).
  --ssl-adapter {builtin,pyopenssl}
                        used by 'cheroot' server if SSL certificates are configured (default: builtin.
  -v, --verbose         increment verbosity by one (default: 1, range: 0..5)
  -q, --quiet           set verbosity 0: suppress any output except for errors
  -c CONFIG_FILE, --config CONFIG_FILE
                        configuration file (default: wsgidav.conf in current directory)
  --no-config           do not try to load default wsgidav.conf
  -V, --version         show program's version number and exit

Licensed under the MIT license.
See https://github.com/mar10/wsgidav for additional information.
$

Use a Configuration File

Much more options are available when a configuration file is specified. By default wsgidav.conf and wsgidav.json is searched in the local directory.
An alternative file name can be specified like so:

$ wsgidav --config=my_config.conf

To prevent the use of of a local default configuration file, use this option:

$ wsgidav --no-config

See also

Configuration

Attention

You are looking at outdated documentation for version 2.x. A newer version is available.

Configuration

This document describes the configuration options of a WsgiDAV server.

The configuration file uses Python syntax to specify these options:

  • Server options (hostname, port, SSL cert, …)
  • List of share-name / WebDAV provider mappings
  • List of users for authentication
  • Optional custom DAV providers (i.e. other than FilesystemProvider)
  • Optional custom lock manager, property manager and domain controller
  • Advanced debugging options
  • (and more)

The options described below can be defined for the CLI either

  • in YAML syntax inside a wsgidav.yaml file,
  • in JSON syntax inside a wsgidav.json file, or
  • in Python syntax inside a wsgidav.conf file.

Note

The three supported file formats are just different ways for the CLI to generate a Python dict that is then passed to the WsgiDAVApp constructor.

See the annotated_wsgidav.conf

For a start, you should copy Sample Configuration or Annotated Sample Configuration and edit it to your needs. You can also start with a (YAML Sample Configuration) or a (JSON Sample Configuration).

Verbosity Level

The verbosity level can have a value from 0 to 5 (default: 3):

Verbosity Option Log level Remarks
0 -qqq CRITICAL quiet
1 -qq ERROR no output (excepting application exceptions)
2 -q WARN warnings and errors only
3   INFO show single line request summaries (for HTTP logging)
4 -v DEBUG show additional events
5 -vv DEBUG show full request/response header info (HTTP Logging) request body and GET response bodies not shown

Sample wsgidav.yaml

The YAML syntax is probably the most concise format to define configuration:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# Sample WsgiDAV configuration file
#
# 1. Rename this file to `wsgidav.yaml`
# 2. Adjust settings as appropriate
# 3. Run `wsgidav` from the same directory or pass file name with `--config` option.
#
# See http://wsgidav.readthedocs.io/en/latest/user_guide_configure.html

host: 0.0.0.0
port: 8080

# Set verbosity to standard
verbose: 1

# Remove this block to prevent directory browsing
dir_browser:
    enable: true
    response_trailer:
    davmount: false
    ms_mount: false
    ms_sharepoint_plugin: true
    ms_sharepoint_urls: false

provider_mapping:
    "/share1": "/path/to/share1"
    "/share2": "/path/to/share2"

user_mapping:
    "/share1":
        "user1":
            password: "abc123"
            description: "User 1 for Share 1"
            roles: []
    "/share2":
        "user1":
            password: "def456"
            description: "User 1 for Share 2"
            roles: []
        "user2":
            password: "qwerty"
            description: "User 2 for Share 2"
            roles: []

acceptbasic: false
acceptdigest: true
defaultdigest: true

Sample wsgidav.json

We can also use a JSON file for configuration if we don’t require the full power of Python code to set everything up.

Note that the parser ignores JavaScript-style comments:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/* Sample WsgiDAV configuration file
 *
 * 1. Rename this file to `wsgidav.json`
 * 2. Adjust settings as appropriate
 * 3. Run `wsgidav` from the same directory or pass file name with `--config` option.
 *
 * JSON formatted, but JavaScript-style comments are allowed.
 *
 * See http://wsgidav.readthedocs.io/en/latest/user_guide_configure.html
 */
{
    "host": "0.0.0.0",
    "port": 8080,
    // Verbosity 0..3
    "verbose": 1,
    // Remove this block to prevent directory browsing
    "dir_browser": {
        "enable": true,
        "response_trailer": "",
        "davmount": false,
        "ms_mount": false,
        "ms_sharepoint_plugin": true,
        "ms_sharepoint_urls": false
    },
    "provider_mapping": {
        "/share1": "/path/to/share1",
        "/share2": "/path/to/share2"
    },
    "user_mapping": {
        "/share1": {
            "user1": {
                "password": "abc123",
                "description": "User 1 for Share 1",
                "roles": []
            }
        },
        "/share2": {
            "user1": {
                "password": "def456",
                "description": "User 1 for Share 2",
                "roles": []
            },
            "user2": {
                "password": "qwerty",
                "description": "User 2 for Share 2",
                "roles": []
            }
        }
    },
    "acceptbasic": false,
    "acceptdigest": true,
    "defaultdigest": true
}

Sample wsgidav.conf

This format uses plain Python syntax, which allows us to use Python data structures, and even write helpers function, etc.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338

# Note: This file is in Python syntax and format

################################################################################
# WsgiDAV configuration file
# See
#   http://wsgidav.readthedocs.io/en/latest/run-configure.html
# for some explanation of the configuration file format.
################################################################################

# HELPERS - Do not modify this section

provider_mapping = {}
user_mapping = {}

def addShare(shareName, davProvider):
    provider_mapping[shareName] = davProvider


def addUser(realmName, user, password, description, roles=[]):
    realmName = "/" + realmName.strip(r"\/")
    userDict = user_mapping.setdefault(realmName, {}).setdefault(user, {})
    userDict["password"] = password
    userDict["description"] = description
    userDict["roles"] = roles


################################################################################
# SERVER OPTIONS
#===============================================================================
# Run WsgiDAV inside this  WSGI server.
# Supported servers: "cheroot", "cherrypy-wsgiserver", "ext_wsgiutils",
#     "flup-fcgi", "flup-fcgi-fork", "paste", "wsgiref"
# 'wsgiref' and 'ext_wsgiutils' are simple builtin servers that should *not* be
# used in production.
# All other servers must have been installed before, e.g. `pip install cheroot`.
# (The binary distribution already includes 'cheroot'.)
# Default: "cheroot", use the --server option on command line to change this.

#server = "cheroot"

# Additional arguments passed to the server on initialization (depends on `server`)
# For example for cheroot:

#server_args = {
#    "numthreads": 10,
#    "max": -1,
#    "request_queue_size": 5,
#    "timeout": 10,
#    "shutdown_timeout": 5,
#    "verbose": 0,
#}

# Server port (default: 8080, use --port on command line)
port = 8080

# Server hostname (default: localhost, use --host on command line)
host = "localhost"

#===============================================================================
# Enable SSL support
# Note:
# A valid certificate must match the servers hostname, so the bogus certs will
# not work in all scenarios.
# Create your own certificates instead!

# ssl_certificate = "wsgidav/server/sample_bogo_server.crt"
# ssl_private_key = "wsgidav/server/sample_bogo_server.key"
# ssl_certificate_chain = None

# Cheroot server supports 'builtin' and 'pyopenssl' (default: 'builtin')
# ssl_adapter = "pyopenssl"


#================================================================================
# Misc. setings
#

# Add the MS-Author-Via Response Header to OPTIONS command to allow editing
# with Microsoft Office (default: False)

add_header_MS_Author_Via = True


# Block size in bytes

# block_size = 8192


# Set this to True, to force unquoting of PATH_INFO. This should already be done by the WSGI
# Framework, so this setting should only be used to fix unexpected problems there (see issue #8).

# unquote_path_info = False


# Re-encode PATH_INFO using UTF-8 (falling back to ISO-8859-1).
# This seems to be wrong, since per PEP 3333 PATH_INFO is always ISO-8859-1 encoded
# (see https://www.python.org/dev/peps/pep-3333/#unicode-issues).
# However it seems to resolve errors when accessing resources with Chinese characters, for
# example (see issue #73).
# Set to `None` (the default) to enable this for Python 3 only.

# re_encode_path_info = None


#===============================================================================
# Middlewares
#
# Use this section to modify the default middleware stack

#from wsgidav.dir_browser import WsgiDavDirBrowser
#from debug_filter import WsgiDavDebugFilter
#from http_authenticator import HTTPAuthenticator
#from error_printer import ErrorPrinter
#middleware_stack = [ WsgiDavDirBrowser, HTTPAuthenticator, ErrorPrinter, WsgiDavDebugFilter ]

#===============================================================================
# Debugging

verbose = 3          # 0 - quiet
                     # 1 - no output (excepting application exceptions)
                     # 2 - warnings and errors only
                     # 3 - show single line request summaries (HTTP logging)
                     # 4 - show additional events
                     # 5 - show full request/response header info (HTTP Logging)
                     #     request body and GET response bodies not shown


# Enable specific module loggers
# E.g. ["lock_manager", "property_manager", "http_authenticator", ...]
enable_loggers = []

# Enable max. logging for certain http methods
# E.g. ["COPY", "DELETE", "GET", "HEAD", "LOCK", "MOVE", "OPTIONS", "PROPFIND", "PROPPATCH", "PUT", "UNLOCK"]
debug_methods = []

# Enable max. logging during  litmus suite tests that contain certain strings
# E.g. ["lock_excl", "notowner_modify", "fail_cond_put_unlocked", ...]
debug_litmus = []


################################################################################
# WsgiDavDirBrowser

dir_browser = {
    "enable": True,               # Render HTML listing for GET requests on collections
    "response_trailer": "",       # Raw HTML code, appended as footer
    "davmount": False,            # Send <dm:mount> response if request URL contains '?davmount'
    "ms_mount": False,            # Add an 'open as webfolder' link (requires Windows)
    "ms_sharepoint_plugin": True, # Invoke MS Offce documents for editing using WebDAV
    "ms_sharepoint_urls": False,  # Prepend 'ms-word:ofe|u|' to URL for MS Offce documents
#    "app_class": MyBrowser,      # (DEPRECATED with 2.4.0) Used instead of WsgiDavDirBrowser
}


################################################################################
# DAV Provider

#===============================================================================
# Property Manager
#
# Uncomment this lines to specify your own property manager.
# Default:        no support for dead properties
# Also available: wsgidav.property_manager.PropertyManager
#                 wsgidav.property_manager.ShelvePropertyManager
#
# Check the documentation on how to develop custom property managers.
# Note that the default PropertyManager works in-memory, and thus is NOT
# persistent.

### Use in-memory property manager (NOT persistent)
# (this is the same as passing 'propsmanager = True')
#from wsgidav.property_manager import PropertyManager
#propsmanager = PropertyManager()

### Use persistent shelve based property manager
#from wsgidav.property_manager import ShelvePropertyManager
#propsmanager = ShelvePropertyManager("wsgidav-props.shelve")

### Use persistent MongoDB based property manager
#from wsgidav.addons.mongo_property_manager import MongoPropertyManager
#prop_man_opts = {}
#propsmanager = MongoPropertyManager(prop_man_opts)

### Use persistent CouchDB based property manager
#from wsgidav.addons.couch_property_manager import CouchPropertyManager
#prop_man_opts = {}
#propsmanager = CouchPropertyManager(prop_man_opts)

### Use in-memory property manager (NOT persistent)
propsmanager = True


### Optional additional live property modification
# Note: by default live properties like file size and last-modified time are
# read-only, but that can be overriden here if the underlying DAV provider
# supports it. For now only the FileSystemProvider supports it and only namely
# changes to the last-modified timestamp. Enable it with the mutable_live_props
# list as below to allow clients to use the utime system call or e.g. the
# touch or cp / rsync commands with the preserve-timestamp flags on a mounted
# DAV share.
# Please note that the timestamp is set on the actual file or directory, so it
# is persistent even for in-memory property managers. It should also be noted
# that mutable last-modified may not be compliant with the RFC 4918.
#mutable_live_props = ["{DAV:}getlastmodified"]


#===============================================================================
# Lock Manager
#
# Uncomment this lines to specify your own locks manager.
# Default:        wsgidav.lock_storage.LockStorageDict
# Also available: wsgidav.lock_storage.LockStorageShelve
#
# Check the documentation on how to develop custom lock managers.
# Note that the default LockStorageDict works in-memory, and thus is NOT
# persistent.

# Example: Use in-memory lock storage
#          (this is the same as passing 'locksmanager = True', which is default)
#from wsgidav.lock_storage import LockStorageDict
#locksmanager = LockStorageDict()


# Example: Use PERSISTENT shelve based lock manager
#from wsgidav.lock_storage import LockStorageShelve
#locksmanager = LockStorageShelve("wsgidav-locks.shelve")


################################################################################
# SHARES
#
# If you would like to publish files in the location '/v_root' through a
# WsgiDAV share 'files', so that it can be accessed by this URL:
#     http://server:port/files
# insert the following line:
#     addShare("files", "/v_root")
# or, on a Windows box:
#     addShare("files", "c:\\v_root")
#
# To access the same directory using a root level share
#     http://server:port/
# insert this line:
#     addShare("", "/v_root")
#
# The above examples use wsgidav.fs_dav_provider.FilesystemProvider, which is
# the default provider implementation.
#
# If you wish to use a custom provider, an object must be passed as second
# parameter. See the examples below.


### Add a read-write file share:
addShare("dav", r"C:\temp")

### Add a read-only file share:
#from wsgidav.fs_dav_provider import FilesystemProvider
#addShare("tmp", FilesystemProvider("/tmp", readonly=True))


### Publish an MySQL 'world' database as share '/world-db'
#from wsgidav.addons.mysql_dav_provider import MySQLBrowserProvider
#addShare("world-db", MySQLBrowserProvider("localhost", "root", "test", "world"))


### Publish a virtual structure
#from wsgidav.samples.virtual_dav_provider import VirtualResourceProvider
#addShare("virtres", VirtualResourceProvider())


### Publish a Mercurial repository
#from wsgidav.addons.hg_dav_provider import HgResourceProvider
#addShare("hg", HgResourceProvider("PATH_OR_URL"))


### Publish a MongoDB
#from wsgidav.samples.mongo_dav_provider import MongoResourceProvider
#mongo_dav_opts = {}
#addShare("mongo", MongoResourceProvider(mongo_dav_opts))


################################################################################
# AUTHENTICATION
#===============================================================================
# HTTP Authentication Options

acceptbasic = True    # Allow basic authentication, True or False
acceptdigest = True   # Allow digest authentication, True or False
defaultdigest = True  # True (default digest) or False (default basic)

# Enter the name of a header field that will be accepted as authorized user.
# Including quotes, for example: trusted_auth_header = "REMOTE_USER"
trusted_auth_header = None


#===============================================================================
# Domain Controller
# Uncomment this line to specify your own domain controller
# Default: wsgidav.domain_controller, which uses the USERS section below
#
# Example:
#   use a domain controller that allows users to authenticate against a
#   Windows NT domain or a local computer.
#   Note: NTDomainController requires basic authentication:
#         Set acceptbasic=True, acceptdigest=False, defaultdigest=False

#from wsgidav.addons.nt_domain_controller import NTDomainController
#domaincontroller = NTDomainController(presetdomain=None, presetserver=None)
#acceptbasic = True
#acceptdigest = False
#defaultdigest = False


#===============================================================================
# USERS
#
# This section is ONLY used by the DEFAULT Domain Controller.
#
# Users are defined per realm:
#     addUser(<realm>, <user>, <password>, <description>)
#
# Note that the default Domain Controller uses the share name as realm name.
#
# If no users are specified for a realm, no authentication is required.
# Thus granting read-write access to anonymous!
#
# Note: If you wish to use Windows WebDAV support (such as Windows XP's My
# Network Places), you need to include the domain of the user as part of the
# username (note the DOUBLE slash), such as:
# addUser("v_root", "domain\\user", "password", "description")

addUser("", "tester", "secret", "")
addUser("", "tester2", "secret2", "")

#addUser("dav", "tester", "secret", "")
#addUser("dav", "tester2", "secret2", "")

#addUser("virtres", "tester", "secret", "")

Attention

You are looking at outdated documentation for version 2.x. A newer version is available.

WebDAV Clients

This section describes, how a WsgiDAV server can be accessed with different clients.

_images/teaser.png

WsgiDAV was tested with these clients

  • Windows 7, 8, 10
  • Windows Vista (Microsoft-WebDAV-!MiniRedir/6.0.6002)
  • Window XP (Microsoft-WebDAV-!MiniRedir/5.1.2600)
  • DAVExplorer
  • Microsoft® Office 2013
  • OpenOffice 3.1
  • Ubuntu Nautilus / gvfs
  • Mac OS/X Finder

The following examples assume, that we have a running WsgiDAV server on a remote machine with this configuration:

  • Server is running on a machine with IP address 192.168.0.2
  • Server is listening on port 80
  • A user called ‘tester’ with password ‘secret’ is configured to have access. (Or the share allows anonymous access.)

Windows clients

Redirector

The easiest way to access a WebDAV share from a Windows client is probably to map it as a network drive, and then use the File Explorer.

If the share allows anonymous access, type this at the command promt:

> net use W: http://192.168.0.2/
> dir W:

For access controlled shares, we must provide a user name:

> net use W: http://192.168.0.2/ /USER:tester

Windows will then prompt for a password. Alternatively we can pass password with the command line:

> net use W: http://192.168.0.2/ /USER:tester secret

To make this connection persistent between reboots:

> net use W: http://192.168.0.2/ /USER:tester secret /PERSISTENT:YES

To stop a connection:

> net use W: /DELETE

Note

Some known quirks of Windows redirector are listed below.

Known issues on all Windows versions

  • See als greenbytes WebDAV Mini-Redirector (MRXDAV.SYS) Versions and Issues List.

  • The WebDAV server must respond to PROPFIND and OPTIONS requests at the root share (‘/’). So when running behind another web server, WsgiDAV must be mounted at top level.

  • Digest authentication is supported by default. Basic authentication is disabled, when HTTP is used instead of SSL/HTTPS. (This can be changed by editing the registry: http://support.microsoft.com/kb/841215)

    Basic authentication sends passwords unencrypted, so it is generally a good thing to do this only over an SSL encrypted channel.

    Problems may arise, when we cannot provide Digest authentication (maybe because a custom WsgiDAV domain controller has no access to the users passwords). Or when our server does not provide HTTPS support.

Additional issues on Windows 7

  • By default Basic authentication is only allowed, when SSL (HTTPS) is used. (See previous notes.)

  • Reportedly on Windows 7, WebDAV requests receive a 3 second delay in the Windows explorer. To fix this, you may change IE’s proxy settings:

    Open IE -> Go to Tools menu -> Internet Options -> Connections
    -> LAN settings -> Un-check Automatically detect settings
    -> Click Ok -> Click Ok
    

Additional issues on Windows Vista:

  • By default Basic authentication is only allowed, when SSL (HTTPS) is used. (See previous notes.)

Additional issues on Windows XP:

  • Windows XP cannot map ‘/’ shares, so we have to connect to an existing sub folder (for example /dav):

    > net use W: http://192.168.0.2/dav
    
  • No custom port is accepted in the URL, like http://192.168.0.2:8001/dav. So WsgiDAV must run on port 80. This also means, that SSL won’t work (This may help: http://www.stunnel.org/).

  • The URL must start with http://. HTTPS is not supported.

    This in turn means that we have to enable Digest authentication, because Basic authentication is not allowed over HTTP (see common Windows issues above).

    However at least on SP3 the redirector seems to follow 302 Redirect responses to a https location. And then Basic authentication worked.

  • There have been problems reported, when the NET USE command prompts you for a name/password. (Servicepack 3 seems to work fine.) In this case, try to pass username and password on the command line with the /USER option:

    > net use W: http://192.168.0.2/dav /USER:tester secret
    
WebFolders

Microsoft’s “WebFolder” client predates Windows XP’s WebDAV Redirector.

  • TODO

Note

Some known quirks of Microsoft’s “WebFolder” client are listed below.

See als greenbytes Web Folder Client (MSDAIPP.DLL) Versions and Issues List.

Linux clients

Nautilus / gvfs

From the Nautilus File menu choose ‘Connect to server…’. In the dialog box enter

  • Service type: ‘WebDAV (HTTP)’
  • Server: ‘192.168.0.2’
  • Folder: ‘ro_docs’ or whatever the share name is (leave empty for root share).
  • Port: the port number (leave empty for default port 80)
  • User Name: leave this empty: do not enter anything here.

Then click ‘Connect’ and enter username and password.

Known issues:

davfs2

On Ubuntu we can mount a WebDAV file system. First make sure, that davfs2 is installed:

$ sudo apt-get install davfs2

Then create the mount point:

$ sudo mkdir /mnt/wsgidav_temp
$ sudo chmod 0766 /mnt/wsgidav_temp
$ sudo mount -t davfs http://192.168.0.2/dav /mnt/wsgidav -o rw
Please enter the username to authenticate with server
http://192.168.0.2/dav or hit enter for none.
Username: tester
Please enter the password to authenticate user tester with server
http://192.168.0.2/dav or hit enter for none.
Password:

To unmount:

sudo unmount /mnt/wsgidav

Mac

OS/X Finder

In the Finder menu, choose Go and Connect to Server….

1. Enter the URI of the WsgiDAV server: http://192.168.0.2/dav 1. Click the [+] button behind it to keep the server URI. 1. Click the [Connect] button at the bottom. 1. Enter credentials and click [Connect] 1. Browse, search, and so on. The volume is on the desktop.

Browser clients

WsgiDAV enables HTTP browsing by default, so it is always possible to enter:

http://192.168.0.2/dav

in the address bar of your favorite web browser.

Attention

You are looking at outdated documentation for version 2.x. A newer version is available.

Using the Library

This section describes how to use the wsgidav package to implement custom WebDAV servers.

Todo

This documentation is still under construction.

The wsgidav package can be used in Python code:

$ python
>>> from wsgidav import __version__
>>> __version__
'2.3.1'

Run Inside a WSGI Server

The WsgiDAV server was tested with these WSGI servers:

  • Cheroot
  • cherrypy.wsgiserver
  • paste.httpserver
  • Pylons
  • wsgidav.ext_wsgiutils_server (bundled with WsgiDAV)
  • wsgiref.simple_server

In order to run WsgiDAV, we need to create an instance of WsgiDAVApp, pass options, and mount it on a WSGI compliant web server.
Here we keep most of the default options and use the cheroot WSGI server

from cheroot import wsgi
from wsgidav.wsgidav_app import WsgiDAVApp

config = {
    "host": "0.0.0.0",
    "port": 8080,
    "mount_path": "/dav",
    "root": "/User/joe/pub",
    "verbose": 1,
    }

app = WsgiDAVApp(config)

server_args = {
    "bind_addr": (config["host"], config["port"]),
    "wsgi_app": app,
    }
server = wsgi.Server(**server_args)
server.start()

Options are passed as Python dict, see the Configuration for details.

By default, the FilesystemProvider is used. This provider creates instances of FileResource and FolderResource to represent files and directories respectively.

This is why the example above will publish the directory /User/joe/pub as http://HOST:8080/dav.

See the Sample WSGI Server for another example.

Run Inside Pylons

See Running as Pylons controller for an example how WsgiDAV can be configured as Pylons controller.

Custom Providers

If we want to implement custom behavior, we can define our own variant of a (derived from DAVProvider), which typically also uses custom instances of DAVNonCollection and DAVCollection.

from cheroot import wsgi
from wsgidav.wsgidav_app import WsgiDAVApp
from bar_package import FooProvider

config = {
    "host": "0.0.0.0",
    "port": 8080,
    "provider_mapping": {
        "/dav": FooProvider(),
        },
    "verbose": 1,
    }

app = WsgiDAVApp(config)

Logging

By default, the library initializes and uses a python logger named ‘wsgidav’ and sub-loggers named like ‘wsgidav.wsgidav_app’, etc.

By default, the wsgidav logger only has a NullHandler assigned and does not propagate to the root logger, so it is silent.

This logger can be enabled like so:

import logging

logger = logging.getLogger("wsgidav")
logger.propagate = True
logger.setLevel(logging.DEBUG)

Note

The CLI calls util.initLogging() on startup, so it logs to stdout as configured by the verbose and enable_loggers options.

Attention

You are looking at outdated documentation for version 2.x. A newer version is available.

Writing Custom Providers

Note

This documentation is under construction.

Samples and Addons for WsgiDAV

Attention

You are looking at outdated documentation for version 2.x. A newer version is available.

Mercurial WebDAV provider
Examples

This screenshot shows how a Mercurial repository appears in Microsoft Windows File Explorer:

_images/Explorer_Mercurial.png

Some new live properties are available:

_images/DAVExplorer_Mercurial.png
Usage

Note

This is not production code.

To publish a Mercurial repository by the share name ‘hg’, simply add thes lines to the configuration file:

# Publish a Mercurial repository
from wsgidav.addons.hg_dav_provider import HgResourceProvider
addShare("hg", HgResourceProvider("REPO_PATH_OR_URL"))
Details

DAV provider that publishes a Mercurial repository.

Note: This is not production code!

The repository is rendered as three top level collections.

edit:
Contains the working directory, i.e. all files. This includes uncommitted changes and untracked new files. This folder is writable.
released:
Contains the latest committed files, also known as ‘tip’. This folder is read-only.
archive:
Contains the last 10 revisions as sub-folders. This folder is read-only.

Sample layout:

/<share>/
    edit/
        server/
            ext_server.py
        README.txt
    released/
    archive/
        19/
        18/
        ...

Supported features:

  1. Copying or moving files from /edit/.. to the /edit/.. folder will result in a hg copy or hg rename.
  2. Deleting resources from /edit/.. will result in a hg remove.
  3. Copying or moving files from /edit/.. to the /released folder will result in a hg commit. Note that the destination path is ignored, instead the source path is used. So a user can drag a file or folder from somewhere under the edit/.. directory and drop it directly on the released directory to commit changes.
  4. To commit all changes, simply drag’n’drop the /edit folder on the /released folder.
  5. Creating new collections results in creation of a file called .directory, which is then hg add ed since Mercurial doesn’t track directories.
  6. Some attributes are published as live properties, such as {hg:}date.

Known limitations:

  1. This ‘commit by drag-and-drop’ only works, if the WebDAV clients produces MOVE or COPY requests. Alas, some clients will send PUT, MKCOL, … sequences instead.
  2. Adding and then removing a file without committing after the ‘add’ will leave this file on disk (untracked) This happens for example whit lock files that Open Office Write and other applications will create.
  3. Dragging the ‘edit’ folder onto ‘released’ with Windows File Explorer will remove the folder in the explorer view, although WsgiDAV did not delete it. This seems to be done by the client.
See:
http://mercurial.selenic.com/wiki/MercurialApi
Requirements:
easy_install mercurial or install the API as non-standalone version from here: http://mercurial.berkwood.com/ http://mercurial.berkwood.com/binaries/mercurial-1.4.win32-py2.6.exe

Attention

You are looking at outdated documentation for version 2.x. A newer version is available.

MongoDB WebDAV provider
Module description

Implementation of a WebDAV provider that provides a very basic, read-only resource layer emulation of a MongoDB database.

Usage: add the following entries to wsgidav.conf:

from wsgidav.samples.mongo_dav_provider import MongoResourceProvider
mongo_dav_opts = {}
addShare("mongo", MongoResourceProvider(mongo_dav_opts))

Valid options are (sample shows defaults):

opts = {"host": "localhost",       # MongoDB server
        "port": 27017,             # MongoDB port
        # This options are used with `mongod --auth`
        # The user must be created in the admin db with
        # > use admin
        # > db.addUser(username, password)
        "user": None,              # Authenticate with this user
        "pwd": None,               # ... and password
        }

Attention

You are looking at outdated documentation for version 2.x. A newer version is available.

MySQL WebDAV provider
Examples

This screenshot shows how the country table of MySQL’s world-db sample database is published as a collection.

All table rows are rendered as non-collections (text files) that contain the CSV formatted columns.

An additional virtual text file _ENTIRE_CONTENTS is created, that contains th whole CSV formatted table content.

_images/Browser_MySQL.gif

The table’s columns are mad accessible as live properties: .. image:: _static/img/DAVExplorer_MySQL.gif

Usage

To publish an MySQL database, simply add thes lines to the configuration file:

### Publish an MySQL 'world' database as share '/world-db'
from wsgidav.addons.mysql_dav_provider import MySQLBrowserProvider
addShare("world-db", MySQLBrowserProvider("localhost", "root", "test", "world"))
Module description

Implementation of a WebDAV provider that provides a very basic, read-only resource layer emulation of a MySQL database.

This module is specific to the WsgiDAV application. It provides a classes MySQLBrowserProvider.

Usage:

(see doc/annotated_wsgidav.conf)
MySQLBrowserProvider(host, user, passwd, db)

host - host of database server
user - username to access database
passwd - passwd to access database
db - name of database on database server

The MySQLBrowserProvider provides a very basic, read-only resource layer emulation of a MySQL database. It provides the following interface:

  • the root collection shared consists of collections that correspond to table names
  • in each table collection, there is a resource called “_ENTIRE_CONTENTS”. This is a non-collection resource that returns a csv representation of the entire table
  • if the table has a single primary key, each table record will also appear as a non-collection resource in the table collection using the primary key value as its name. This resource returns a csv representation of the record and will also include the record attributes as live properties with attribute name as property name and table name suffixed with colon as the property namespace

This is a very basic interface and below is a by no means thorough summary of its limitations:

  • Really only supports having numbers or strings as primary keys. The code uses a numeric or string comparison that may not hold up if the primary key is a date or some other datatype.
  • There is no handling for cases like BLOBs as primary keys or such. Well, there is no handling for BLOBs in general.
  • When returning contents, it buffers the entire contents! A bad way to return large tables. Ideally you would have a FileMixin that reads the database even as the application reads the file object….
  • It takes too many database queries to return information. Ideally there should be some sort of caching for metadata at least, to avoid unnecessary queries to the database.

Attention

You are looking at outdated documentation for version 2.x. A newer version is available.

Attention

You are looking at outdated documentation for version 2.x. A newer version is available.

Virtual WebDAV provider
Examples

Given these 3 ‘resources’:

Title Orga Status Tags Attachments
My doc 1 development draft cool, hot MySpec.doc MySpec.pdf
My doc 2 development published cool, nice MyURS.doc
My doc (.) marketing published nice MyURS.doc

this dynamic structure is published:

_images/Explorer_virtual.png

A resource is served as an collection, which is generated on-the-fly and contains some virtual files with additional information:

_images/Browser_virtual.png
Usage

To publish the sample virtual resources, simply add thes lines to the configuration file:

# Publish a virtual structure
from wsgidav.samples.virtual_dav_provider import VirtualResourceProvider
addShare("virtres", VirtualResourceProvider())
Module description

Sample implementation of a DAV provider that provides a browsable, multi-categorized resource tree.

Note that this is simply an example with no concrete real world benefit. But it demonstrates some techniques to customize WsgiDAV.

Compared to a published file system, we have these main differences:

  1. A resource like My doc 1 has several attributes like key, orga, tags, status, description. Also there may be a list of attached files.
  2. These attributes are used to dynamically create a virtual hierarchy. For example, if status is draft, a collection <share>/by_status/draft/ is created and the resource is mapped to <share>/by_status/draft/My doc 1.
  3. The resource My doc 1 is rendered as a collection, that contains some virtual descriptive files and the attached files.
  4. The same resource may be referenced using different paths For example <share>/by_tag/cool/My doc 1, <share>/by_tag/hot/My doc 1, and <share>/by_key/1 map to the same resource. Only the latter is considered the real-path, all others are virtual-paths.
  5. The attributes are exposed as live properties, like “{virtres:}key”, “{virtres:}tags”, and “{virtres:}description”. Some of them are even writable. Note that modifying an attribute may also change the dynamically created tree structure. For example changing “{virtres:}status” from ‘draft’ to ‘published’ will make the resource appear as <share>/by_status/published/My doc 1.
  6. This provider implements native delete/move/copy methods, to change the semantics of these operations for the virtual ‘/by_tag/’ collection. For example issuing a DELETE on <share>/by_tag/cool/My doc 1 will simply remove the ‘cool’ tag from that resource.
  7. Virtual collections and artifacts cannot be locked. However a resource can be locked. For example locking <share>/by_tag/cool/My doc 1 will also lock <share>/by_key/1.
  8. Some paths may be hidden, i.e. by_key is not browsable (but can be referenced) TODO: is this WebDAV compliant?

The database is a simple hard coded variable _resourceData, that contains a list of resource description dictionaries.

A resource is served as an collection, which is generated on-the-fly and contains some virtual files (artifacts).

In general, a URL is interpreted like this:

<share>/<category-type>/<category-key>/<resource-name>/<artifact-name>

An example layout:

<share>/
    by_tag/
        cool/
            My doc 1/
                .Info.html
                .Info.txt
                .Description.txt
                MySpec.pdf
                MySpec.doc
            My doc 2/
        hot/
            My doc 1/
            My doc 2/
        nice/
            My doc 2/
            My doc 3
    by_orga/
        development/
            My doc 3/
        marketing/
            My doc 1/
            My doc 2/
    by_status/
        draft/
            My doc 2
        published/
            My doc 1
            My doc 3
    by_key/
        1/
        2/
        3/

When accessed using WebDAV, the following URLs both return the same resource ‘My doc 1’:

<share>/by_tag/cool/My doc 1
<share>/by_tag/hot/My doc 1
<share>/by_key/1

Attention

You are looking at outdated documentation for version 2.x. A newer version is available.

NT Domain Controller
Examples

TODO

Usage

TODO

Module description

Implementation of a domain controller that allows users to authenticate against a Windows NT domain or a local computer (used by HTTPAuthenticator).

Purpose

Usage:

from wsgidav.addons.nt_domain_controller import NTDomainController
domaincontroller = NTDomainController(presetdomain=None, presetserver=None)

where:

  • domaincontroller object corresponds to that in wsgidav.conf or as input into wsgidav.http_authenticator.HTTPAuthenticator.
  • presetdomain allows the admin to specify a domain to be used (instead of any domain that may come as part of the username in domainuser). This is useful only if there is one domain to be authenticated against and you want to spare users from typing the domain name
  • presetserver allows the admin to specify the NETBIOS name of the domain controller to be used (complete with the preceding \). if absent, it will look for trusted domain controllers on the localhost.

This class allows the user to authenticate against a Windows NT domain or a local computer, requires NT or beyond (2000, XP, 2003, etc).

This class requires Mark Hammond’s Win32 extensions for Python at here or sourceforge

Information on Win32 network authentication was from the following resources:

Testability and caveats
Digest Authentication

Digest authentication requires the password to be retrieve from the system to compute the correct digest for comparison. This is so far impossible (and indeed would be a big security loophole if it was allowed), so digest authentication WILL not work with this class.

Highly recommend basic authentication over SSL support.

User Login

Authentication will count as a user login attempt, so any security in place for invalid password attempts may be triggered.

Also note that, even though the user is logged in, the application does not impersonate the user - the application will continue to run under the account and permissions it started with. The user has the read/write permissions to the share of the running account and not his own account.

Using on a local computer
This class has been tested on a local computer (Windows XP). Leave domain as None and do not specify domain when entering username in this case.
Using for a network domain
This class is being tested for a network domain (I’m setting one up to test).

ml

Attention

You are looking at outdated documentation for version 2.x. A newer version is available.

MongoDB property manager

Note

This is not production code.

Module description

Implements a property manager based on MongoDB.

Usage: add this lines to wsgidav.conf:

from wsgidav.addons.mongo_property_manager import MongoPropertyManager
prop_man_opts = {}
propsmanager = MongoPropertyManager(prop_man_opts)

Valid options are (sample shows defaults):

opts = {"host": "localhost",       # MongoDB server
        "port": 27017,             # MongoDB port
        "dbName": "wsgidav-props", # Name of DB to store the properties
        # This options are used with `mongod --auth`
        # The user must be created with db.addUser()
        "user": None,              # Authenticate with this user
        "pwd": None,               # ... and password
        }

Attention

You are looking at outdated documentation for version 2.x. A newer version is available.

CouchDB property manager

Note

This is not production code.

Module Description

Implements a property manager based on CouchDB.

http://wiki.apache.org/couchdb/Reference http://packages.python.org/CouchDB/views.html

Usage: add this lines to wsgidav.conf:

from wsgidav.addons.couch_property_manager import CouchPropertyManager
prop_man_opts = {}
propsmanager = CouchPropertyManager(prop_man_opts)

Valid options are (sample shows defaults):

opts = {"url": "http://localhost:5984/",  # CouchDB server
        "dbName": "wsgidav-props",        # Name of DB to store the properties
        }

Note

Not all samples have yet been ported to WsgiDAV 2.x.

Mercurial WebDAV provider
WebDAV provider that publishes a Mercurial repository.
MongoDB WebDAV provider
WebDAV provider that publishes a mongoDB database.
MySQL WebDAV provider
Implementation of a WebDAV provider that provides a very basic, read-only resource layer emulation of a MySQL database.
Google App Engine provider
Implementation of a WebDAV provider that implements a virtual file system built on Google App Engine’s data store (‘Bigtable’). This project also implements a lock storage provider that uses memcache.
Virtual WebDAV provider
Sample implementation of a DAV provider that provides a browsable, multi-categorized resource tree.
NT Domain Controller
Implementation of a domain controller that allows users to authenticate against a Windows NT domain or a local computer (used by HTTPAuthenticator).
MongoDB property manager
Implementation of a property manager, that stores dead properties in mongoDB (used by WebDAV providers).
CouchDB property manager
Implementation of a property manager, that stores dead properties in CouchDB (used by WebDAV providers).

Attention

You are looking at outdated documentation for version 2.x. A newer version is available.

FAQ

What do I need to run WsgiDAV?
See run-install for details.
Which web servers are supported?

WsgiDAV comes with a standalone server, to run out of the box. There is also built-in support for CherryPy, Paste, and wsgiref servers, as long as these packages are installed. (Fast)CGI should be possible using flup.

Basically, it runs with all WSGI servers. Currently we tested with Pylons, CherryPy, Paste server.

See run-configure for details.

Which configuration do you recommend?

Currently CherryPy seems to be very robust. Also installing lxml is recommended.

But since WsgiDAV is pretty new, please provide feedback on your experience.

Which WebDAV clients are supported?

Basically all WebDAV clients on all platforms, though some of them show odd behaviors.

See run-access for details.

I found a bug, what should I do?

First, check the issue list, if this is a known bug. If not, open a new issue and provide detailed information how to reproduce it.

Then fix it and send me the patch ;-)

How do you pronounce WsgiDAV?
Don’t care really, but I would say ‘Whiskey Dove’.

Attention

You are looking at outdated documentation for version 2.x. A newer version is available.

Reference Guide

This document gives a brief introduction to the WsgiDAV application package (targeted to developers).

Attention

You are looking at outdated documentation for version 2.x. A newer version is available.

Architecture

This document gives a brief introduction to the WsgiDAV application package (targeted to developers).

WSGI Application Stack

WsgiDAV is a WSGI application.

WSGI <http://www.python.org/peps/pep-0333.html> stands for Web Server Gateway Interface, a proposed standard interface between web servers and Python web applications or frameworks, to promote web application portability across a variety of web servers. If you are unfamiliar with WSGI, do take a moment to read the PEP.

As most WSGI applications, WsgiDAV consists of middleware which serve as pre-filters and post-processors, and the actual application.

WsgiDAV implements this WSGI application stack:

<Request>
  |
<Server> -> wsgidav_app.WsgiDavApp (container)
             |
             +-> debug_filter.WsgiDavDebugFilter (middleware, optional)
                   |
                 error_printer.ErrorPrinter (middleware)
                   |
                 http_authenticator.HTTPAuthenticator (middleware)
                   |           \- Uses a domain controller object
                   |
                 dir_browser.WsgiDavDirBrowser (middleware, optional)
                   |
                 request_resolver.RequestResolver (middleware)
                   |
                   *-> request_server.RequestServer (application)
                               \- Uses a DAVProvider object
                                   \- Uses a lock manager object
                                      and  a property  manager object

Note

This is the default stack. Middleware applications and order can be configured using middleware_stack option. You can write your own or extend existing middleware and place it on middleware stack.

See the following sections for details.

Building Blocks

DAV Providers
Inheritance diagram of wsgidav.dav_provider, wsgidav.fs_dav_provider

DAV providers are abstractions layers that are used by the RequestServer to access and manipulate DAV resources.

All DAV providers must implement a common interface. This is usually done by deriving from the abstract base class DAVProvider.

WsgiDAV comes with a DAV provider for file systems, called FilesystemProvider. That is why WsgiDAV is a WebDAV file server out-of-the-box.

There are also a few other modules that may serve as examples on how to plug-in your own custom DAV providers (see also Writing Custom Providers).

Property Managers
Inheritance diagram of wsgidav.property_manager

DAV providers may use a property manager to support persistence for dead properties.

WsgiDAV comes with two default implementations, one based on a in-memory dictionary, and a persistent one based on shelve:

property_manager.PropertyManager
property_manager.ShelvePropertyManager

PropertyManager is used by default, but ShelvePropertyManager can be enabled by uncommenting two lines in the configuration file.

In addition, this may be replaced by a custom version, as long as the required interface is implemented.

Lock Managers
Inheritance diagram of wsgidav.lock_manager, wsgidav.lock_storage

DAV providers may use a lock manager to support exclusive and shared write locking.

WsgiDAV comes with two default implementations, one based on a in-memory dictionary, and a persistent one based on shelve:

lock_manager.LockManager
lock_manager.ShelveLockManager

LockManager is used by default, but ShelveLockManager can be enabled by uncommenting two lines in the configuration file.

In addition, this may be replaced by a custom version, as long as the required interface is implemented.

Domain Controllers
Inheritance diagram of wsgidav.http_authenticator, wsgidav.domain_controller, wsgidav.addons.nt_domain_controller

A domain controller provides user/password checking for a realm to the HTTPAuthenticator.

WsgiDAV comes with a default implementation that reads a user/password list from the config file.

However, this may be replaced by a custom version, as long as the required interface is implemented.

~wsgidav.addons.nt_domain_controller is an example for such an extension.

WsgiDAVDomainController
Default implementation of a domain controller as used by HTTPAuthenticator.

Applications

Inheritance diagram of wsgidav.middleware, wsgidav.dir_browser, wsgidav.debug_filter, wsgidav.dav_error, wsgidav.error_printer, wsgidav.http_authenticator, wsgidav.rw_lock, wsgidav.wsgidav_app, wsgidav.request_server, wsgidav.request_resolver
WsgiDavApp
WsgiDavDebugFilter
ErrorPrinter

Middleware error_printer.ErrorPrinter Handle DAV exceptions and internal errors.

On init:
Store error handling preferences.
For every request:
Pass the request to the next middleware. If a DAV exception occurs, log info, then pass it on. Internal exceptions are converted to HTTP_INTERNAL_ERRORs.
HTTPAuthenticator

Middleware http_authenticator.HTTPAuthenticator Uses a domain controller to establish HTTP authentication.

On init:
Store the domain controller object that is used for authentication.
For every request:

if authentication is required and user is not logged in: return authentication response.

Else set these values:

``environ['httpauthentication.realm']``
``environ['httpauthentication.username']``
WsgiDavDirBrowser

Middleware dir_browser.WsgiDavDirBrowser Handles GET requests on collections to display a HTML directory listing.

On init:

For every request:

If path maps to a collection:
Render collection members as directory (HTML table).
RequestResolver

Middleware request_resolver.RequestResolver Find the mapped DAV-Provider, create a new RequestServer instance, and dispatch the request.

On init:

Store URL-to-DAV-Provider mapping.

For every request:

Setup environ["SCRIPT_NAME"] to request realm and and environ["PATH_INFO"] to resource path.

Then find the registered DAV-Provider for this realm, create a new RequestServer instance, and pass the request to it.

Note: The OPTIONS method for ‘*’ is handled directly.

RequestServer

Application request_server.RequestServer Handles one single WebDAV request.

On init:

Store a reference to the DAV-Provider object.

For every request:

Handle one single WebDAV method (PROPFIND, PROPPATCH, LOCK, …) using a DAV-Provider instance. Then return the response body or raise an DAVError.

Note: this object only handles one single request.

Attention

You are looking at outdated documentation for version 2.x. A newer version is available.

Glossary

This document defines some terms gives a brief introduction to the WsgiDAV application package (targeted to developers).

See also

You can find more information about WebDAV terms and naming convention in official WebDAV specification documentation.

You will find this terms / naming conventions in the source:

URL:

In general URLs follow these rules:

  • Byte strings, using ISO-8859-1 encoding
  • Case sensitive
  • Quoted, i.e. special characters are escaped
  • Collections have a trailing ‘/’ (but we also accept request URLs, that omit them.)
When we use the term URL in WsgiDAV variables, we typically mean absolute URLs:
/<mount>/<path>
When we use the term full URL, we typically mean complete URLs:
http://<server>:<port>/<mount>/<path>
Constructed like
fullUrl = util.makeCompleteURL(environ)
Example
http://example.com:8080/dav/public/my%20nice%20doc.txt
Path (in general):

When we use the term Path in WsgiDAV variables, we typically mean unquoted URLs, relative to the mount point.

Example
“/public/my nice doc.txt”
mount point (also ‘mount path’, ‘approot’):

Unquoted, ISO-8859-1 encoded byte string.

The application’s mount point. Starts with a ‘/’ (if not empty).

This is the virtual directory, where the web server mounted the WsgiDAV application. So it is the environ[SCRIPT_NAME] that the server had set, before calling WsgiDAVApp.

Example
“”
share path (also ‘share’, ‘domain’):

Unquoted, ISO-8859-1 encoded byte string.

The application’s share path, relative to the mount point. Starts with a ‘/’ (if not empty).

For every request, WsgiDAVApp tries to find the registered provider for the URL (after the mount path was popped). The share path is the common URL prefix of this URL.

TODO: do we need to ditinguish between server mount points (‘mount path’) and WsgiDAV mount points (‘share path’)?

Constructed like
mountPath = environ[SCRIPT_NAME]
Example
“/dav”
realm:

Unquoted, ISO-8859-1 encoded byte string.

The domain name, that a resource belongs to.

This string is used for HTTP authentication.

Each realm would have a set of username and password pairs that would allow access to the resources.

Examples
“Marketing Department” “Windows Domain Authentication”

The domain_controller.WsgiDAVDomainController implementation uses the mount path as realm name.

path

Unquoted, ISO-8859-1 encoded byte string.

The resource URL, relative to the application’s mount point. Starts with a ‘/’. Collections also should have a trailing ‘/’.

Constructed like:
path = environ[PATH_INFO]
Examples:
“/public/my nice doc.txt” “/public/”
preferred path:

Unquoted, ISO-8859-1 encoded byte string.

The preferred or normalized path.

Depending on case sensitivity of the OS file system, all these paths may map to the same collection resource:

/public/my folder/
/public/my folder   (missing '/')
/public/MY FOLDER/  (on a Windows server, which is not case sensitive)

provider.getPreferredPath(path) will return:

/public/my folder/

for all of these variants.

reference URL:

Quoted, UTF-8 encoded byte string.

This is basically the same as an URL, that was build from the preferred path. But this deals with ‘virtual locations’ as well.

Since it is always unique for one resource, <refUrl> is used as key for the lock- and property storage.

A resource has always one ‘real location’ and may have 0..n ‘virtual locations’.

For example:

/dav/public/my%20folder/file1.txt
/dav/by_key/1234
/dav/by_status/approved/file1.txt

may map to the same resource, but only:

/dav/by_key/1234

is the refUrl.

Constructed like:
realUrl = quote(mountPath + reference path)
Examples:
“/dav/by_key/1234”
href:

Quoted, UTF-8 encoded byte string.

Used in XML responses. We are using the path-absolute option. i.e. starting with ‘/’. (See http://www.webdav.org/specs/rfc4918.html#rfc.section.8.3)

Constructed like:
href = quote(mountPath + preferredPath)
Example:
“/dav/public/my%20nice%20doc.txt”
filePath:

Unicode

Used by fs_dav_provider when serving files from the file system. (At least on Vista) os.path.exists(filePath) returns False, if a file name contains special characters, even if it is correctly UTF-8 encoded. So we convert to unicode.

Attention

You are looking at outdated documentation for version 2.x. A newer version is available.

API Doc

Package wsgidav

wsgidav._version Package version number.
wsgidav.compat Tool functions to support Python 2 and 3.
wsgidav.dav_error Implements a DAVError class that is used to signal WebDAV and HTTP errors.
wsgidav.dav_provider Abstract base class for DAV resource providers.
wsgidav.debug_filter WSGI middleware used for debugging (optional).
wsgidav.dir_browser WSGI middleware that handles GET requests on collections to display directories.
wsgidav.domain_controller Implementation of a domain controller that uses realm/username/password mappings from the configuration file and uses the share path as realm name.
wsgidav.error_printer WSGI middleware to catch application thrown DAVErrors and return proper responses.
wsgidav.fs_dav_provider Implementation of a DAV provider that serves resource from a file system.
wsgidav.http_authenticator WSGI middleware for HTTP basic and digest authentication.
wsgidav.lock_manager Implements the LockManager object that provides the locking functionality.
wsgidav.lock_storage Implements two storage providers for LockManager.
wsgidav.middleware Abstract base middleware class
wsgidav.property_manager Implements two property managers: one in-memory (dict-based), and one persistent low performance variant using shelve.
wsgidav.request_resolver WSGI middleware that finds the registered mapped DAV-Provider, creates a new RequestServer instance, and dispatches the request.
wsgidav.request_server WSGI application that handles one single WebDAV request.
wsgidav.rw_lock ReadWriteLock
wsgidav.util Miscellaneous support functions for WsgiDAV.
wsgidav.wsgidav_app WSGI container, that handles the HTTP requests.
wsgidav.xml_tools Small wrapper for different etree packages.

Package wsgidav.addons

wsgidav.addons.couch_property_manager Implements a property manager based on CouchDB.
wsgidav.addons.hg_dav_provider
wsgidav.addons.mongo_property_manager Implements a property manager based on MongoDB.
wsgidav.addons.mysql_dav_provider Implementation of a WebDAV provider that provides a very basic, read-only resource layer emulation of a MySQL database.
wsgidav.addons.nt_domain_controller Implementation of a domain controller that allows users to authenticate against a Windows NT domain or a local computer (used by HTTPAuthenticator).

Package wsgidav.samples

wsgidav.samples.dav_provider_tools Tools that make it easier to implement custom WsgiDAV providers.
wsgidav.samples.mongo_dav_provider Implementation of a WebDAV provider that provides a very basic, read-only resource layer emulation of a MongoDB database.
wsgidav.samples.virtual_dav_provider Sample implementation of a DAV provider that provides a browsable, multi-categorized resource tree.

Package wsgidav.server

wsgidav.server.ext_wsgiutils_server ext_wsgiutils_server.py is an extension of the wsgiutils server in Paste.
wsgidav.server.run_reloading_server Wrapper for server_cli, that restarts the server when source code is modified.
wsgidav.server.server_cli server_cli
wsgidav.server.server_sample Simple example how to a run WsgiDAV in a 3rd-party WSGI server.

Attention

You are looking at outdated documentation for version 2.x. A newer version is available.

Index

Attention

You are looking at outdated documentation for version 2.x. A newer version is available.

Development

This section describes how developers can contribute to the WsgiDAV project.

First off, thanks for taking the time to contribute!

There are many ways you can help:

  • Send feedback:
    Know a cool project that uses it, created a custom provider or have an interesting use case? Let us know in the forum .
  • Create issues for bugs or feature requests (see Bug Reports and Feature Requests below).
  • Help others, by answering questions in the forum or on Stackoverflow.
  • Improve this documentation.
  • Fix bugs or propose features.

This small guideline may help taking the first steps.

Happy hacking :)

Bug Reports and Feature Requests

If you have encountered a problem with WsgiDAV or have an idea for a new feature, please submit it to the issue tracker on GitHub.

Note

The issue tracker is for bugs and feature requests. Please use the Q&A forum or Stackoverflow to ask questions.

Use the search function to find existing issues if any. If you have additional information, add a comment there instead of creating a new issue.

If it’s a bug report:

  • Carefully describe the required steps to reproduce the failure.
  • Give additional information about the server: (OS version, WsgiDAV version, WSGI server setup)? Which settings are enabled (configuration file)?
  • What client are you using (OS, client software and -version)?
  • What output do you see on the console or log files?
  • Maybe attach a patch file or describe a potential fix?

If it’s a feature request:

  • What are you trying to accomplish?
  • Why is this a cool feature? Give use cases.
  • Can you propose a specification? Are there similar implementations in other projects? Add references or screenshots if you have some. Remember that the general API must stay generic, extensible, and consistent. How will this interfere with existing API and functionality? Does it play well with the other extensions?

Contributing Code

Note

Please open (or refer to) an issue, even if you provide a pull request. It will be useful to discuss different approaches or upcoming related problems.

The recommended way for new contributors to submit code to WsgiDAV is to fork the repository on GitHub and then submit a pull request after committing the changes. The pull request will then need to be approved before it is merged.

  1. Check for open issues or open a fresh issue to start a discussion around a feature idea or a bug.
  2. Fork the repository on GitHub.
  3. Clone the repo to your local computer
  4. Setup the project for development
  5. Create and activate your feature branch
  6. Hack, Hack, Hack
  7. Test, Test, Test
  8. Send a pull request and bug the maintainer until it gets merged and published.

Note

Don’t mix different topics in a single commit or issue. Instead submit a new pull request (and even create separate branches as apropriate).

Setup for Development

Fork the Repository
  1. Create an account on GitHub.

  2. Fork the main WsgiDAV repository (mar10/wsgidav) using the GitHub interface.

  3. Clone your forked repository to your machine.

    git clone https://github.com/YOUR_USERNAME/wsgidav
    cd wsgidav
    
  4. Create and activate a new working branch. Choose any name you like.

    git checkout -b feature-xyz
    
Create and Activate a Virtual Environment

Virtual environments allow us to develop and test in a sandbox, without affecting our system othwerwise.
We need Python 2.7, Python 3.4+, and pip on our system.

If you want to run tests on all supported platforms, install Python 2.7, 3.4, 3.5, and 3.6.

On Linux/OS X, we recommend to use pipenv to activate a virtual environment:

$ cd /path/to/wsgidav
$ pipenv shell
bash-3.2$

Note: On Ubuntu you additionally may have to install apt-get install python3-venv.

Alternatively (especially on Windows), use virtualenv to create and activate the virtual environment. For example using Python’s builtin venv (instead of virtualenvwrapper) in a Windows PowerShell:

> cd /path/wsgidav
> py -3.6 -m venv c:\env\wsgidav_py36
> c:\env\wsgidav_py36\Scripts\Activate.ps1
(wsgidav_py36) $
Install Requirements

Now that the new environment exists and is activated, we can setup the requirements:

$ pip install -r requirements-dev.txt

and install wsgidav to run from source code:

$ pip install -e .

If everything is cool, this code should now run:

$ wsgidav --version
$ 2.3.1

The test suite should run as well:

$ tox

Hack, Hack, Hack

Note

Follow the Style Guide, basically PEP 8.

We prefer to use double quotes and allow a max. line length of 99 characters.

Failing tests or not follwing PEP 8 will break builds on travis and therefore be automatically rejected.

Run $ flake8 and $ tox frequently and before you commit!

Test, Test, Test

Testing is best done through tox, which provides a number of targets and allows testing against multiple different Python environments:

  • To run all unit tests on all suppprted Python versions inclusive flake8 style checks and code coverage:

    $ tox
    
  • To run unit tests for a specific Python version, such as 3.6:

    $ tox -e py36
    
  • To run selective tests, ww can call py.test directly, e.g.:

    $ py.test -ra wsgidav tests/test_util.py
    
  • To list all possible targets (available commands):

    $ tox -av
    
  • To build the Sphinx documentation:

    $ tox -e docs
    

Gain additional Kudos by first adding a test that fails without your changes and passes after they are applied.
New unit tests should be included in the tests directory whenever possible.

Create a Pull Request

The proposed procedure is:

  1. Make sure you have forked the original repository on GitHub and checked out the new fork to your computer as described above.

  2. Create and activate your feature branch (git checkout -b my-new-feature).

  3. Commit your changes (git commit -am “Added some cool feature”).

  4. Push to the branch (git push origin my-new-feature).

  5. Create a new Pull Request on GitHub.

  6. Please add a bullet point to ../../CHANGELOG.md if the fix or feature is not trivial (small doc updates, typo fixes). Then commit:

    git commit -m '#42: Add useful new feature that does this.'
    

    GitHub recognizes certain phrases that can be used to automatically update the issue tracker.

    For example:

    git commit -m 'Closes #42: Fix invalid markup in docstring of Foo.bar.'
    

    would close issue #42.

  7. Wait for a core developer to review your changes.

Attention

You are looking at outdated documentation for version 2.x. A newer version is available.

Release Info

# Changelog

## 2.4.2 / Unreleased

## 2.4.1 / 2018-06-16

  • Fix some logging exceptions
  • Fix exception in CLI config reader (Py2)

## 2.4.0 / 2018-05-30

  • Improve configuration files: - YAML is now the preferred configuration file format. - Add support for JSON config files (JavaScript-style comments allowed) (#89) - Use wsgidav.yaml, wsgidav.json, or wsgidav.conf by default if they exist in the local folder
  • Expand ‘~’ in –root and –config command line options
  • #97: Fix assumption that QUERY_STRING is in environment (dir_browser)
  • #99: Fix virtual_dav_provider for Py3: WSGI expects binary instead of str
  • #100: Send ETags with PUT response
  • #101: Fail cleanly if trying to PUT to unknown collection
  • Refactor logging: - Re-define verbosity level range: 0..5 - Remove usage of print in favor of logging.getLogger().debug - Remove util.note(), .status(), … helpers
  • Refactor code base: - Use .format() syntax instead of %s for string templating - Mandatory PEP 8 compliance (checked by flake8)
  • Rework documentation on Read The Docs
  • MSI setup uses Cheroot version 6.2+

## 2.3.0 / 2018-04-06

  • #80: Drop support for Python 3.3 (end-of-life September 2017)
  • #86: Give custom PropertyManager implementations access to the environ
  • #87: Don’t assume sys.stdout.encoding is not None
  • #90: Custom response headers
  • #93: Add support for streaming large files on Mac

## 2.2.4 / 2017-08-11

  • Fix #75: Return 401 when auth method is not supported
  • Fix #77: removeProperty call to not lose dryRun, otherwise removeProperty is called twice for real
  • Fix #79: Prevent error when missing environment variable

## 2.2.2 / 2017-06-23

  • #69: Rectifying naming disparity for CherryPy server
  • Fix #67: lock manager returns timeout negative seconds
  • Fix #71: Attempts to unlock a nonexistent resource cause an internal server error
  • Fix #73: Failed on processing non-iso-8859-1 characters on Python 3
  • MSI setup uses Python 3.5

## 2.2.1 / 2017-02-25

  • #65: Support for Cheroot server, which is the standalone WSGI server of CherryPy since version 9.0. –server=cheroot is the default now.
  • New option –ssl-adapter, used by ‘cheroot’ server if SSL certificates are configured. Defaults to ‘builtin’.<br> Set to ‘pyopenssl’ to use an existing OpenSSL nstallation. (Note: Currently broken as of Cheroot 5.1, see cherrypy/cheroot#6)
  • Deprecate cherrypy.wsgiserver.<br> –server=cherrypy was renamed to –cherrypy-wsgiserver
  • #64: Fix LOCK without owner
  • #65: Add lxml to MSI installer
  • Release as Wheel

## 2.1.0 / 2016-11-13

  • #42: Remove print usage in favor of logging (Sergi Almacellas Abellana)
  • #43: PEP8 fixes (Sergi Almacellas Abellana, Tom Viner)
  • #45 New method _DAVResource.finalizeHeaders(environ, responseHeaders) (Samuel Fekete)
  • #55 Custom response handlers for PUT, GET etc.
  • New helpers addons.stream_tools.FileLikeQueue and StreamingFile allow to pipe / proxy PUT requests to external consumers.

## 2.0.1 / 2016-10-07

  • #46 Wrap xml libraries with the equivalent defusedxml packages (Tom Viner)

## 2.0.0 / 2016-10-02

  • #4: Support Python 3
  • Windows MSI Installer
  • Drop support for Python 2.6
  • cherrypy.wsgiserver is no longer included as source package. CherryPy is still the recommended standalone WSGI server, and deployed with the binary installation. It is also installed as dependency by setup.py test. However if a source installation is used, either install cherrypy using pip install cherrypy or choose another server using the –server option.
  • Configuration: - New options server and server_args - Removed ext_servers option
  • Standalone server: - New command line option –server (defaults to cherrypy) - New command line option –no-config - Removed command line option -d use -vv instead
  • Use py.test & tox

## 1.3.0 / 2016-08-24

  • #19: Add option mutable_live_props to support setting last modified file/directory timestamps (Jonas Bardino)
  • #23: Fix Windows file manager and OSX Finder fails on file names with comma (Jonas Bardino)
  • #27: Do not install tests (Erich Seifert)
  • #28: New option trusted_auth_header allows reverse proxy authentication (Mageti)
  • #30: API change to allow much easier per-user chrooting (Jonas Bardino)
  • #32: Fix digest authentication rejected due to invalid header (Adrian Crețu)

## 1.2.0 / 2015-05-14

  • #8: Unquote PATH_INFO is now optional ‘unquote_path_info’; defaults to false. (Brian Sipos)
  • #9: Fixed status codes for apache mod_wsgi (Brian Sipos)
  • #10: Hotfix for file copy on GVFS (Brian Sipos)
  • #12: Configurable middleware stack (Pavel Shiryaev)
  • #15: Fix Finder access (Jonas Bardino)

## 1.1.0 / 2014-01-01

  • New dir_browser option ‘ms_sharepoint_plugin’ to start MS Office documents in edit-mode
  • Moved project from Google Code to GitHub
  • Moved documentation to ReadTheDocs

## 1.0.0 / 2013-12-27

  • NOTE: no longer tested with Python 2.4.
  • SSL sample with bogo-cert
  • Renamed ‘msmount’ option to ‘ms_mount’.
  • Files are always stored in binary mode.
  • Port and hostname can now be specified in config file (before: command line only).
  • New option for dir_browser: ‘msSharepointUrls’ will prepend ‘ms-word:ofe|u|’ to URL for MS Offce documents.
  • New option ‘add_header_MS_Author_Via = True’ to support editing with Microsoft Office
  • FilesystemProvider expands variables like ‘~’, ‘$Name’ and ‘%NAME%’ in folder paths (i.e. ‘~/foo’ -> ‘/Users/joe/foo’)
  • Issue #55 Failure operating with litmus test suite, Mac OS X WebDAV Client, Windows 7 (thanks to Ben Allums)
  • Fixed issue #48 Allow the dirbrowser to be configured from the config file (thanks to Herman Grecco)
  • Fixed issue #43 Unicode error in Ubuntu
  • Allow Ctrl-C / SIGINT to stop CherryPyWSGIServer
  • Made mimetype guessing more robust
  • Updated CherryPy standalone WSGI server to 3.2.4
  • Support ‘setup.py test’ which uses nosetests and includes litmus

## 0.5.0 / 2011-01-16

  • Bundled with CherryPy standalone WSGI server
  • Added copyright notes for original PyFileServer
  • Changed signature of DAVProvider (and derived classes): provider argument was removed
  • New method DAVResource.getMemberList() replaces getMemberNames().
  • New class DAVCollection allows for more efficient implementation of custom providers.
  • Forcing ‘Connection: close’, when a required Content-Length is missing. So it’s possible now to return GET responses without knowing the size.
  • New property manager based on CouchDB (addons.couch_property_manager)
  • New property manager based on MongoDB (addons.mongo_property_manager)
  • New sample DAV provider for MongoDBs (samples.mongo_dav_provider)
  • Debug output goes to stdout (was stderr)
  • Support davmount (rfc 4709).
  • Added support for Microsoft FOLDER behavior.
  • renamed displayType() -> getDirectoryInfo()
  • Fixed RANGE response

## 0.4.0.b3

  • Refactored LockManager. using separate LockStorage
  • Bugfixes

## 0.4.0.b2 / 2010-02-15

  • Bugfixes

## 0.4.0.b1

  • Using HTTP/1.1 with keep-alive (St�phane KLEIN)
  • Correctly return pre- and postconditions on lock conflicts.
  • Added Sphinx docs
  • Added Mercurial provider
  • Changed configuration: no property manager used by default

## Until 0.4.0 alpha

See https://github.com/mar10/wsgidav/blob/master/doc/changelog04.md

Main Features

  • Comes bundled with a server and a file system provider, so we can share a directory right away from the command line.
  • Designed to run behind any WSGI compliant server.
  • Tested with different clients on different platforms (Windows, Linux, Mac).
  • Supports online editing of MS Office documents.
  • Contains a simple web browser interface.
  • SSL support
  • Support for authentication using Basic or Digest scheme.
  • Passes the litmus test suite.
  • Open architecture allows to write custom providers (i.e. storage, locking, authentication, virtual file systems, …).
  • WsgiDAV is a refactored version of PyFileServer written by Ho Chun Wei.

Quickstart

Releases are hosted on PyPI. Install WsgiDAV (and a server) like:

$ pip install cheroot wsgidav

To serve the /tmp folder as WebDAV / share, simply run:

$ wsgidav --host=0.0.0.0 --port=80 --root=/tmp

Note

MS Windows users that only need the command line interface may prefer the MSI installer.

Supported Clients

WsgiDAV comes with a web interface and was tested with different clients (Windows File Explorer and drive mapping, MS Office, Ubuntu, Mac OS X, …).

_images/teaser.png