WebDAV¶
Description
WebDAV is a protocol to manage your site directly from MS Windows Explorer and such. Plone supports WebDAV without add-ons.
Introduction¶
WebDAV is enabled by default. A Zope server listening on port 8080 will also accept WebDAV traffic on that port. For common cases, client-side tools should work reasonably well. (http://plone.293351.n2.nabble.com/webdav-status-td7570063.html) (http://stackoverflow.com/questions/9127269/how-can-i-stop-people-accessing-a-plone-server-via-webdav)
Enabling WebDAV on an extra port in Zope¶
Modify your buildout configuration's client setup to add a webdav address:
Short buildout.cfg
example:
[instance]
...
recipe = plone.recipe.zope2instance
...
webdav-address=1980
...
Alternative buildout.cfg
configuration snippet which might be needed for
some WebDAV clients:
[instance]
...
zope-conf-additional =
enable-ms-author-via on
<webdav-source-server>
address YOUR_SERVER_PUBLIC_IP_ADDRESS_HERE:1980
force-connection-close off
</webdav-source-server>
These snippets will be in the generated parts/instance/etc/zope.conf
after buildout has been re-run.
This will enable the WebDAV server on http://www.mydomain.com:1980/. Note
that you cannot use this URL in your web browser, just in WebDAV clients.
Using the web browser will give you an error message AttributeError:
manage_FTPget
. You could also just run the WebDAV server on localhost
with address 1980, forcing you to either use a WebDAV client locally or
proxy WebDAV through Apache.
Disabling WebDAV¶
You can't disable WebDAV in Plone itself, it's tightly integrated in Zope. You could take away the "Access WebDAV" permission from everyone, but the Zope server will still answer each request.
What you can do: Make your web server filter out the WebDAV commands. This will stop WebDAV requests from reaching your Zope server.
Nginx¶
For nginx, this is done by adding:
dav_methods off
to the server block in your nginx.conf. (http://wiki.nginx.org/HttpDavModule)
If you do not use the HttpDavModule, you can add:
limit_except GET POST {
deny all;
}
to the location block.
Apache¶
For Apache, you can use the limit
statement, see http://httpd.apache.org/docs/current/mod/core.html#limit
Supporting WebDAV in your custom content¶
Please read more about it in the Dexterity WebDAV manual.
WebDAV notes¶
WebDAV uses a number of HTTP verbs to perform different operations. The following notes describe how they are implemented in Zope 2 and Dexterity.
Background¶
Basic WebDAV support can be found in the webdav
package. This defines two
base classes, webdav.Resource.Resource
and
webdav.Collection.Collection
. Collection
extends Resource
. These
are mixed into item and container content objects, respectively.
The webdav package also defines the NullResource
object. A
NullResource
is a kind of placeholder, which supports the HTTP verbs HEAD
,
PUT
, and MKCOL
.
Containers based on ObjectManager
(including those in Dexterity) will
return a NullResource
if they cannot find the requested object and the
request is a WebDAV request.
The zope.filerepresentation
package defines a number of interfaces which
are intended to help manage file representations of content objects. Dexterity
uses these interfaces to allow the exact file read and write operations to
be overridden without subclassing.
HEAD
¶
A HEAD
request retrieves headers only.
Resource.HEAD()
sets Content-Type
based on self.content_type()
,
Content-Length
based on self.get_size()
, Last-Modified
based on
self._p_mtime
, and an ETag based on self.http__etag()
, if available.
Collection.HEAD()
looks for self.index_html.HEAD()
and returns its
value if that exists. Otherwise, it returns a 405 Method Not Allowed
response.
If there is no index_html
object, it returns 404 Not Found
.
GET
¶
A GET
request retrieves headers and body.
Zope calls manage_DAVget()
to retrieve the body. The default
implementation calls manage_FTPget()
.
In Dexterity, manage_FTPget()
adapts self
to IRawReadFile
and uses
its mimeType
and encoding
properties to set the Content-Type
header, and its size()
method to set Content-Length
.
If the IRawReadFile
adapter is also an IStreamIterator
, it will be
returned for the publisher to consume directly. This provides for efficient
serving of large files, although it does require that the file can be read
in its entirety with the ZODB connection closed. Dexterity solves this problem
by writing the file content to a temporary file on the server.
If the IRawReadFile
adapter is not a stream iterator, its contents are
returned as a string, by calling its read()
method. Note that this loads
the entire file contents into memory on the server.
The default IRawReadFile
implementation for Dexterity content returns an
RFC 2822 style message document. Most fields on the object and any enabled
behaviours will be turned into UTF-8 encoded headers. The primary field, if
any, will be returned in the body, also most likely encoded as an UTF-8
encoded string. Binary data may be base64 encoded instead.
A type which wishes to override this behaviour can provide its own adapter. For example, an image type could return the raw image data.
PUT
¶
A PUT
request reads the body of a request and uses it to update a resource
that already exists, or to create a new object.
By default Resource.PUT()
fails with 405 Method Not Allowed
. That is, it
is not by default possible to PUT
to a resource that already exists. The same
is true of Collection.PUT()
.
In Dexterity, the PUT()
method is overridden to adapt self to
zope.filerepresentation.IRawWriteFile
, and call its write()
method one
or more times, writing the contents of the request body, before calling
close()
. The mimeType
and encoding
properties will also be set
based on the value of the Content-Type
header, if available.
The default implementation of IRawWriteFile
for Dexterity objects assumes
the input is an RFC 2822 style message document. It will read header values
and use them to set fields on the object or in behaviours, and similarly read
the body and update the corresponding primary field.
NullResource.PUT()
is responsible for creating a new content object and
initialising it (recall that a NullResource
may be returned if a WebDAV
request attempts to traverse to an object which does not exist). It sniffs the
content type and body from the request, and then looks for the
PUT_factory()
method on the parent folder.
In Dexterity, PUT_factory()
is implemented to look up an IFileFactory
adapter on self and use it to create the empty file. The default
implementation will use the content_type_registry
tool to determine a
type name for the request (e.g. based on its extension or MIME type), and
then construct an instance of that type.
Once an instance has been constructed, the object will be initialised by
calling its PUT()
method, as above.
Note that when content is created via WebDAV, an IObjectCreatedEvent
will
be fired from the IFileFactory
adapter, just after the object has been
constructed. At this point, none of its values will be set. Subsequently,
at the end of the PUT()
method, an IObjectModifiedEvent
will be fired.
This differs from the event sequence of an object created through the web.
Here, only an IObjectCreatedEvent
is fired, and only after the object
has been fully initialised.
DELETE
¶
A DELETE
request instructs the WebDAV server to delete a resource.
Resource.DELETE()
calls manage_delObjects()
on the parent folder to delete
an object.
Collection.DELETE()
does the same, but checks for write locks of all
children of the collection, recursively, before allowing the delete.
PROPFIND
¶
A PROPFIND
request returns all or a set of WebDAV properties. WebDAV
properties are metadata used to describe an object, such as the last modified
time or the author.
Resource.PROPFIND()
parses the request and then looks for a
propertysheets
attribute on self.
If an allprop
request is received, it calls dav__allprop()
, if
available, on each property sheet. This method returns a list of name/value
pairs in the correct WebDAV XML encoding, plus a status.
If a propnames
request is received, it calls dav__propnames()
, if
available, on each property sheet. This method returns a list of property
names in the correct WebDAV XML encoding, plus a status.
If a propstat
request is received, it calls dav__propstats()
, if
available, on each property sheet, for each requested property. This method
returns a property name/value pair in the correct WebDAV XML encoding, plus a
status.
The PropertyManager
mixin class defines the propertysheets
variable to
be an instance of DefaultPropertySheets
. This in turn has two property
sheets, default
, a DefaultProperties
instance, and webdav
, a
DAVProperties
instance.
The DefaultProperties
instance contains the main property sheet. This
typically has a title
property, for example.
DAVProperties
will provides various core WebDAV properties. It defines a
number of read-only properties: creationdate
, displayname
,
resourcetype
, getcontenttype
, getcontentlength
, source
,
supportedlock
, and lockdiscovery
. These in turn are delegated to
methods prefixed with dav__
, so e.g. reading the creationdate
property
calls dav__creationdate()
on the property sheet instance. These methods
in turn return values based on the property manager instance (i.e. the
content object). In particular:
creationdate
- returns a fixed date (January 1st, 1970).
displayname
- returns the value of the
title_or_id()
method resourcetype
- returns an empty string or <n:collection/>
getlastmodified
- returns the ZODB modification time
getcontenttype
- delegates to the
content_type()
method, falling back on thedefault_content_type()
method. In Dexterity,content_type()
is implemented to look up theIRawReadFile
adapter on the context and return the value of itsmimeType
property. getcontentlength
- delegates to the
get_size()
method (which is also used for the "size" column in Plone folder listings). In Dexterity, this looks up azope.size.interfaces.ISized
adapter on the object and callssizeForSorting()
. If this returns a unit of'bytes'
, the value portion is used. Otherwise, a size of 0 is returned. source
- returns a link to
/document_src
, if that attribute exists supportedlock
- indicates whether
IWriteLock
is supported by the content item lockdiscovery
- returns information about any active locks
Other properties in this and any other property sheets are returned as stored when requested.
If the PROPFIND
request specifies a depth of 1 or infinity
(i.e. the client wants properties for items in a collection),
the process is repeated for all
items returned by the listDAVObjects()
methods,
which by default returns
all contained items via the objectValues()
method.
PROPPATCH
¶
A PROPPATCH
request is used to update the properties on an existing object.
Resource.PROPPATCH()
deals with the same types of properties from property
sheets as PROPFIND()
. It uses the PropertySheet
API to add or update
properties as appropriate.
MKCOL
¶
A MKCOL
request is used to create a new collection resource, i.e. create a
new folder.
Resource.MKCOL()
raises 405 Method Not Allowed, because the resource
already exists (remember that in WebDAV, the MKCOL
request, like a PUT
for a
new resource, is sent with a location that specifies the desired new resource
location, not the location of the parent object).
NullResource.MKCOL()
handles the valid case where a MKCOL
request has
been sent to a new resource. After checking that the resource does not already
exist, that the parent is indeed a collection (folderish item), and that the
parent is not locked, it calls the MKCOL_handler()
method on the parent
folder.
In Dexterity, MKCOL()_handler
is overridden to adapt self to an
IDirectoryFactory
from zope.filerepresentation
and use this to create
a directory. The default implementation simply calls manage_addFolder()
on the parent. This will create an instance of the Folder
type.
COPY
¶
A COPY
request is used to copy a resource.
Resource.COPY()
implements this operation using the standard Zope content
object copy semantics.
MOVE
¶
A MOVE
request is used to relocate or rename a resource.
Resource.MOVE()
implements this operation using the standard Zope content
object move semantics.
LOCK
¶
A LOCK
request is used to lock a content object.
All relevant WebDAV methods in the webdav
package are lock aware.
That is,
they check for locks before attempting any operation that would violate a
lock.
Also note that plone.locking
uses the lock implementation from the
webdav
package by default.
Resource.LOCK()
implements locking and lock refresh support.
NullResource.LOCK()
implements locking on a NullResource
. In effect,
this means locking the name of the non-existent resource. When a
NullResource
is locked, it is temporarily turned into a
LockNullResource
object, which is a persistent object set onto the
parent (remember that a NullResource
is a transient object returned
when a child object cannot be found in a WebDAV request).
UNLOCK
¶
An UNLOCK
request is used to unlock a locked object.
Resource.UNLOCK()
handles unlock requests.
LockNullResource.UNLOCK()
handles unlocking of a LockNullResource
.
This deletes the LockNullResource
object from the parent container.
Fields on container objects¶
When browsing content via WebDAV, a container object (folderish item) will
appear as a folder. Most likely, this object will also have content in the
form of schema fields. To make this accessible, Dexterity containers expose
a pseudo-file with the name '_data', by injecting this into the return value
of listDAVObjects()
and adding a special traversal hook to allow its
contents to be retrieved.
This pseudo-file supports HEAD
, GET
, PUT
, LOCK
,
UNLOCK
, PROPFIND
and PROPPATCH
requests
(an error will be raised if the user attempts to rename, copy, move
or delete it). These operate on the container object, obviously.
For example, when the data object is updated via a PUT
request,
the PUT()
method on the container is called,
by default delegating to an IRawWriteFile
adapter on the container.