Merge branch 'bugfix/1039/fix-webdav' of git://github.com/PVince81/MPD into v0.22.x

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1039
This commit is contained in:
Max Kellermann 2021-01-05 13:08:39 +01:00
commit e99f6b5b38
4 changed files with 49 additions and 18 deletions

2
NEWS
View File

@ -1,4 +1,6 @@
ver 0.22.4 (not yet released) ver 0.22.4 (not yet released)
* storage
- curl: fix several WebDAV protocol bugs
* decoder * decoder
- dsdiff: apply padding to odd-sized chunks - dsdiff: apply padding to odd-sized chunks
* filter * filter

View File

@ -243,6 +243,7 @@ class PropfindOperation : BlockingHttpRequest, CommonExpatParser {
enum class State { enum class State {
ROOT, ROOT,
RESPONSE, RESPONSE,
PROPSTAT,
HREF, HREF,
STATUS, STATUS,
TYPE, TYPE,
@ -262,18 +263,19 @@ public:
request.SetOption(CURLOPT_MAXREDIRS, 1L); request.SetOption(CURLOPT_MAXREDIRS, 1L);
request_headers.Append(StringFormat<40>("depth: %u", depth)); request_headers.Append(StringFormat<40>("depth: %u", depth));
request_headers.Append("content-type: text/xml");
request.SetOption(CURLOPT_HTTPHEADER, request_headers.Get()); request.SetOption(CURLOPT_HTTPHEADER, request_headers.Get());
request.SetOption(CURLOPT_POSTFIELDS, request.SetOption(CURLOPT_POSTFIELDS,
"<?xml version=\"1.0\"?>\n" "<?xml version=\"1.0\"?>\n"
"<a:propfind xmlns:a=\"DAV:\">" "<a:propfind xmlns:a=\"DAV:\">"
"<a:prop><a:resourcetype/></a:prop>" "<a:prop>"
"<a:prop><a:getcontenttype/></a:prop>" "<a:resourcetype/>"
"<a:prop><a:getcontentlength/></a:prop>" "<a:getcontenttype/>"
"<a:getcontentlength/>"
"</a:prop>"
"</a:propfind>"); "</a:propfind>");
// TODO: send request body
} }
using BlockingHttpRequest::GetEasy; using BlockingHttpRequest::GetEasy;
@ -321,9 +323,13 @@ private:
break; break;
case State::RESPONSE: case State::RESPONSE:
if (strcmp(name, "DAV:|href") == 0) if (strcmp(name, "DAV:|propstat") == 0)
state = State::PROPSTAT;
else if (strcmp(name, "DAV:|href") == 0)
state = State::HREF; state = State::HREF;
else if (strcmp(name, "DAV:|status") == 0) break;
case State::PROPSTAT:
if (strcmp(name, "DAV:|status") == 0)
state = State::STATUS; state = State::STATUS;
else if (strcmp(name, "DAV:|resourcetype") == 0) else if (strcmp(name, "DAV:|resourcetype") == 0)
state = State::TYPE; state = State::TYPE;
@ -353,9 +359,15 @@ private:
case State::RESPONSE: case State::RESPONSE:
if (strcmp(name, "DAV:|response") == 0) { if (strcmp(name, "DAV:|response") == 0) {
FinishResponse();
state = State::ROOT; state = State::ROOT;
} }
break;
case State::PROPSTAT:
if (strcmp(name, "DAV:|propstat") == 0) {
FinishResponse();
state = State::RESPONSE;
}
break; break;
@ -366,22 +378,22 @@ private:
case State::STATUS: case State::STATUS:
if (strcmp(name, "DAV:|status") == 0) if (strcmp(name, "DAV:|status") == 0)
state = State::RESPONSE; state = State::PROPSTAT;
break; break;
case State::TYPE: case State::TYPE:
if (strcmp(name, "DAV:|resourcetype") == 0) if (strcmp(name, "DAV:|resourcetype") == 0)
state = State::RESPONSE; state = State::PROPSTAT;
break; break;
case State::MTIME: case State::MTIME:
if (strcmp(name, "DAV:|getlastmodified") == 0) if (strcmp(name, "DAV:|getlastmodified") == 0)
state = State::RESPONSE; state = State::PROPSTAT;
break; break;
case State::LENGTH: case State::LENGTH:
if (strcmp(name, "DAV:|getcontentlength") == 0) if (strcmp(name, "DAV:|getcontentlength") == 0)
state = State::RESPONSE; state = State::PROPSTAT;
break; break;
} }
} }
@ -389,6 +401,7 @@ private:
void CharacterData(const XML_Char *s, int len) final { void CharacterData(const XML_Char *s, int len) final {
switch (state) { switch (state) {
case State::ROOT: case State::ROOT:
case State::PROPSTAT:
case State::RESPONSE: case State::RESPONSE:
case State::TYPE: case State::TYPE:
break; break;
@ -455,11 +468,19 @@ CurlStorage::GetInfo(std::string_view uri_utf8, [[maybe_unused]] bool follow)
gcc_pure gcc_pure
static std::string_view static std::string_view
UriPathOrSlash(const char *uri) noexcept UriPathOrSlash(const char *uri, bool relative) noexcept
{ {
auto path = uri_get_path(uri); auto path = uri_get_path(uri);
if (path.data() == nullptr) if (path.data() == nullptr)
path = "/"; path = "/";
else if (relative) {
// search after first slash
path = path.substr(1);
auto slash = path.find('/');
if (slash != std::string_view::npos)
path = path.substr(slash);
}
return path; return path;
} }
@ -468,13 +489,15 @@ UriPathOrSlash(const char *uri) noexcept
*/ */
class HttpListDirectoryOperation final : public PropfindOperation { class HttpListDirectoryOperation final : public PropfindOperation {
const std::string base_path; const std::string base_path;
const std::string base_path_relative;
MemoryStorageDirectoryReader::List entries; MemoryStorageDirectoryReader::List entries;
public: public:
HttpListDirectoryOperation(CurlGlobal &curl, const char *uri) HttpListDirectoryOperation(CurlGlobal &curl, const char *uri)
:PropfindOperation(curl, uri, 1), :PropfindOperation(curl, uri, 1),
base_path(CurlUnescape(GetEasy(), UriPathOrSlash(uri))) {} base_path(CurlUnescape(GetEasy(), UriPathOrSlash(uri, false))),
base_path_relative(CurlUnescape(GetEasy(), UriPathOrSlash(uri, true))) {}
std::unique_ptr<StorageDirectoryReader> Perform() { std::unique_ptr<StorageDirectoryReader> Perform() {
DeferStart(); DeferStart();
@ -500,9 +523,15 @@ private:
/* kludge: ignoring case in this comparison to avoid /* kludge: ignoring case in this comparison to avoid
false negatives if the web server uses a different false negatives if the web server uses a different
case */ case */
if (uri_has_scheme(path)) {
path = StringAfterPrefixIgnoreCase(path, base_path.c_str()); path = StringAfterPrefixIgnoreCase(path, base_path.c_str());
if (path == nullptr || path.empty()) } else {
path = StringAfterPrefixIgnoreCase(path, base_path_relative.c_str());
}
if (path == nullptr || path.empty()) {
return nullptr; return nullptr;
}
const char *slash = path.Find('/'); const char *slash = path.Find('/');
if (slash == nullptr) if (slash == nullptr)

View File

@ -85,7 +85,7 @@ uri_after_scheme(std::string_view uri) noexcept
} }
bool bool
uri_has_scheme(const char *uri) noexcept uri_has_scheme(std::string_view uri) noexcept
{ {
return !uri_get_scheme(uri).empty(); return !uri_get_scheme(uri).empty();
} }

View File

@ -40,7 +40,7 @@
*/ */
gcc_pure gcc_pure
bool bool
uri_has_scheme(const char *uri) noexcept; uri_has_scheme(std::string_view uri) noexcept;
/** /**
* Returns the scheme name of the specified URI, or an empty string. * Returns the scheme name of the specified URI, or an empty string.