blob: ff19320c3154ecc192f0b9347395bf22e9704e32 [file] [log] [blame]
eZ component: Webdav, RFC overview, 1.1
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
:Author: Tobias Schlitt
:Revision: $Rev$
:Date: $Date$
:Status: Draft
.. contents::
Scope
~~~~~
This document tries to summarize the major points of `RFC 2518`_ (WebDAV) and
the associated `RFC 2616`_ (HTTP/1.1) in respect to distributed editing. The
main points here are the support of entity tags and locking. This document only
summarizes the RFCs and contains small design related hints here and there. The
main design for the functionality analyzed here is contained in the
design-1.1.txt file.
.. _`RFC 2518`: http://www.ietf.org/rfc/rfc2518.txt
.. _`RFC 2616`: http://www.ietf.org/rfc/rfc2616.txt
Entity Tags
~~~~~~~~~~~
Entity tags are generally used in the HTTP/1.1 protocol to provide a mechanism
of validating that a resource is in the same state. Whenever the state of a
resource changes, its entity tag needs to change. In following, the definition
of the HTTP/1.1 validation mechanisms in general, the definition of an entity
tag and the definition of the ETag-header are described. In addition the usage
of entity tags in the Webdav RFC is described.
Section 3.11 of the HTTP/1.1 RFC describes entity tags. These strings identify
(tag) the state of a resource, named "entities" in the RFC. The entity tag
consists of a quoted string and an optional weakness modifier. The quoted
string must be unique for each state. ::
entity-tag = [ weak ] opaque-tag
weak = "W/"
opaque-tag = quoted-string
A non-weak entity tag must identify a certain state uniquely. With the added W/
prefix, one and the same tag may identify different states of a resource that
are semantically equivalent.
Entity tags are used in HTTP/1.1 in combination with the following headers:
- Request
- If-Match
- If-None-Match
- If-Range
- Response
- ETag
Since the Webdav component will generate the entity tag, we should ensure to
only generate strong entity tags.
Headers
=======
The following section describes the headers that are affected by entity tags
and how the server should respect them.
If-Match
--------
The If-Match header is generally used to make the method it is send with
conditional. Only if the conditions defined by the header are met, the action
associated with the method should be performed by the server.
The header format is defined as follows (14.24): ::
If-Match = "If-Match" ":" ( "*" | 1#entity-tag )
The If-Match header in general assumes that the affected resource exists, if it
does not, the request must fail since no entity is there to compare the given
criteria too (no entity exists). The header either specifies "*", to indicate
that an entity must exist, whichever that is. Alternatively any number of
entity tags can be given, divided by ",". If one of the given tags match the
current state of the resource, the method is performed as if not If-Match
header was given. Else the method must fail with 412 (Precondition failed).
In case the request would have failed anyway (not result in a 2xx or 412
status), the If-Match condition is not even checked, but the error response
generated by the request is returned. The reaction of a server to a combination
of multiple If-* headers is undefined.
.. Note::
We should just throw all If-* headers away if a combination of the occurs,
so the back-end does not need to deal with it.
Examples: ::
If-Match: "xyzzy"
If-Match: "xyzzy", "r2d2xxxx", "c3piozzzz"
If-Match: *
If-None-Match
-------------
This header behaves similar to the If-Match header, except that the operation
is only performed if none of the submitted entity tags matches. In case the
operation is a GET or a HEAD operation and one of the tags matched, the server
should return a 304 (Not Modified) code, in all other cases a 412 (Precondition
Failed) must be returned.
The header is defined like this: ::
If-None-Match = "If-None-Match" ":" ( "*" | 1#entity-tag )
The '*' again means that any entity tag exists (as for If-Match). In case of
the If-None-Match header, the operation will be executed only of no entity of
the resource exists. In fact this means that the resource does not exist.
Examples: ::
If-None-Match: "xyzzy"
If-None-Match: W/"xyzzy"
If-None-Match: "xyzzy", "r2d2xxxx", "c3piozzzz"
If-None-Match: W/"xyzzy", W/"r2d2xxxx", W/"c3piozzzz"
If-None-Match: *
If-Range
--------
The If-Range header does only make sense to be respected, if the server supports
partial GET requests and resuming of such. Since the Webdav component does not
support this, yet, the header will be ignored.
.. Note::
If we support partial GET sometimes, support for this header must be
considered, too.
ETag
----
The ETag header is send by a server to give the client an entity-tag that
identifies the current state of a certain resource. This tag can later be used
by the client with any of the headers described above. The ETag header is
therefore a response header, while the headers described above are request
headers.
The ETag header is build up like this: ::
ETag = "ETag" ":" entity-tag
Examples::
ETag: "xyzzy"
ETag: W/"xyzzy"
ETag: ""
.. Attention::
The following is an assumption that should be verified somehow. It seems that
the ETag header is only defined for a single resource, which indicates that
responses that affect multiple resources should not contain it. For Webdav
this includes several methods like COPY and MOVE, but also PROPFIND with a
Depth header other than 0.
Validation
==========
The purpose of entity tags is to ensure, that a certain operation is only
performed, if a resource resides in a certain state. This state is defined by
an entity tag. Most likely, this applies to the GET operation in combination
with caching for pure HTTP/1.1. However, in combination with the WebDAV
extension, validation of entity tags might also be necessary for operations
like PUT and others.
The HTTP/1.1 RFC defines 2 different validator schemes: Strong and weak
validators. Entity tags are generally considered strong validators, since they
should change as soon as the affected resource changes its state. However, the
protocol provides a way to declare entity tags to be weak validators, as
described in the ETag header above.
.. Note::
We should not make use of this way of "weaking" entity tags, but always provide
the strong method.
Caches (like proxy servers and browser caches) use additional methods to
validate their content. The most common way here is to use the Last-Modified
header in addition, which indicates the last modification time of the resource.
-- Note::
We need to decide if we should support this validator in addition. This
would also involve more headers to react on, like If-Modified-Since. This is
not mandatory.
Locking
~~~~~~~
This section tries to summarize the important facts about locking mentioned in
RFC 2518 in various places, enhanced by first pre-considerations of design and
implementation issues associated with them.
Lock types
==========
The WebDAV RFC distinguishes between read and write locks, while only write
locks are defined in detail. The RFC explains, that "the syntax is extensible,
and permits the eventual specification of locking for other access types".
A write lock determines that the locking principle may exclusively write to the
affected resources. Reading is possibly for every other principle, too. If a
principle that does not hold a specific lock tries to perform a writing operation
to a resource which is locked by another principle, this operation must fail.
Lock scopes
===========
The WebDAV RFC specifies 2 different scopes for locking:
- Exclusive
- Shared
For an exclusive lock exactly 1 principle may hold a lock on a specific resource
and only this principle is allowed to perform the affected operations on the
locked resource. For a shared lock it is possible that multiple principles take
part in the lock (group editing). Every principle that takes part in a shared lock
may perform the affected operations on the locked resource.
WebDAV does not provide any channel to allow communication between the
principles involved into a shared lock. The communication of these principles must
be handled externally.
The following table shows lock compatibility:
+----------------------+-----------------+--------------+
| Current lock state/ | Shared Lock | Exclusive |
| Lock request | | Lock |
+----------------------+-----------------+--------------+
| None | True | True |
+----------------------+-----------------+--------------+
| Shared Lock | True | False |
+----------------------+-----------------+--------------+
| Exclusive Lock | False | False* |
+----------------------+-----------------+--------------+
*Legend*: True = lock may be granted. False = lock MUST NOT be granted. \*=It is
illegal for a principal to request the same lock twice.
Lock tokens
===========
A lock token identifies a specific lock uniquely across all resources for all
times. Whenever a successful LOCK request was processed, it returns the
specific lock token for this lock. The lock token associates the locking principle
with the locked resource. Therefore multiple lock tokens might be assigned to a
single resource, if the lock is a shared lock.
A lock token must be unique throughout all resources for all times. The WebDAV
RFC therefore defines a lock token scheme, which can optionally be used by the
server. The so called "opaquelocktoekn" scheme makes use of UUID_, as defined
in `ISO-11578`_. A `PECL package for UUIDs`_ is available.
.. _`UUID`: http://en.wikipedia.org/wiki/UUID
.. _`ISO-11578`: http://www.iso.ch/cate/d2229.html
.. _`PECL package for UUIDs`: http://pecl.php.net/package/uuid
Since the opaquelocktoken scheme is not mandatory, the code snippet ::
$token = md5( uniqid( rand(), true ) );
could be used as an alternative to provide the necessary amount of uniqueness.
To create a lock token that, a this way generated ID could be appended to the
URI of the affected resource to provide transparency of the source of a lock
token. For example: http://webdav/foo/bar.txt#<id>.
Every principle that can access the WebDAV server has access to lock tokens
through the LOCKDISCOVERY request method, so the lock must be bound to a
different authentication mechanism. An owner string is submitted with the LOCK
request, which might help here.
Affected requests
=================
Locks affect several request, beside the explicitly lock related requests. The
following 2 sections summarize the affected request methods and give a short
overview about how these are affected.
LOCK
----
The LOCK method is a new method, which needs to be supported. The request body
of the LOCK method contains a dedicated XML element. Both, the request
abstraction object and objects for the conent, already exist in the Webdav
component.
The method supports the Depth header, but only with the values 0 and INFINITY.
The value 1 is not supported. 0 means that only the resource itself is affected
and INFITY includes all descendant resources. For non-collection resources both
mean the same, For collection resources 0 means that only the collection should
be locked and INFINITY reciusively locks all descendants of the collection in
addition to the collection itself. No Depth header means that INFINITY is
asumed.
A LOCK method must only return a single lock token for all resources locked
with this request. If an UNLOCK method is successfully executed with this lock
token, all affected resources must be unlocked.
If a LOCK operation fails because there is a conflict with one of the resources
to LOCK, the complete operation needs to fail (no partial success). The
response code 409 (Conflict) must be returned, the body must be a multistatus
XML element that contains the resource that is responsible for the conflict.
Status codes returned by the LOCK method are:
200 (OK) - The lock request succeeded and the value of the lockdiscovery
property is included in the body.
412 (Precondition Failed) - The included lock token was not enforceable on this
resource or the server could not satisfy the request in the lockinfo XML
element.
423 (Locked) - The resource is locked, so the method has been rejected.
Example - Simple LOCK request: ::
>>Request
LOCK /workspace/webdav/proposal.doc HTTP/1.1
Host: webdav.sb.aol.com
Timeout: Infinite, Second-4100000000
Content-Type: text/xml; charset="utf-8"
Content-Length: xxxx
Authorization: Digest username="ejw",
realm="ejw@webdav.sb.aol.com", nonce="...",
uri="/workspace/webdav/proposal.doc",
response="...", opaque="..."
<?xml version="1.0" encoding="utf-8" ?>
<D:lockinfo xmlns:D='DAV:'>
<D:lockscope><D:exclusive/></D:lockscope>
<D:locktype><D:write/></D:locktype>
<D:owner>
<D:href>http://www.ics.uci.edu/~ejw/contact.html</D:href>
</D:owner>
</D:lockinfo>
>>Response
HTTP/1.1 200 OK
Content-Type: text/xml; charset="utf-8"
Content-Length: xxxx
<?xml version="1.0" encoding="utf-8" ?>
<D:prop xmlns:D="DAV:">
<D:lockdiscovery>
<D:activelock>
<D:locktype><D:write/></D:locktype>
<D:lockscope><D:exclusive/></D:lockscope>
<D:depth>Infinity</D:depth>
<D:owner>
<D:href>
http://www.ics.uci.edu/~ejw/contact.html
</D:href>
</D:owner>
<D:timeout>Second-604800</D:timeout>
<D:locktoken>
<D:href>
opaquelocktoken:e71d4fae-5dec-22d6-fea5-00a0c91e6be4
</D:href>
</D:locktoken>
</D:activelock>
</D:lockdiscovery>
</D:prop>
Example - Refreshing LOCK request: ::
>>Request
LOCK /workspace/webdav/proposal.doc HTTP/1.1
Host: webdav.sb.aol.com
Timeout: Infinite, Second-4100000000
If: (<opaquelocktoken:e71d4fae-5dec-22d6-fea5-00a0c91e6be4>)
Authorization: Digest username="ejw",
realm="ejw@webdav.sb.aol.com", nonce="...",
uri="/workspace/webdav/proposal.doc",
response="...", opaque="..."
>>Response
HTTP/1.1 200 OK
Content-Type: text/xml; charset="utf-8"
Content-Length: xxxx
<?xml version="1.0" encoding="utf-8" ?>
<D:prop xmlns:D="DAV:">
<D:lockdiscovery>
<D:activelock>
<D:locktype><D:write/></D:locktype>
<D:lockscope><D:exclusive/></D:lockscope>
<D:depth>Infinity</D:depth>
<D:owner>
<D:href>
http://www.ics.uci.edu/~ejw/contact.html
</D:href>
</D:owner>
<D:timeout>Second-604800</D:timeout>
<D:locktoken>
<D:href>
opaquelocktoken:e71d4fae-5dec-22d6-fea5-00a0c91e6be4
</D:href>
</D:locktoken>
</D:activelock>
</D:lockdiscovery>
</D:prop>
.. Note::
The server must not honor the principles Timeout header!
Example - Multi resource LOCK Request: ::
>>Request
LOCK /webdav/ HTTP/1.1
Host: webdav.sb.aol.com
Timeout: Infinite, Second-4100000000
Depth: infinity
Content-Type: text/xml; charset="utf-8"
Content-Length: xxxx
Authorization: Digest username="ejw",
realm="ejw@webdav.sb.aol.com", nonce="...",
uri="/workspace/webdav/proposal.doc",
response="...", opaque="..."
<?xml version="1.0" encoding="utf-8" ?>
<D:lockinfo xmlns:D="DAV:">
<D:locktype><D:write/></D:locktype>
<D:lockscope><D:exclusive/></D:lockscope>
<D:owner>
<D:href>http://www.ics.uci.edu/~ejw/contact.html</D:href>
</D:owner>
</D:lockinfo>
>>Response
HTTP/1.1 207 Multi-Status
Content-Type: text/xml; charset="utf-8"
Content-Length: xxxx
<?xml version="1.0" encoding="utf-8" ?>
<D:multistatus xmlns:D="DAV:">
<D:response>
<D:href>http://webdav.sb.aol.com/webdav/secret</D:href>
<D:status>HTTP/1.1 403 Forbidden</D:status>
</D:response>
<D:response>
<D:href>http://webdav.sb.aol.com/webdav/</D:href>
<D:propstat>
<D:prop><D:lockdiscovery/></D:prop>
<D:status>HTTP/1.1 424 Failed Dependency</D:status>
</D:propstat>
</D:response>
</D:multistatus>
UNLOCK
------
The UNLOCK method handles the removal of a lock established via the LOCK
method. A lock may also disappear by itself, for example when a timeout is
reached. If the principle holding a lock finished its operation on the locked
resources it should use the UNLOCK method to release the lock.
The UNLOCK method only receives a lock token via the Lock-Token header. The
lock identified by this token is to be released.
.. Note::
Not only the resource identified by the resource URI must be unlocked, but
all other resources that are locked by the given lock token!
Example - UNLOCK request: ::
>>Request
UNLOCK /workspace/webdav/info.doc HTTP/1.1
Host: webdav.sb.aol.com
Lock-Token: <opaquelocktoken:a515cfa4-5da4-22e1-f5b5-00a0451e6bf7>
Authorization: Digest username="ejw",
realm="ejw@webdav.sb.aol.com", nonce="...",
uri="/workspace/webdav/proposal.doc",
response="...", opaque="..."
>>Response
HTTP/1.1 204 No Content
Affected base methods
---------------------
The following methods may only be performed on a locked resource if the
performing principle owns the specific lock.
- PUT
- POST
- PROPPATCH
- MOVE
- COPY
- DELETE
- MKCOL
In addition the PROPFIND request is affected by lock support, since lock
information is visible to every principle through the LOCKDISCOVERY and
SUPPORTEDLOCK properties.
Locking resources
=================
Both types of resources (non-collection and collection resources) can be
locked. This section describes differences for both types and other points
directly related to locking resources.
Non-collection resources
------------------------
A non collection resource can be affected directly or indirectly by a lock. In
the first case a principle has issued a LOCK request explicitly for this resource,
only locking this single resource. The second case occurs, if a principle locked a
collection resource and the non-collection resource is a direct or in-direct
descendant of it. For detailed information on this topic see the next section
about locking if collection resources.
Collections
-----------
The LOCK request allows the 'Depth' header to be set to specify the depth of
the created lock. A depth value of ZERO means, that only the affected
collection itself is locked. This might be sensible to add new resources to
this collection. The depth value INFINITY means that the created lock
recursively affects all descendants of the collection. This way it is possible
to lock a complete sub-tree of the WebDAV repository.
Any lock (no matter which depth) on a collection prevents the addition and
removal of direct members of this collection by non-lock-owners. This affects
the following methods:
- PUT
- POST
- MKCOL
- DELETE
If a collection should be locked and any of its members is already locked, this
conflicts with the lock to be set and must result in an 423 error (Locked).
Members that are newly created inside a locked collection or copied/moved to
it are automatically included to the lock. This affects infinity-depth locks as
well as zero-depth ones for direct children of the locked collection.
Lock null resources
-------------------
A write lock might be acquired to a resource that does not (yet) exist. This
is called a "lock null resource". A lock null resource only supports the
methods:
- PUT
- MKCOL
- OPTIONS
- PROPFIND
- LOCK
- UNLOCK
All other methods must return 404 (Not Found) or 405 (Method Not Allowed).
The properties of a null resource are mostly empty, except there must be
LOCKDISCOVERY and SUPPORTEDLOCK properties. If PUT or MKCOL are issued, a null
resource becomes a normal one. The RFC does not state if the lock stays on this
newly created (real) resource or if it is removed.
COPY/MOVE
---------
Both methods destroy locks. If a resource is moved to a locked collection, it
is automatically added to the lock (same principle assumed). Both will not work,
if the destination is locked but no lock is owned by the principle.
Refresh
=======
A LOCK request must not occur twice. To refresh a lock, principles send a LOCK
request with empty body and an If header that specifies the lock tokens to
refresh locks for. If this occurs, the timers of the lock must be reset. A
Timeout header might be send by the principle, but the server may safely ignore
these and simply perform a refresh as it desires.
If header
~~~~~~~~~
In addition to the headers If-Match and If-None-Match, which are described in
the HTTP/1.1 RFC, RFC 2518 (WebDAV) describes the If-Header. The If header is
used to define conditional actions by the client, similar to the 2 headers
named before. However, it is constructed in a much more complex and weird way.
The If header is described with the following pseudo EBNF: ::
If = "If" ":" ( 1*No-tag-list | 1*Tagged-list)
No-tag-list = List
Tagged-list = Resource 1*List
Resource = Coded-URL
List = "(" 1*(["Not"](State-token | "[" entity-tag "]")) ")"
State-token = Coded-URL
Coded-URL = "<" absoluteURI ">"
It may contain entity tags (see `Entity tag support`_), lock tokens (see `Lock
tokens`_) and combination of both. In addition the header may contain
additional resource URIs to affect not only the main request URI. Luckily, the
If header either containes a tagged list (including affected resource URIs) or
a no-tag list (without resource URIs). It cannot contain a combination of
those.
Both lists (tagged and no-tag) contain a not limmited number of lock tokens
and/or entity tags and maybe prefixed by the keyword "Not". This indicates that the
affected method may only be executed if the condition defined in the list does
not match. This works similar to the If-None-Match header, specified by the
HTTP/1.1 RFC.
To illustrate this complex definition some more, some examples are presented
and explained in following.
No-tag list
------------
::
If: (<locktoken:a-write-lock-token> ["I am an ETag"]) (["I am another ETag"])
This If header consists of 2 no-tag lists (it does not contain any resource
URIs). The first list consists of a lock token and an entity tag, the second
only contains an entity tag. The semantics of this example is, that the method
containing this If header may only be executed if
- either the first combination of lock token and entity tag is matched
- or if the second entity tag is matched.
Note that the first list item describes a logical AND operation, while the
whole list concatenates its items by logical OR.
Tagged list
-----------
::
COPY /resource1 HTTP/1.1
Host: www.foo.bar
Destination: http://www.foo.bar/resource2
If: <http://www.foo.bar/resource1> (<locktoken:a-write-lock-token>
[W/"A weak ETag"]) (["strong ETag"])
<http://www.bar.bar/random>(["another strong ETag"])
This example does not only show an If header with a tagged list, but also a
context where this could make some sense: The COPY method affects several
resources at once. It works at least on a source (the request URI) and a
destination (see Destination header), Additionally it can affect whole
sub-tress, using the Depth header.
The If header in this case affects 2 resources, while one of the defined
conditions will be checked and the other won't. The first list affects the
request URI and contains 2 elements concatenated with logical OR. The first
item consists of an AND-combination of a lock token and an entity tag. The
second only contains an entity tag. This is similar to the example shown above
for the no-tag list. Except that it contains the "tagging" URI. For the
second tag only an entity tag is listed. Anyway, this condition would not be
checked in the request but simply be ignored, since the resource in the tag is
not affected by the resource.
Not
---
::
If: (Not <locktoken:write1> <locktoken:write2>)
This simple If header only shows the definition of a "Not" affected list. The
keyword must occur at the very begining of the affected list item. This item
contains 2 lock tokens combined with logical AND. In clear words the requested
affected by this If header will be executed if non of the affected resources is
locked by either of the specified lock tokens.
..
Local Variables:
mode: rst
fill-column: 79
End:
vim: et syn=rst tw=79