Instantly share code, notes, and snippets.

Embed
What would you like to do?
WebDAV in PHP

It's actually pretty easy.

Background

DAV ("Distributed Authoring and Versioning") is basically a way to share content between people in a central source. WebDAV is that applied over the internet. It's an older protocol but quite powerful - full CRUD support, plus fancier features like locking.

Windows, and probably Linux too, has built-in support for mounting WebDAV endpoints as "network locations" or virtual drives (eg, Z:). With full DAV support, you can edit files over the internet. There are three different levels/classes of support:

  1. Original WebDAV (RFC 2518)
  2. Locking
  3. Revised WebDAV (RFC 4918)

Personally I'm using it as a read-only mount point: I want to be able to programmatically expose a dynamic filesystem to myself, where files and directories change based on whatever reasons I want. It's actually only going to be a temporary mechanism to help me get files between a web server and my phone (connect phone to PC, WebDAV mount, drag-and-drop files), to be eventually replaced with an app, but that's besides the point.

Note

This is meant to be a supplement to reading the RFC.

In here, http://www.example.com/dav will be the root of the WebDAV system.

Also, I'm only talking about read operations. No creating, updating, or deleting. But understanding reading makes the rest easier.

Windows Note

I'm doing all this on Windows 10. Earlier versions (particularly XP and Vista) had quirks, such as requiring that the root of the site itself also support WebDAV. There are a few KB articles around. Watching the server access logs will give you a big clue as to what Windows is trying to do - not to mention full request and response logging while developing.

I had some issues with Windows not issuing WebDAV requests, no doubt because earlier responses were messed up. Restart the "WebClient" service if you notice requests not being made anymore, or just to clear the cache it seems to use.

Mounting is possible either through the Add Network Location dialog or by the net use command:

net use * http://www.example.com/dav

Local computer form (\\www.example.com\dav) is also allowed, if that's applicable.

Basics

There are two types of objects: files and directories (not official names) and they're just like what you'd expect. The root of the endpoint should be a directory that lists everything available. Beyond that it's up to you.

There are three basic HTTP requests made to a WebDAV path/resource:

  1. OPTIONS
  2. GET
  3. PROPFIND

(If the resource doesn't exist then respond with a 404. You can also use other HTTP status codes as needed.)

OPTIONS explains what is possible on a resource.

OPTIONS /dav HTTP/1.1

Respond with two headers and no content.

Allow: GET,OPTIONS,PROPFIND
DAV: 1, 3

Allow explains the verbs available - GET, OPTIONS, and PROPFIND are all you need. DAV lists the feature support, and basically you choose based on whether you support locking: "1,3" without (1 and 3 are essentially the same) or "1,2,3" with.

GET is easy: retrieve the contents of the resource.

If the resource is a file then get the contents of the file. Respond as you normally would in HTTP: include headers like Content-Type, Content-Length, etc. (don't bother with Content-Disposition) and use the file data as the response body.

If the resource is a directory then... well, anything goes. GETting a directory does not list contents of the directory. WebDAV is conveniently compatible (mostly) with HTTP, so a good idea here is to respond with a basic webpage that does show the contents. A user could then browse the DAV hierarchy normally.

PROPFIND is where it gets much more complicated...

PROPFIND

It's surprisingly difficult to get a good explanation of how to implement PROPFIND: the RFC is about as suitable for documentation as most others are, but when you go to Google afterwards for the unanswered questions, most people say "just read the RFC" or "read the traffic between a client and some server". It does have examples, however they're not terribly thorough. The main hiccup is knowing what XML to return.

Oh, right. WebDAV uses XML. If you don't know XML then learn that now - it's not hard, especially if you know HTML already.

Let's just start with an example request and response pair (that doesn't show irrelevant or non-WebDAV headers).

PROPFIND /dav HTTP/1.1
Content-Type: application/xml; charset="utf-8"
Depth: 0

<?xml version="1.0" encoding="utf-8"?>
<propfind xmlns="DAV:">
    <propname />
</propfind>
HTTP/1.1 207 Multi-Status
Content-Type: application/xml; charset="utf-8"

<?xml version="1.0" encoding="utf-8"?>
<multistatus xmlns="DAV:">
    <response>
        <href>http://www.example.com/dav</href>
        <propstat>
            <prop>
                <displayname />
                <resourcetype />
            </prop>
            <status>HTTP/1.1 200 OK</status>
        </propstat>
    </response>
</multistatus>

This demonstrates a basic query against "/dav", the root of our WebDAV system. Note the unusual "DAV:" namespace URI. I believe it's technically invalid, however it's generally supported by libraries (such as libxml).

The Depth header is either 0 (show just the requested resource), 1 (show the resource and immediate children), or "infinite" (the source and all descendants). Only 0 and 1 matter in practice and you should be able to safely treat "infinite" as 1.

The request included an optional <propfind> body. This indicates what sort of information to return. A <propname> means to just return the names of the active properties supported without their values. There's also <allprop>, which means to return absolutely all properties with their values, and <prop>, which lists out precisely which properties should be returned (with values). Windows does not seem to send any of that.

The response uses a custom 207 "Multi-Status" response, with a <multistatus> as the root element. Each <response> lists the resources available (subject to the Depth), their path (which can be relative instead of absolute), the list of properties, and the overall status.

Another example:

PROPFIND /dav HTTP/1.1
Depth: 1
HTTP/1.1 207 Multi-Status
Content-Type: application/xml; charset="utf-8"

<?xml version="1.0" encoding="utf-8"?>
<multistatus xmlns="DAV:">
    <response>
        <href>http://www.example.com/dav</href>
        <propstat>
            <prop>
                <displayname>Example WebDAV Server</displayname>
                <resourcetype>
                    <collection />
                </resourcetype>
            </prop>
            <status>HTTP/1.1 200 OK</status>
        </propstat>
    </response>
    <response>
        <href>http://www.example.com/dav/foo</href>
        <propstat>
            <prop>
                <displayname>foo</displayname>
                <resourcetype />
            </prop>
            <status>HTTP/1.1 200 OK</status>
        </propstat>
    </response>
</multistatus>

This request used a Depth of 1 so the response should include children. It did not include a <propfind> so show property values by default.

The response is like before except it shows property values and includes the /foo child file. <displayname> is self-explanatory; <resourcetype> basically either contains a <collection/> (directory) or is empty (file).

WIP

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment