Site Tools


software:nginx:webdav

Nginx and Microsoft Windows WebClient (WebDav)

В Microsoft Windows начиная с 2к встроен Веб Клиент, по сути это средство для монтирования сетевых дисков по протоколу WebDav, который ходит поверх HTTP/HTTPS.
Клиент писался явно под себя (связка с IIS) и от того работает весьма корявосвоеобразно.
Это единственный способ “малой” кровью примонтировать диск через интернет не настраивая VPN для проброса SAMBA протокола.
SAMB-у в принципе тоже без VPN можно пробросить, но это из области поиска приключений с последствиями.

Nginx в базовом функционале имеет не полную поддержку Dav: PUT DELETE MKCOL COPY MOVE.
Расширить его ещё двумя: PROPFIND и OPTIONS можно с помощью модуля: dav-ext

Для работы потребуется Nginx собранный с поддержкой Dav, dav-ext, rewrite и headers_more.

[x] HTTP_DAV              Enable http_webdav module
[x] HTTP_DAV_EXT          3rd party webdav_ext module
[x] HTTP_REWRITE          Enable http_rewrite module
[x] HEADERS_MORE          3rd party headers_more module

Проблема 1 - Майкрософт нарушает стандарты и свои обещания

Перед тем как создать файл WebDav клиент проверяет наличие файла посылая запрос:

PROPFIND /!!!!/test.lnk HTTP/1.1
Connection: Keep-Alive
User-Agent: Microsoft-WebDAV-MiniRedir/6.1.7601
Depth: 0
translate: f
Content-Length: 0
Host: 172.16.0.254:8089

В ответ IIS ему выдаёт:

HTTP/1.1 404 Resource Not Found
Content-Length: 1635
Content-Type: text/html
Server: Microsoft-IIS/6.0
X-Powered-By: ASP.NET
Date: Sun, 10 Aug 2014 20:06:08 GMT

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
...

Только это не правильно, должен быть код 207 и xml в котором описан элемент и код для него, те 404 должно быть в xml.
с точки зрения стандартов: http://tools.ietf.org/html/rfc2518#page-24
с точки зрения доков мс: http://msdn.microsoft.com/en-us/library/aa142960(v=exchg.65).aspx
У яндекса тоже в примерах xml: http://api.yandex.ru/disk/doc/dg/reference/propfind_property-request.xml

Ответ Nginx:

HTTP/1.1 207 Multi-Status
Server: nginx/1.7.4
Date: Sun, 10 Aug 2014 21:17:34 GMT
Transfer-Encoding: chunked
Connection: keep-alive
Keep-Alive: timeout=60

47
<?xml version="1.0" encoding="utf-8" ?>
<D:multistatus xmlns:D="DAV:">

cc
<D:response>
<D:href>/Family/test.lnk</D:href>
<D:propstat>
<D:prop>
</D:prop>
<D:status>HTTP/1.1 404 Not Found</D:status>
</D:propstat>
</D:response>

11
</D:multistatus>

0

Такой “неожиданный” ответ сносит голову WebDav клиенту винды.

Фикс 1: в конфиге (возможно это сведёт с ума остальные, порядочные WebDav клиенты, но лично мне нужен был только один не такой как все):

error_page	599 = @propfind_handler;
if ($request_method = PROPFIND) {
	return 599;
}

location @propfind_handler {
	internal;

	open_file_cache	off;
	if (!-e $webdav_root/$uri) { # Microsoft specific handle.
		return 404;
	}
	root		$webdav_root;
	dav_ext_methods	PROPFIND;
}

Проблема 2 - PROPPATCH отсутствует в nginx


WebDav от мс очень хочет метод PROPPATCH, которого в Nginx и расширениях нет. Совсем нет.
Я рассматривал два варианта решения:
1. Написать плагин к Nginx или патч к dav-ext, короче Си код и пересборка Nginx.
2. Положится на кривость виндовой реализации WebDav и скормить статический ответ.

Запрос:

PROPPATCH /Family/test.lnk HTTP/1.1
Cache-Control: no-cache
Connection: Keep-Alive
Pragma: no-cache
Content-Type: text/xml; charset="utf-8"
User-Agent: Microsoft-WebDAV-MiniRedir/6.1.7601
translate: f
Content-Length: 443
Host: xxx.xxx.net

<?xml version="1.0" encoding="utf-8" ?><D:propertyupdate xmlns:D="DAV:" xmlns:Z="urn:schemas-microsoft-com:"><D:set><D:prop><Z:Win32CreationTime>Sun, 10 Aug 2014 21:30:21 GMT</Z:Win32CreationTime><Z:Win32LastAccessTime>Sun, 10 Aug 2014 21:30:22 GMT</Z:Win32LastAccessTime><Z:Win32LastModifiedTime>Sun, 10 Aug 2014 21:30:22 GMT</Z:Win32LastModifiedTime><Z:Win32FileAttributes>00000020</Z:Win32FileAttributes></D:prop></D:set></D:propertyupdate>

Ответ IIS:

HTTP/1.1 207 Multi-Status
Date: Sun, 10 Aug 2014 12:24:47 GMT
Server: Microsoft-IIS/6.0
X-Powered-By: ASP.NET
Content-Type: text/xml
Transfer-Encoding: chunked

<?xml version="1.0"?><a:multistatus xmlns:a="DAV:" xmlns:b="urn:schemas-microsoft-com:"><a:response><a:href>http://172.16.0.254:8088//!!!!/test.lnk</a:href><a:propstat><a:status>HTTP/1.1 200 OK</a:status><a:prop><b:Win32CreationTime/><b:Win32LastAccessTime/><b:Win32LastModifiedTime/><b:Win32FileAttributes/></a:prop></a:propstat></a:response></a:multistatus>
0

“Пропритериарщина”, - подумал я. Вот кому такое вообще надо? И как эти все атрибуты писать в разных ОС и разных ФС?…

Фикс 2

if ($request_method = PROPPATCH) { # Unsupported, allways return OK.
	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>';
}

Из ответа IIS я выкинул всё что мне не понравилось и виндовый WebDav это проглотил.
Теперь одиночные и группы файлов без проблем можно копировать на сетевой диск примонтированный по WebDav.
Минусом - не выставляется оригинальная дата создания, модификации и атрибуты.

Проблема 3 - создание папок


См п1 :)

Приходит запрос:

MKCOL /Family/MR3020 HTTP/1.1
Connection: Keep-Alive
User-Agent: Microsoft-WebDAV-MiniRedir/6.1.7601
translate: f
Content-Length: 0
Host: xxx.xxx.net

Nginx на него отвечает (немного странно выбирая код, на мой взгляд, но в принципе правильно):

HTTP/1.1 409 Conflict
Server: nginx/1.7.4
Date: Sun, 10 Aug 2014 21:43:15 GMT
Content-Type: text/html
Content-Length: 166
Connection: keep-alive
Keep-Alive: timeout=60

<html>
<head><title>409 Conflict</title></head>
<body bgcolor="white">
<center><h1>409 Conflict</h1></center>
<hr><center>nginx/1.7.4</center>
</body>
</html>

IIS отвечает: “HTTP/1.1 201 Created”, - смотреть там не на что.

Как должно быть описано:
У майкрософта: http://msdn.microsoft.com/en-us/library/aa142923(v=exchg.65).aspx
У яндекса: http://api.yandex.ru/disk/doc/dg/reference/mkcol.xml
И даже в RFC: http://tools.ietf.org/html/rfc2518#page-33

Во всех примерах URL оканчивается слешем.
Но только не в запросе WebDav клиента от мс.

Вариантов опять было два:
1. Поправить файл: http://lxr.nginx.org/source/src/http/modules/ngx_http_dav_module.c строчки 484 - 493, там как раз проверка наличия слеша и его отрезание.
2. Пофиксить через конфиг.

Вариант 1 я оставляю на усмотрение програмеров nginx, может по стандарту оно и должно ругаться.

Фикс 3:

if ($request_method = MKCOL) { # Microsoft specific handle: add trailing slash.
	rewrite ^(.*[^/])$ $1/ break;
}

Вот для этого пустяка и потребовался REWRITE плагин.

Проблема 4 - удаление папок

Ноги тут те же что и в п3: отсутствие слеша на конце урла.
Однако я столкнулся с тем, что nginx тоже ведёт себя несколько странно: если слеш на конце отсутствует, то nginx считает что его просят удалить файл, и получает ошибку: 21: Is a directory при попытке удалить.

Фикс 4:

error_page	598 = @delete_handler;
if ($request_method = DELETE) {
	return 598;
}

location @delete_handler {
	internal;

	open_file_cache	off;
	if (-d $webdav_root/$uri) { # Add trailing slash to dirs.
		rewrite ^(.*[^/])$ $1/ break;
	}
	root		$webdav_root;
	dav_methods	DELETE;
}

Если кратко, то переносим обработку DELETE в отдельный локейшин (процедуру), дальше проверяем, если удаляется папка то добавляем слеш.

Проблема 5 - копирование и переименование

Ноги тут те же что и в п4: отсутствие слеша на конце урла и в Destination заголовке.
Для решения проблемы с добавлением “/” в заголовок Destinaton нам потребуется модуль headers_more.

Фикс 6:

error_page	597 = @copy_move_handler;
if ($request_method = COPY) {
	return 597;
}
if ($request_method = MOVE) {
	return 597;
}

location @copy_move_handler {
	internal;

	open_file_cache	off;
	if (-d $webdav_root/$uri) { # Add trailing slash to dirs.
		more_set_input_headers 'Destination: $http_destination/';
		rewrite ^(.*[^/])$ $1/ break;
	}
	root		$webdav_root;
	dav_methods	COPY MOVE;
}

Проблема 6 - OPTIONS корня

Клиент запрашивает опции не у той папки которую мы хотим чтобы он подключил а у корня сервера.

Фикс 6:

location / {
	if ($request_method = OPTIONS) {
		add_header	Allow 'OPTIONS, GET, HEAD, POST, PUT, MKCOL, MOVE, COPY, DELETE, PROPFIND, PROPPATCH';
		add_header	DAV '1, 2';
		return 200;
	}
}

Возвращаем ему статический список.
Можно было просто разрешить: dav_ext_methods OPTIONS;

Проблема 7 - виндовый клиент не может подключится

Соединение происходит но в логах nginx ничего нет, а клиент сообщает об ошибке.
В логах с включённым дебагом ssl видно что рукопожатие не проходит.
Проблема у меня проявилась не так давно, после обновления OpenSSL.
При этом IE и другие браузеры могут авторизоваться по этому URL и отобразить листинг директории.

Фикс 7: Заменить OpenSSL на LibreSSL и пересобрать nginx. Возможно OpenSSL уже исправили.

Ещё немного о Nginx

Не трудно заметить, что nginx не добавляет в ответы:

Content-Type: text/xml

и хотя в данном случае это не создаёт проблем, но всё же это не правильно.

Конфиг nginx для WebDav

WebDav для Nginx:

# WebDAV
set $webdav_root "/mnt/WebDav_folder";
location ^~ /Family {
	if ($ssl_protocol = "") { # Block non ssl/tls connections.
		add_header	Strict-Transport-Security 'max-age=600';
		return 403;
	}
	auth_basic		"Private site";
	auth_basic_user_file	/usr/local/etc/nginx/secure/authbasic.htpasswd;

	root			$webdav_root;
	error_page		599 = @propfind_handler;
	error_page		598 = @delete_handler;
	error_page		597 = @copy_move_handler;
	open_file_cache		off;
	client_max_body_size	50m;

	if ($request_method = PROPFIND) {
		return 599;
	}
	if ($request_method = PROPPATCH) { # Unsupported, allways return OK.
		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) { # Microsoft specific handle: add trailing slash.
		rewrite ^(.*[^/])$ $1/ break;
	}
	if ($request_method = DELETE) {
		return 598;
	}
	if ($request_method = COPY) {
		return 597;
	}
	if ($request_method = MOVE) {
		return 597;
	}

	dav_methods		PUT MKCOL;
	dav_ext_methods		OPTIONS;
	create_full_put_path	on;
	min_delete_depth	0;
	dav_access		user:rw group:rw all:rw;

	autoindex		on;
	autoindex_exact_size	on;
	autoindex_localtime	on;
}
location @propfind_handler {
	internal;

	auth_basic		"Private site";
	auth_basic_user_file	/usr/local/etc/nginx/secure/authbasic.htpasswd;

	open_file_cache	off;
	if (!-e $webdav_root/$uri) { # Microsoft specific handle.
		return 404;
	}
	root			$webdav_root;
	dav_ext_methods		PROPFIND;
}
location @delete_handler {
	internal;

	auth_basic		"Private site";
	auth_basic_user_file	/usr/local/etc/nginx/secure/authbasic.htpasswd;

	open_file_cache	off;
	if (-d $webdav_root/$uri) { # Microsoft specific handle: Add trailing slash to dirs.
		rewrite ^(.*[^/])$ $1/ break;
	}
	root			$webdav_root;
	dav_methods		DELETE;
}
location @copy_move_handler {
	internal;

	auth_basic		"Private site";
	auth_basic_user_file	/usr/local/etc/nginx/secure/authbasic.htpasswd;

	open_file_cache	off;
	if (-d $webdav_root/$uri) { # Microsoft specific handle: Add trailing slash to dirs.
		more_set_input_headers 'Destination: $http_destination/';
		rewrite ^(.*[^/])$ $1/ break;
	}
	root			$webdav_root;
	dav_methods		COPY MOVE;
}
location / {
	if ($request_method = OPTIONS) {
		add_header	Allow 'OPTIONS, GET, HEAD, POST, PUT, MKCOL, MOVE, COPY, DELETE, PROPFIND, PROPPATCH';
		add_header	DAV '1, 2';
		return 200;
	}
}

open_file_cache off; - нужно потому что мы меняем файлы, если кеширование включено то можно будет долго гадать почему не видно файлы которые мы только что закинули на сервер.
Также в самом начале локейшена есть требование использовать SSL и если SSL используется то запрашивается BASIC авторизация.

Важно

  1. set $webdav_root “/mnt/WebDav_folder”; - полный путь на диске к папке которую мы расшариваем. Папка должна существовать и на неё должны быть выставлены права доступа которые позволят nginx получить доступ к содержимому, читать и записывать файлы.
  2. в папке “/mnt/WebDav_folder” должна существовать папка Family
  3. location ^~ /Family - означает что клиент должен использовать URL: https://SERVER_ADDRESS/Family
  4. директивы auth_basic и auth_basic_user_file должны быть в каждом location который осуществляет обработку запросов, см DELETE requests work unauthenticated

Немного о настройке клиента

Он как капризный ребёнок, из коробки ему подавай SSL и никакой Basic аутентификации, файлы не больше 50 мегабайт, в папках не более 500-1000 файлов.
Увы, но даже после всех проделанных настроек файлы более 4гб передавать не получится. (Скорее всего из за кривой реализации, которая файлы при открытии скачивает в память/на диск чтобы получить более менее стандартный файловый дискриптор, это мои домыслы.)

Вот здесь собраны все настройки с описанием: http://blogs.msdn.com/b/robert_mcmurray/archive/2008/01/17/webdav-redirector-registry-settings.aspx

На данный момент мои настройки WebClient выглядят так:

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\WebClient\Parameters]
"SupportLocking"=dword:00000000
"InternetServerTimeoutInSec"=dword:0000001e
"ServiceDllUnloadOnStop"=dword:00000001
"ServerNotFoundCacheLifeTimeInSec"=dword:0000000a
"ClientDebug"=dword:00000000
"FileSizeLimitInBytes"=dword:ffffffff
"SendReceiveTimeoutInSec"=dword:0000003c
"LocalServerTimeoutInSec"=dword:0000000f
"FileAttributesLimitInBytes"=dword:00989680
"AcceptOfficeAndTahoeServers"=dword:00000001
"ServiceDebug"=dword:00000000
"BasicAuthLevel"=dword:00000002

Полезное

software/nginx/webdav.txt · Last modified: 2022/02/05 04:30 by root