Фантазия на тему 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 нуждается в адаптации к суровой реальности;
— идея вполне реализуема.
Об этом — в следующием посте.