Фантазия на тему WebDAV. Штатный Клиент

А кому легко?

В предыдущем посте изложена идея интерфейса на основе протокола WebDAV. Естественным требованием при её релизации является обеспечение работы со штатными WebDAV-клиентами различных ОС. Поскольку RFC не догма, следует начать с выяснения деталей их «поведения». Т.е. набора и последовательности методов инициируемых клиентами при подключении, выполнении операций обмена (передать, получить) и удаленных операций (создать, копировать, переместить/переименовать, удалить) над объектами: файл, папка, ветвь. Полезно уточнить поддержку клиентами cookies; http-аутентификации; настроек прокси для локальных адресов; взаимодействие с портом сервера, отличным от 80.

Обзор не претендует на полноту, поскольку ограничен рамками поставленной задачи и доступным программным обеспечением.
Под рукой оказались:
— openSUSE 13.2 KDE (wdfs, cadaver, Konqueror, Dolphin);
— ubuntu 14.04 LTS Gnome (davfs, Nautilus);
— MS Windows 7 SP1 Starter, Windows 10 Home, Windows XP SP3 Professional (net use, Explorer).

Основные сведения получены методом научного тыка экспериментальным путем на эмуляторе безымянного WebDAV сервера класса 1 (Server: noname/0.0.7; DAV: 1; Allowed: OPTIONS, GET, HEAD, PUT, DELETE, PROPFIND, PROPPATCH, MKCOL, COPY, MOVE). Корень сервера: /test/dav/. В ответах использовались только свойства пространства имен D="DAV:", а в тегах <D:href> — абсолютная адресация. Отмечу, что согласно RFC, эмулятор "MUST", но не факт, что соответствует. Возникшие по ходу тестирования вопросы могут быть следствием этого.

MS Windows 7 SP1 Starter (Explorer, net use)
User-Agent: Microsoft-WebDAV-MiniRedir/6.1.7601
Поддерживает cookie; negotiate и digest (по-умолчанию), basic (после шаманства с реестром) авторизацию; настройки proxy IE; нестандартные http-порты. Особенности обмена:

1. Подключение: \\host:port\path или \\host@port\path. При первом вызове сервера клиент опрашивает OPTIONS начиная от корня "/" до первого сегмента пути. Не получив ожидаемого (заголовки Allowed, DAV), запрашивает PROPFIND на первый сегмент. Разрешил недоразумение, посылая клиента (301-Moved Permanently) по адресу корня сервера на запросы OPTIONS, PROPFIND промежуточных сегментов.
Запросы клиента содержат заголовок translate: f

2. Запросы PROPFIND клиента не содержат тела.
Проводник Windows на каждый чих клик разражается серией PROPFIND. Периодически запрашивает свойства файлов: desktop.ini, folder.gif, folder.jpg, Thumbs.db. Ответ 404 клиента вполне устраивает. Из за его многословности перешел на консоль: COPY, XCOPY, MOVE, DEL, RMDIR, REN…

3. Передача файла выполняется последовательностью методов:
— PROPFIND (Depth: 0) папки назначения;
— PROPFIND файла (проверка на наличие);
— PUT файла нулевой длины при отсутствии;
— LOCK (Timeout: Second-3600) файла:

<?xml version="1.0" encoding="utf-8" standalone="no"?>
<D:lockinfo xmlns:D="DAV:">
  <D:lockscope><D:exclusive/></D:lockscope>
  <D:locktype><D:write/></D:locktype>
  <D:owner><D:href>HOST_NAME\USER_NAME</D:href></D:owner>
</D:lockinfo>

Ответ сервера в соответствии с rfc, но клиент принимает и 501-Not Implemented.
— PROPPATCH свойств файла:

<?xml version="1.0" encoding="utf-8" standalone="no"?>
<D:propertyupdate xmlns:D="DAV:" xmlns:Z="urn:schemas-microsoft-com:">
<D:set><D:prop>
  <Z:Win32CreationTime>Mon, 24 Jun 2013 11:16:17 GMT</Z:Win32CreationTime>
  <Z:Win32LastAccessTime>Tue, 23 Jun 2015 11:16:23 GMT</Z:Win32LastAccessTime>
  <Z:Win32LastModifiedTime>Mon, 24 Jun 2013 11:16:17 GMT</Z:Win32LastModifiedTime>
  <Z:Win32FileAttributes>00000000</Z:Win32FileAttributes>
</D:prop></D:set>
</D:propertyupdate>

Метод PROPPATCH сервер класса 1 должен поддерживать. А как быть с пространством имен Microsoft? RFC допускает ответ 403-Forbidden (cannot-modify-protected-property). Клиент игнорирует отказ и продолжает гнуть свое.
— HEAD файла;
— PUT содержимого файла;
— PROPPATCH свойств файла:

<?xml version="1.0" encoding="utf-8" standalone="no"?>
<D:propertyupdate xmlns:D="DAV:" xmlns:Z="urn:schemas-microsoft-com:">
<D:set><D:prop>
  <Z:Win32LastModifiedTime>Mon, 24 Jun 2013 11:16:17 GMT</Z:Win32LastModifiedTime>
  <Z:Win32FileAttributes>00000020</Z:Win32FileAttributes>
</D:prop></D:set>
</D:propertyupdate>

— UNLOCK;
— PROPFIND файла;
— PROPPATCH файла атрибутом Win32FileAttributes=00000020.

4. Получить файл:
— PROPFIND (Depth:0) папки-контейнера;
— PROPFIND файла;
— GET файла.

5. Создать папку:
— PROPFIND (Depth:0) папки назначения;
— PROPFIND создаваемой папки;
— MKCOL;
— PROPPATCH свойств папки аналогично передаче файла (значение Win32FileAttributes=00000010).

6. Копирование файла выполняется в два приема:
— получить файл (п.4);
— передать файл (п.3).
Осторожный вопрос: а метод COPY где?

7. Перемещение/переименование объектов (включая ветви) выполняется методом MOVE (Overwrite: F; Destination: ....; Depth отсутствует, что подразумевает Infinity).

8. Копирование ветви производится последовательным созданием папок и копированием (п.6) файлов. Удаление ветви производится последовательным удалением вложенных файлов и папок.

Диалоги в Windows 10 (User-Agent: Microsoft-WebDAV-MiniRedir/10.0.10240) навскидку не отличаются от вышеизложенного. Редиректор Windows XP (User-Agent: Microsoft-WebDAV-MiniRedir/5.1.2600) НЕ ПОДДЕРЖИВАЕТ cookies; порты, отличные от 80; не использует LOCK/UNLOCK. Проводник Windows XP вставляет лишний слэш после первого сегмента корня сервера в запросах PROPFIND диалога передачи/перемещения файла (PROPFIND /test//dav/file.txt HTTP/1.1).

ubuntu 14.04 LTS Gnome (Nautilus 3.10.1)
User-Agent: gvfs/1.20.1
Поддерживает cookie, авторизацию, настройки proxy, нестандартные порты. Детали обмена:

1. Подключение (dav://host:port/path):
— OPTIONS на корень сервера;
— PROPFIND (Depth:0) корня сервера (набор свойств используется для проверки наличия папок):

<?xml version="1.0" encoding="utf-8" standalone="no"?>
<D:propfind xmlns:D="DAV:"><D:prop>
   <D:resourcetype/>
</D:prop></D:propfind>

— OPTIONS на предыдущий сегмент корня, откуда послан (301) по назначению;
— PROPFIND корня сервера с заголовками Depth: 0/1 и запросом свойств (в основном их и использует):

<?xml version="1.0" encoding="utf-8" standalone="no"?>
<D:propfind xmlns:D="DAV:"><D:prop>
   <D:creationdate/>
   <D:displayname/>
   <D:getcontentlength/>
   <D:getcontenttype/>
   <D:getetag/>
   <D:getlastmodified/>
   <D:resourcetype/>
</D:prop></D:propfind>


2. Передать файл:
— PROPFIND (Depth:0) папки назначения;
— PROPFIND дисковой квоты папки назначения согласно rfc4331:

<?xml version="1.0" encoding="utf-8" standalone="no"?>
<D:propfind xmlns:D="DAV:"><D:prop>
   <D:quota-available-bytes/>
   <D:quota-used-bytes/>
</D:prop></D:propfind>

— HEAD файла на наличие;
— PUT файла;
— PROPFIND файла аж целых 5 раз (?);

3. Принять файл:
— PROPFIND (Depth:0) файла x5 раз (?);
— GET файла.

4. Создать папку:
— PROPFIND (Depth:0) папки назначения;
— PROPFIND дисковой квоты папки назначения;
— PROPFIND создаваемой папки;
— MKCOL создаваемой папки;
— PROPFIND созданной папки x5 раз (?);

5. Копировать файл:
— PROPFIND дисковой квоты в папке назначения;
— PROPFIND исходного файла x5 раз;
— GET файла;
— HEAD файла на наличие в папке назначения;
— PUT файла в папку назначения;
— PROPFIND скопированного файла x5 раз.

6. Переименовать файл/папку/ветвь:
— MOVE (Overwrite: F; Destination: …; Depth отсутствует) файла/папки.

7. Удалить файл:
— PROPFIND (Depth:0) файла;
— PROPFIND (Depth:1) файла;
— DELETE файла.

8. Копирование ветви производится созданием структуры ветви в папке назначения с последующим копированием (п.5) файлов. Удаление ветви производится последовательным удалением вложенных файлов и папок. Перемещение файла/папки/ветви производится копированием (п.5/п.8) с последующим удалением источника.

openSUSE 13.2 (wdfs, cadaver), ubuntu 14.04 LTS (davfs2)
Клиенты используют библиотеку neon/0.30.0, совместимую с WebDAV RFC2518 из которой поддержка cookies вырезана в релизе 0.25.0. Цитата из Feature list по поставленным вопросам: 'Modern HTTP authentication support: a complete implementation of the new authentication standard, RFC2617, supporting the Digest, Basic, and Negotiate protocols. … The system's proxy configuration can be optionally used, on some platforms.'

wdfs
User-Agent: wdfs/1.4.2 neon/0.30.0
Тестирование проводилось консольными командами. Текущая папка: ~/

1. Монтирование fs (http://host:port/path):
— OPTIONS на корень сервера.

2. ls на точку монтирования:
— PROPFIND (Depth: 0/1):

<?xml version="1.0" encoding="utf-8"?>
<propfind xmlns="DAV:"><prop>
  <resourcetype xmlns="DAV:"/>
  <getcontentlength xmlns="DAV:"/>
  <getlastmodified xmlns="DAV:"/>
  <creationdate xmlns="DAV:"/>
</prop></propfind>


3. Передать файл:
— PROPFIND (Depth:0) каждой папки в пути назначения;
— PROPFIND файла в папке назначения;
— PUT файла нулевой длины;
— PROPFIND файла;
— GET файла нулевой длины;
— PUT файла.

4. Принять файл:
— PROPFIND (Depth:0) каждой папки пути к файлу;
— PROPFIND файла;
— GET файла.

5. Создать папку:
— PROPFIND (Depth:0) каждой папки в пути назначения;
— PROPFIND создаваемой папки;
— MKCOL папки;
— PROPFIND созданной папки;

6. Копировать файл:
— PROPFIND (Depth:0) каждой папки в пути назначения и файла назначения на наличие;
— PROPFIND (Depth:0) каждой папки в пути источника и файла источника;
— GET файла источника;
— PUT файла нулевой длины;
— PROPFIND файла нулевой длины;
— GET файла нулевой длины;
— PUT файла.

7. Переместить/переименовать файл/папку/ветвь:
— PROPFIND папки назначения;
— PROPFIND объекта-источника;
— PROPFIND объекта-источника в папке назначения x2?;
— MOVE (Overwrite:T; Destination:...; Depth отсутствует) объекта-источника в папку назначения.

8. Удаление папки/ветви производится одним методом DELETE (Depth отсутствует). Копирование ветви производится последовательным созданием папок и копированием файлов (GET/PUT).

davfs
User-Agent: davfs2/1.4.7 neon/0.30.0
Тестирование проводилось консольными командами. Текущая папка: ~/

1. Монтирование fs (http://host:port/path):
— OPTIONS на корень сервера;
— PROPFIND (Depth:1; Connection:TE; TE:trailers) корня:

<?xml version="1.0" encoding="utf-8"?>
<propfind xmlns="DAV:"><prop>
  <getetag xmlns="DAV:"/>
  <getcontentlength xmlns="DAV:"/>
  <creationdate xmlns="DAV:"/>
  <getlastmodified xmlns="DAV:"/>
  <resourcetype xmlns="DAV:"/>
  <executable xmlns="http://apache.org/dav/props/"/>
</prop></propfind>

— PROPFIND (Depth: 0) корня:

<?xml version="1.0" encoding="utf-8"?>
<propfind xmlns="DAV:"><prop>
  <quota-available-bytes xmlns="DAV:"/>
  <quota-used-bytes xmlns="DAV:"/>
</prop></propfind>


2. Передать файл:
— PROPFIND (Depth:1) каждой папки в пути назначения;
— PUT (If-None-Match:*) файла;
— HEAD файла.

3. Принять файл:
— PROPFIND (Depth:1) каждой папки в пути источника;
— GET (If-Modified-Since:...) файла.

4. Создать папку:
— PROPFIND (Depth:1) каждой папки в пути назначения;
— MKDIR.

5. Копировать файл:
— PROPFIND (Depth:1) папки назначения;
— GET (If-Modified-Since:...) файла-источника;
— PUT (If-None-Match:*) файла;
— HEAD файла.

6. Переименование/перемещение объектов производится одним методом MOVE.

7. Копирование папки/ветви производится последовательным созданием папок и копированием (получить/передать) файлов в общем случае.

8. Удаление папки/ветви производится последовательным удалением файлов и папок.

cadaver
User-Agent: cadaver/0.23.3 neon/0.30.0
Подключение: http://host:port/path
Тело запроса PROPFIND cadaver содержит:

<?xml version="1.0" encoding="utf-8" standalone="no"?>
<propfind xmlns="DAV:"><prop>
  <getcontentlength/>
  <getlastmodified/>
  <executable xmlns="http://apache.org/dav/props/"/>
  <resourcetype/>
  <checked-in/>
  <checked-out/>
</prop></propfind>


Методы WebDAV представлены соответствующими командами. Принципиальной особенностью cadaver является возможность управления пользовательскими свойствами объектов.

openSUSE 13.2 KDE (Konqueror 4.14.8, Dolphin 15.04.0)
User-Agent: Mozilla/5.0 (X11; Linux x86_64) KHTML/4.14.9 (like Gecko) Konqueror/4.14 SUSE
Как всякий браузер, Konqueror поддерживает cookie; авторизацию; настройки proxy для локальных адресов; нестандартные http-порты. Как не всякий — WebDAV.

1. Подключение: webdav://host:port/path (localhost доступен по ip). Клиент не заморачивается с OPTIONS, сходу запрашивает PROPFIND на корень сервера:

<?xml version="1.0" encoding="utf-8" standalone="no"?>
<D:propfind xmlns:D="DAV:"><D:prop>
  <D:creationdate/>
  <D:getcontentlength/>
  <D:displayname/>
  <D:source/>
  <D:getcontentlanguage/>
  <D:getcontenttype/>
  <D:getlastmodified/>
  <D:getetag/>
  <D:supportedlock/>
  <D:lockdiscovery/>
  <D:resourcetype/>
</D:prop></D:propfind>

Впрочем, довольствуется тем, что сервер вернет. А сервер из принципа не возвращал свойства supportedlock и lockdiscovery.

2. Передать файл:
— PROPFIND (Depth:0) папки назначения;
— PROPFIND свойств файла в папке назначения:

<?xml version="1.0" encoding="utf-8" standalone="no"?>
<D:propfind xmlns:D="DAV:"><D:prop>
  <D:creationdate/>
  <D:getcontentlength/>
  <D:displayname/>
  <D:resourcetype/>
</D:prop></D:propfind>

— PROPFIND свойств (п.1) файла в папке назначения, если существует;
— PUT файла;
— PROPFIND (Depth:1) свойств папки назначения из п1.

3. Получить файл:
— PROPFIND файла в папке источнике;
— GET файла.

4. Создать папку:
— MKCOL;
— PROPFIND (Depth:1) родительской папки;
— PROPFIND (Depth:0) созданной папки.

5. Копировать файл:
— PROPFIND файла в папке назначения;
— COPY файла в папку назначения;
— PROPFIND (Depth:1) папки назначения.

6. Переместить/переименовать файл/папку/ветвь:
— PROPFIND (Depth:0) папки назначения;
— MOVE (Overwrite:F; Depth:infinity) файла;
— PROPFIND (Depth:1) папки назначения.

7. При копировании ветви/папки предварительно создается папка/ветвь в папке назначения, затем копируются файлы методом COPY.

8. Удалить файл/папку/ветвь:
— DELETE (Depth отсутствует, что подразумевает Infinity).

За кадром остались клиенты Apple и файловые менеджеры Android, но и то, что есть, позволяет сделать выводы:

— предложенный интерфейс на основе протокола WebDAV нуждается в адаптации к суровой реальности;
— идея вполне реализуема.

Об этом — в следующием посте.

© Habrahabr.ru