CL-WEBDAV - A WebDAV server written in Common Lisp





Abstract CL-WEBDAV is a WebDAV server written in Common Lisp. It aims to be as flexible as possible, allowing you to completely customize the way resources are handled. The code comes with a BSD-style license so you can basically do with it whatever you want. Download shortcut: http://weitz.de/files/cl-webdav.tar.gz.

The current development version of CL-WEBDAV can be found at GitHub, https://github.com/edicl/cl-webdav. This is the one to send patches against. Use at your own risk.

An unofficial Mercurial repository of older versions is available at http://arcanes.fr.eu.org/~pierre/2007/02/weitz/ thanks to Pierre Thierry.





http://localhost:4242/

/tmp/

If you want to send patches, please read this first.





I ended up not using CL-WEBDAV for the tutorial, though, as I came to the conclusion that there was too much stuff in it that would distract from the actual subject. But as it was more or less complete, I invested some more time for testing, polishing, cleanup, and documentation, and then released it.

The initial release has been tested mildly with LispWorks on Windows and with SBCL on Linux against cadaver, WebDrive, and the native Windows XP client. I currently (April 2007) don't use CL-WEBDAV myself, so I can't say much about its usefulness, but here's a list of potential pitfalls that come to mind:

CL-WebDAV is only class 1 compliant at the moment, i.e. there's no support for locks. (And as a consequence it won't work with the native client of OS X IIRC - I don't have a Mac to test with.) Class 2 compliance shouldn't be too hard to add, though. You can bribe me if you desperately need it... :)

There's no portable way to implement a correct RESOURCE-CREATION-DATE method for file resources, but it can certainly be done using implementation-specific functionality. I'll happily accept patches for this.

method for file resources, but it can certainly be done using implementation-specific functionality. I'll happily accept patches for this. XML handling in CL-WEBDAV isn't particularly efficient and elegant, but I don't expect this to be an issue, as the XML documents exchanged between WebDAV clients and servers are usually pretty small.

The FILE-RESOURCE class doesn't use Common Lisp's pathname algebra but rather deals with strings for obvious reasons. Again, I'll gladly accept patches to make this more elegant.

class doesn't use Common Lisp's pathname algebra but rather deals with strings for obvious reasons. Again, I'll gladly accept patches to make this more elegant. There are several issues with the "WebDAV Mini-Redirector" of Windows XP and Microsoft's "Web Folder Client" (follow both links for more info). These are obviously not specific to CL-WEBDAV, but you should be aware of them before you send bug reports...

RESOURCE

RESOURCE



[Standard class]

resource



This is the base class you'll have to subclass if you want to create your own custom WebDAV server. Each object of this class represents one resource on the server and most of the time these objects are created by the server using only the :SCRIPT-NAME initarg. If you need more initialization to happen, write an :AFTER method for INITIALIZE-INSTANCE . See the file file-resources.lisp for an example of a subclass of RESOURCE .



[Generic accessor]

resource-script-name resource => script-name

(setf ( resource-script-name resource ) script-name )



The base class RESOURCE has only one slot which can be manipulated with this accessor. If a resource is created by the server, then it will fill this slot with the script name that was used to access the resource. In this case you should only read the slot's value. If you create your own RESOURCE objects (for example in RESOURCE-CHILDREN ), you should set the value of this slot to a string that could be used as a script name to retrieve the resource. There are several places where CL-WEBDAV is calling this function internally, so it is important that you use a meaningful value.



[Special variable]

*resource-class*



Whenever a DAV handler is executed, this variable should be bound to the resource class which is to be used. If you're using CREATE-DAV-DISPATCHER , this will already be taken care of for you, so you can ignore this variable.

FILE-RESOURCE



[Generic function]

resource-exists resource => generalized-boolean



This function must return a true value if the resource resource exists on the server and NIL otherwise. You must specialize this generic function for your own classes.



[Generic function]

resource-children resource => children



This function must return a list of all children of resource (which themselves are RESOURCE objects). You must specialize this generic function for your own classes.



[Generic function]

resource-parent resource => parent



This function must return a RESOURCE object which is the parent resource of resource or NIL if there is no parent. You must specialize this generic function for your own classes.



[Generic function]

resource-collection-p resource => generalized-boolean



This function must return a true value iff the resource resource is a collection. You must specialize this generic function for your own classes.



[Generic function]





This function must return a universal time denoting the time the resource resource was last modified. You must specialize this generic function for your own classes.



[Generic function]

resource-length resource => length



This function must return an integer denoting the length of the resource resource in octets. You must specialize this generic function for your own classes.



[Generic function]

resource-display-name resource => display-name



This function must return a string which, according to the WebDAV RFC, "provides a name for the resource that is suitable for presentation to a user." You must specialize this generic function for your own classes.



[Generic function]

send-content resource stream => whatever



This function is called for GET requests and must send the complete contents of the (non-collection) resource resource to the (flexi) stream stream . The return value is irrelevant.



[Generic function]

get-content resource stream length => whatever



This function is called for PUT requests and must read length octets of data from the (flexi) stream stream and store them in a place appropriate for the resource resource . The return value is irrelevant.



[Generic function]

remove-resource resource => whatever



This function must completely remove the resource resource . It doesn't have to deal with dead properties, and it can assume that resource doesn't have children in case it's a collection. The return value is irrelevant.



[Generic function]

move-resource source destination => whatever



This function must "move" the (contents of the) resource source in such a way that it can in the future be accessed as destination . It doesn't have to deal with dead properties, and it can assume that source doesn't have children in case it's a collection. The return value is irrelevant.



[Generic function]

copy-resource source destination => whatever



This function must "copy" the (contents of the) resource source in such a way that the copy can in the future be accessed as destination . It doesn't have to deal with dead properties, and it can assume that source doesn't have children in case it's a collection. The return value is irrelevant.



[Generic function]

create-collection resource => whatever



This function must create a collection resource that in the future can be accessed as resource . The return value is irrelevant.



[Generic function]

accept-request-p resource-class request => generalized-boolean



This must be a function which accepts a Hunchentoot request object request and returns a generalized boolean denoting whether request is a resource the DAV server wants to handle. It will be called by the dispatcher created with CREATE-DAV-DISPATCHER . Usually, you'll want to look at the script name of the request or something like that - see the class FILE-RESOURCE for an example. Note that you specialize this function on the resource class (or its name) and not on the resource like the other functions in this subsection.



[Generic function]





This function must return a universal time denoting the time the resource resource was created. There's a default method which returns RESOURCE-WRITE-DATE , but most likely you'll want to specialize this for you own classes and return a more meaningful value.



[Generic function]

resource-content-type resource => type-string



This function must return a string denoting the MIME type of the resource resource . It will only be called if resource is not a collection. There's a default method which always returns "application/octet-stream" , but most likely you'll want to specialize this for your own classes.



[Generic function]

resource-content-language resource => language



This function should return either NIL or a language tag as defined in section 14.13 of RFC 2068. If the value returned by this function is not NIL , it will also be used as the Content-Language header returned for GET requests. There's a default method which always returns NIL .



[Generic function]

resource-source resource => xmls-node



This function should return either NIL or a DAV "source" XML node (structured as an XMLS node) that, according to the WebDAV RFC, "identifies the resource that contains the unprocessed source of the link's source." There's a default method which always returns NIL .



[Generic function]

resource-etag resource => etag



This function should return an ETag for the resource resource or NIL . If the value returned by this function is not NIL , it will also be used as the ETag header returned for GET requests. There's a default method which synthesizes a value based on the script name and the write date of the resource, and in most cases you probably don't need to specialize this function.



[Generic function]

resource-type resource => xmls-node



This function should return either NIL or a DAV "resourcetype" XML node (structured as an XMLS node) that, according to the WebDAV RFC, "specifies the nature of the resource." There's a default method which returns something fitting for collections and NIL otherwise, and in most cases you probably don't need to specialize this function.



[Generic function]

resource-uri-prefix resource => prefix-string



This function must return a string which is the part of a resource's HTTPS or HTTPS URI that comprises the scheme, the host, and the port and ends with a slash - something like "http://localhost:4242/" or "https://www.lisp.org/" . The default method synthesizes this from the information Hunchentoot provides and usually you only have to write your own method if you're sitting behind a proxy.

RESOURCE-TYPE

We're representing XML as XMLS nodes which are very similar to CXML's XMLS nodes but try to get namespaces right because they don't purport to be compatible with XMLS.



[Function]

xmls-node-p thing => generalized-boolean



Checks whether thing is an XMLS node.



[Function]

local-name thing => local-name



Returns the local name of the XMLS node or attribute thing .



[Function]

namespace-uri thing => namespace-uri



Returns the namespace URI (which can be NIL ) of the XMLS node or attribute thing .



[Accessor]

node-attributes xmls-node => attributes

(setf ( node-attributes xmls-node ) attributes )



Returns or sets the list of attributes of the XMLS node xmls-node .



[Accessor]

node-children xmls-node => children

(setf ( node-children xmls-node ) children )



Returns or sets the list of children of the XMLS node xmls-node .



[Function]

dav-node local-name &rest children => xmls-node



Returns an XMLS node with the local name local-name , the namespace URI "DAV:" , and the children children (a list of XMLS nodes and/or strings).



[Function]

parse-dav octets &optional root-name => xmls-node



Accepts an array octets of octets representing a DAV XML node and converts it into the corresponding XMLS node. According to the WebDAV RFC, non-DAV elements are skipped unless they appear in positions (like in a "prop" element) where arbitrary elements are allowed. If root-name is given, it should be the local name (a string) of a DAV node. In this case, the XML is validated. This function is expected to be called from within a Hunchentoot request and calls ABORT-REQUEST-HANDLER with a return code of +HTTP-BAD-REQUEST+ if a parsing error occurs or if the XML is invalid. This is kind of the inverse operation to SERIALIZE-XMLS-NODE .



[Function]

serialize-xmls-node xmls-node => octet-vector



Serializes xmls-node to a vector of octets which is returned. This is kind of the inverse operation to PARSE-DAV and very similar to CXML-XMLS:MAP-NODE .



[Generic function]

get-dead-properties resource => property-list



This function must return all dead properties of the resource resource as a list of XML elements structured as XMLS nodes. There's a default method but you should definitely specialize this for production servers.



[Generic function]

remove-dead-property resource property => whatever



This function must remove the currently stored dead property designated by property (an XMLS node) of the resource resource . There's a default method but you should definitely specialize this for production servers. The return value is irrelevant.



[Generic function]

set-dead-property resource property => whatever



This function must replace the currently stored dead property designated by property (an XMLS node) of the resource resource with property , i.e. property doubles as the property itself and as the property designator. There's a default method but you should definitely specialize this for production servers. The return value is irrelevant.



[Generic function]

remove-dead-properties resource => whatever



This function must remove all dead properties of the resource resource . There's a default method but you should definitely specialize this for production servers. The return value is irrelevant.



[Generic function]

move-dead-properties source destination => whatever



This function must move all dead properties of the resource source to the resource destination . There's a default method but you should definitely specialize this for production servers. The return value is irrelevant.



[Generic function]

copy-dead-properties source destination => whatever



This function must copy all dead properties of the resource source to the resource destination . There's a default method but you should definitely specialize this for production servers. The return value is irrelevant.

CREATE-DAV-DISPATCHER



[Generic function]

create-dav-dispatcher resource-class &optional ms-workaround-p => dispatcher



Creates and returns a dispatcher for the class resource-class which must be a subclass of RESOURCE . If ms-workaround-p is true (which is the default), OPTIONS requests are always handled irrespective of the results of ACCEPT-REQUEST-P - this is needed to work around problems with some Microsoft DAV clients.



[Function]

options-handler => nil



The handler for OPTIONS requests. Output is basically determined by *ALLOWED-METHODS* and *DAV-COMPLIANCE-CLASSES* .



[Function]

options-dispatcher request => handler



A dispatcher which'll dispatch to OPTIONS-HANDLER in case of an OPTIONS request and decline otherwise. This is only useful if you want to cater to Microsoft DAV clients which always unconditionally send OPTIONS requests to the "/" root resource. Sigh... If you use CREATE-DAV-DISPATCHER with a true value for ms-workaround-p , you don't need this dispatcher.



[Special variable]

*allowed-methods*



The list of methods (as keywords) returned by the Allow header in case of OPTIONS requests (and also utilized by the handler for MKCOL). The initial value is the list (:options :get :head :delete :propfind :proppatch :put :copy :move :mkcol) Can be adapted to allow for more methods, but for a WebDAV server at least the methods above should be listed.



[Special variable]

*dav-compliance-classes*



A sorted list of DAV compliance classes reported in the DAV header when answering OPTIONS requests. It doesn't make much sense to have more then class 1 in here as long as there's no lock support, i.e. the initial value is the list (1) .

FILE-RESOURCE

FILE-RESOURCE

RESOURCE

Again, this is not meant to be ready for a production system. You should at least make sure that properties are persisted.



[Standard class]

file-resource



A subclass of RESOURCE representing resources which are mapped to a subtree of the local file system.



[Generic function]

file-resource-base-path-namestring resource-class => namestring



This generic function is called for subclasses of FILE-RESOURCE to determine the base pathname that's currently being used, i.e. the part of the filesystem where the files served by the DAV server are stored. The function must return the namestring of the truename of an absolute pathname denoting a directory, specifically it must return a string starting and ending with slashes. (Note: This should work on Windows as well.) You can specialize this function (either on the class or on the name of the class) if you want. The default method returns the current value of *FILE-RESOURCE-BASE-PATH-NAMESTRING* .



[Special variable]

*file-resource-base-path-namestring*



The value of this variable is the return value of the default method for FILE-RESOURCE-BASE-PATH-NAMESTRING . It should be the namestring of the truename of an absolute pathname denoting a directory, specifically it must return a string starting and ending with slashes. (Note: This should work on Windows as well.) The initial value is the result of calling (namestring (truename (ensure-directories-exist "/tmp/"))) meaning that this directory will be created on your machine at load time if it doesn't exist (which is possible if you're on Windows and pretty unlikely on Linux or Unix).



[Generic function]

file-resource-base-uri resource-class => uri



This generic function is called for subclasses of FILE-RESOURCE to determine the base URI that's currently being used, i.e. the prefix the script name of a resource's URI must have in order to be valid. (In other words: this URI represents the top-level collection of the DAV server.) The function must return a string which starts with a slash if it's not empty and does not end with a slash and is not URL-encoded. You can specialize this function (either on the class or on the name of the class) if you want. The default method returns the current value of *FILE-RESOURCE-BASE-URI* .



[Special variable]

*file-resource-base-uri*



The value of this variable is the return value of the default method for FILE-RESOURCE-BASE-URI . It should be a string which starts with a slash if it's not empty and does not end with a slash and is not URL-encoded. The initial value is "" (the empty string).



[Standard class]

authorized-file-resource



A subclass of FILE-RESOURCE representing file resources which are associated with a certain user.

This documentation was prepared with DOCUMENTATION-TEMPLATE.

$Header: /usr/local/cvsrep/cl-webdav/doc/index.html,v 1.20 2009/02/17 20:17:30 edi Exp $

BACK TO MY HOMEPAGE