WebDAV on Nginx for Windows Explorer

Intro

You really shouldn’t want to do this, probably. I mostly wrote this article because I couldn’t find it anywhere else, and that’s what this blog is supposed to be all about: me writing the blog post I wish I’d found 6 hours ago.

WebDAV is a set of extensions to HTTP that allows clients (web browsers and other web user agents) to share files directly with a web server, without the need for an intermediary like a FTP or SMB server. This was a really cool idea in 1996, but now we have like 100 better and easier ways to share files.

So why am I even attempting this? Well, I was working on a project where I needed to read/write/execute files from a public-facing Linux server to a Windows 10 machine using only Windows Explorer. Sure, I could use Samba, but permissions on Samba can get….complicated, and I didn’t care about controlling who could manipulate files on the share for this use case (it’s fine, I promise). So I turned to WebDAV, because it has the nice feature of being supported by Windows 10 out of the box (unlike somebody…cough cough NFS cough), and it’s really old, so it should be well-understood and supported, right?

Not well-understood or supported

On the web server where I was hosting the files, I was already running a (related) web app with Nginx, so choosing Nginx as my web server was easy. (Since I already had a site I already had a working config with SSL, and SSL is required for this to work.) Nginx also has built in extensions for WebDAV, so I figured this would be a well-supported use case (in hindsight, foolish hubris). So I installed them:

$ sudo apt install nginx-extras libnginx-mod-http-dav-ext

Installing the packages also enabled the modules by default as well. Then I set up the folder for my share:

$ sudo mkdir /srv/share

$ sudo chown www-data:www-data /srv/share

Now keep in mind, I’m not setting up authentication for this because I explicitly wanted it to be public. You should absolutely add at least basic user/pass authentication for any real use case.

Next I took the naiive approach and followed the documentation to set up my server by editing my site config in the sites-available directory for the site I already had:

location /share {

dav_methods PUT DELETE MKCOL COPY MOVE;
dav_ext_methods PROPFIND OPTIONS;
dav_access user:rw group:rw all:rw;
create_full_put_path on;
root "/srv/";
}

I restarted nginx and mounted the URL of my share https://example.com/share as a network drive in Windows, and it worked! I could see my files, and I could copy a file from my desktop to the share folder and……oh. Bummer. What’s Can't read from the source file or disk mean?

So Windows is giving me a cryptic error when I try to write to the share. It’s also probably lying, considering that it being unable to read from my local disk seems unlikely. As it turns out, Windows does WebDAV in a way that Nginx doesn’t (natively) support. So, yet again, I get to either fundamentally change my approach, or resort to shenanigans. Today, I chose shenanigans.

Shenanigans

By default, Nginx supports 5 WebDAV verbs: PUTDELETEMKCOLCOPY, and MOVE. Pretty basic stuff. With the WebDAV extension, we added PROPFINDOPTIONSLOCK, and UNLOCK. The “normal” WebDAV copy of a file from local into the server would require a PUT, and maybe some locking logic, depending on the implementation.

Naturally, this is Windows we’re talking about, so something weird has to be going on somewhere. As it turns out, to do an inbound copy, Windows does a PUT with a zero-byte file size, and then uses PROPPATCH to copy the file contents into the newly PUT-ed empty file. Astute readers will notice that PROPPATCH isn’t in either list of verbs I said that Nginx supports! And so begin the shenanigans.

What if we just accept the PROPPATCH and tell Windows that everything is OK?

location /share {

dav_methods PUT DELETE MKCOL COPY MOVE;
dav_ext_methods PROPFIND OPTIONS;
dav_access user:rw group:rw all:rw;
create_full_put_path on;
root "/srv/";
if ($request_method = PROPPATCH) {
add_header Content-Type 'text/xml';
return 207 '<?xml version="1.0"?><a:multistatus xmlns:a="DAV:"><a:response><a:propstat><a:status>HTTP/1.1 200 OK</a:status></a:propstat></a:response></a:multistatus>';
}
}

Of course, we have no idea if everything is actually 200 OK or not, but Windows doesn’t need to know that. What could possibly go wrong?

Oh, turns out Windows also doesn’t handle trailing slashes correctly. Easy fix:

location /share {

dav_methods PUT DELETE MKCOL COPY MOVE;
dav_ext_methods PROPFIND OPTIONS;
dav_access user:rw group:rw all:rw;
create_full_put_path on;
root "/srv/";
if ($request_method = PROPPATCH) {
add_header Content-Type 'text/xml';
return 207 '<?xml version="1.0"?><a:multistatus xmlns:a="DAV:"><a:response><a:propstat><a:status>HTTP/1.1 200 OK</a:status></a:propstat></a:response></a:multistatus>';
}
if ($request_method = MKCOL) {
rewrite ^(.*[^/])$ $1/ break;
}
}

Against all odds, this worked, and I can now upload files to the WebDAV server reliably.

In Closing

Learn from my pain. You should not do this. Don’t say I didn’t warn you.

If you have thoughts, or you think I’m wrong and just have to tell me, email me!