Compare commits

...

14 Commits

Author SHA1 Message Date
Max Kellermann
9b95e65bd9 release v0.21.16 2019-10-16 11:58:36 +02:00
Max Kellermann
12a86c4975 queue/PlaylistEdit: fix relative destination offset when moving a range
Commit 13208bf5a7 added range support to
the `move` command, but applied the wrong offset to the `to` variable.
When the source range is before the current song, and the song thus
gets decremented by the range size, then the final destination offset
must also be decremented by the range size.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/663
2019-10-15 17:00:16 +02:00
Max Kellermann
0b9435858b storage/curl: unescape file names from PROPFIND
This is the last missing piece for https://github.com/MusicPlayerDaemon/MPD/issues/662
2019-10-15 16:49:17 +02:00
Max Kellermann
f0386459ee storage/curl: follow redirects for collections without trailing slash 2019-10-15 16:42:39 +02:00
Max Kellermann
e98d4670b8 storage/curl: work around different case in hex digits 2019-10-15 16:26:53 +02:00
Max Kellermann
56cc42b752 storage/curl: use MapUTF8() to reuse existing escaping code
Commit 29f78b18b1 continued.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/662
2019-10-15 16:26:53 +02:00
Max Kellermann
ead208987d storage/curl: unescape URI in MapToRelativeUTF8() 2019-10-15 16:26:49 +02:00
Max Kellermann
364acc8949 lib/curl/Escape: add CurlUnescape() 2019-10-15 13:39:02 +02:00
Max Kellermann
a8f4d2b6fc storage/curl: move code to EscapeUriPath() 2019-10-15 13:24:06 +02:00
Max Kellermann
0eb113e7c6 lib/curl/String: OO wrapper for allocated strings returned from CURL 2019-10-15 13:13:39 +02:00
Andre Heider
96a9670c69 lib/icu: fix build with iconv() 2019-10-07 13:34:04 +02:00
Max Kellermann
dcc5ce6792 storage/curl: request the "resourcetype" property to fix update
Without requesting the property, "good" WebDAV servers would not send
it, and so MPD could never recognize a directory, failing the database
update.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/660
2019-10-07 12:44:48 +02:00
Max Kellermann
23d08820a2 db/update/Walk: fix crash when music_directory is not a directory
Add a runtime sanity check to avoid the assertion failure.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/660
2019-10-07 12:24:25 +02:00
Max Kellermann
b9b906ab20 increment version number to 0.21.16 2019-10-07 12:24:25 +02:00
14 changed files with 252 additions and 40 deletions

11
NEWS

@@ -1,3 +1,14 @@
ver 0.21.16 (2019/10/16)
* queue
- fix relative destination offset when moving a range
* storage
- curl: request the "resourcetype" property to fix database update
- curl: URL-encode more paths
- curl: follow redirects for collections without trailing slash
* update
- fix crash when music_directory is not a directory
* fix build with iconv() instead of ICU
ver 0.21.15 (2019/09/25)
* decoder
- dsdiff, dsf: fix displayed bit rate

@@ -2,8 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.musicpd"
android:installLocation="auto"
android:versionCode="38"
android:versionName="0.21.15">
android:versionCode="39"
android:versionName="0.21.16">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="26"/>

@@ -38,7 +38,7 @@ author = 'Max Kellermann'
# built documents.
#
# The short X.Y version.
version = '0.21.15'
version = '0.21.16'
# The full version, including alpha/beta/rc tags.
release = version

@@ -1,7 +1,7 @@
project(
'mpd',
['c', 'cpp'],
version: '0.21.15',
version: '0.21.16',
meson_version: '>= 0.49.0',
default_options: [
'c_std=c99',

@@ -493,6 +493,12 @@ UpdateWalk::Walk(Directory &root, const char *path, bool discard) noexcept
if (!GetInfo(storage, "", info))
return false;
if (!info.IsDirectory()) {
FormatError(update_domain, "Not a directory: %s",
storage.MapUTF8("").c_str());
return false;
}
ExcludeList exclude_list;
UpdateDirectory(root, exclude_list, info);

@@ -30,6 +30,8 @@
#ifndef CURL_EASY_HXX
#define CURL_EASY_HXX
#include "String.hxx"
#include <curl/curl.h>
#include <utility>
@@ -88,8 +90,8 @@ public:
throw std::runtime_error(curl_easy_strerror(code));
}
char *Escape(const char *string, int length=0) const noexcept {
return curl_easy_escape(handle, string, length);
CurlString Escape(const char *string, int length=0) const noexcept {
return CurlString(curl_easy_escape(handle, string, length));
}
};

71
src/lib/curl/Escape.cxx Normal file

@@ -0,0 +1,71 @@
/*
* Copyright (C) 2018 Max Kellermann <max.kellermann@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "Escape.hxx"
#include "Easy.hxx"
#include "String.hxx"
#include "util/IterableSplitString.hxx"
std::string
CurlEscapeUriPath(CURL *curl, StringView src) noexcept
{
std::string dest;
for (const auto i : IterableSplitString(src, '/')) {
CurlString escaped(curl_easy_escape(curl, i.data, i.size));
if (!dest.empty())
dest.push_back('/');
dest += escaped.c_str();
}
return dest;
}
std::string
CurlEscapeUriPath(StringView src) noexcept
{
CurlEasy easy;
return CurlEscapeUriPath(easy.Get(), src);
}
std::string
CurlUnescape(CURL *curl, StringView src) noexcept
{
int outlength;
CurlString tmp(curl_easy_unescape(curl, src.data, src.size,
&outlength));
return std::string(tmp.c_str(), outlength);
}
std::string
CurlUnescape(StringView src) noexcept
{
CurlEasy easy;
return CurlUnescape(easy.Get(), src);
}

51
src/lib/curl/Escape.hxx Normal file

@@ -0,0 +1,51 @@
/*
* Copyright (C) 2018 Max Kellermann <max.kellermann@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef CURL_ESCAPE_HXX
#define CURL_ESCAPE_HXX
#include <curl/curl.h>
#include <string>
struct StringView;
std::string
CurlEscapeUriPath(CURL *curl, StringView src) noexcept;
std::string
CurlEscapeUriPath(StringView src) noexcept;
std::string
CurlUnescape(CURL *curl, StringView src) noexcept;
std::string
CurlUnescape(StringView src) noexcept;
#endif

@@ -28,6 +28,7 @@
*/
#include "Form.hxx"
#include "String.hxx"
std::string
EncodeForm(CURL *curl,
@@ -43,12 +44,10 @@ EncodeForm(CURL *curl,
result.push_back('=');
if (!i.second.empty()) {
char *value = curl_easy_escape(curl, i.second.data(),
i.second.length());
if (value != nullptr) {
CurlString value(curl_easy_escape(curl, i.second.data(),
i.second.length()));
if (value)
result.append(value);
curl_free(value);
}
}
}

77
src/lib/curl/String.hxx Normal file

@@ -0,0 +1,77 @@
/*
* Copyright 2019 Max Kellermann <max.kellermann@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef CURL_STRING_HXX
#define CURL_STRING_HXX
#include <curl/curl.h>
#include <utility>
/**
* An OO wrapper for an allocated string to be freed with curl_free().
*/
class CurlString {
char *p = nullptr;
public:
CurlString() noexcept = default;
CurlString(std::nullptr_t) noexcept {}
explicit CurlString(char *_p) noexcept
:p(_p) {}
CurlString(CurlString &&src) noexcept
:p(std::exchange(src.p, nullptr)) {}
~CurlString() noexcept {
if (p != nullptr)
curl_free(p);
}
CurlString &operator=(CurlString &&src) noexcept {
using std::swap;
swap(p, src.p);
return *this;
}
operator bool() const noexcept {
return p != nullptr;
}
operator const char *() const noexcept {
return p;
}
const char *c_str() const noexcept {
return p;
}
};
#endif

@@ -11,6 +11,7 @@ curl = static_library(
'Init.cxx',
'Global.cxx',
'Request.cxx',
'Escape.cxx',
'Form.cxx',
include_directories: inc,
dependencies: [

@@ -20,7 +20,7 @@ if icu_dep.found()
elif not get_option('iconv').disabled()
have_iconv = compiler.has_function('iconv')
conf.set('HAVE_ICONV', have_iconv)
if get_option('iconv').enabled()
if not have_iconv and get_option('iconv').enabled()
error('iconv() not available')
endif
endif

@@ -353,7 +353,7 @@ playlist::MoveRange(PlayerControl &pc, unsigned start, unsigned end, int to)
return;
to = (currentSong + abs(to)) % GetLength();
if (start < (unsigned)to)
to--;
to -= end - start;
}
queue.MoveRange(start, end, to);

@@ -25,8 +25,10 @@
#include "lib/curl/Init.hxx"
#include "lib/curl/Global.hxx"
#include "lib/curl/Slist.hxx"
#include "lib/curl/String.hxx"
#include "lib/curl/Request.hxx"
#include "lib/curl/Handler.hxx"
#include "lib/curl/Escape.hxx"
#include "lib/expat/ExpatParser.hxx"
#include "fs/Traits.hxx"
#include "event/Call.hxx"
@@ -35,7 +37,6 @@
#include "thread/Cond.hxx"
#include "util/ASCII.hxx"
#include "util/ChronoUtil.hxx"
#include "util/IterableSplitString.hxx"
#include "util/RuntimeError.hxx"
#include "util/StringCompare.hxx"
#include "util/StringFormat.hxx"
@@ -77,26 +78,15 @@ CurlStorage::MapUTF8(const char *uri_utf8) const noexcept
if (StringIsEmpty(uri_utf8))
return base;
CurlEasy easy;
std::string path_esc;
for (auto elt: IterableSplitString(uri_utf8, '/')) {
char *elt_esc = easy.Escape(elt.data, elt.size);
if (!path_esc.empty())
path_esc.push_back('/');
path_esc += elt_esc;
curl_free(elt_esc);
}
std::string path_esc = CurlEscapeUriPath(uri_utf8);
return PathTraitsUTF8::Build(base.c_str(), path_esc.c_str());
}
const char *
CurlStorage::MapToRelativeUTF8(const char *uri_utf8) const noexcept
{
// TODO: escape/unescape?
return PathTraitsUTF8::Relative(base.c_str(), uri_utf8);
return PathTraitsUTF8::Relative(base.c_str(),
CurlUnescape(uri_utf8).c_str());
}
class BlockingHttpRequest : protected CurlResponseHandler {
@@ -132,6 +122,10 @@ public:
std::rethrow_exception(postponed_error);
}
CURL *GetEasy() noexcept {
return request.Get();
}
protected:
void SetDone() {
assert(!done);
@@ -269,6 +263,8 @@ public:
CommonExpatParser(ExpatNamespaceSeparator{'|'})
{
request.SetOption(CURLOPT_CUSTOMREQUEST, "PROPFIND");
request.SetOption(CURLOPT_FOLLOWLOCATION, 1l);
request.SetOption(CURLOPT_MAXREDIRS, 1l);
request_headers.Append(StringFormat<40>("depth: %u", depth));
@@ -277,6 +273,7 @@ public:
request.SetOption(CURLOPT_POSTFIELDS,
"<?xml version=\"1.0\"?>\n"
"<a:propfind xmlns:a=\"DAV:\">"
"<a:prop><a:resourcetype/></a:prop>"
"<a:prop><a:getcontenttype/></a:prop>"
"<a:prop><a:getcontentlength/></a:prop>"
"</a:propfind>");
@@ -284,6 +281,7 @@ public:
// TODO: send request body
}
using BlockingHttpRequest::GetEasy;
using BlockingHttpRequest::Wait;
protected:
@@ -454,9 +452,7 @@ CurlStorage::GetInfo(const char *uri_utf8, gcc_unused bool follow)
{
// TODO: escape the given URI
std::string uri = base;
uri += uri_utf8;
const auto uri = MapUTF8(uri_utf8);
return HttpGetInfoOperation(*curl, uri.c_str()).Perform();
}
@@ -503,7 +499,11 @@ private:
if (path == nullptr)
return nullptr;
path = StringAfterPrefix(path, base_path.c_str());
/* kludge: ignoring case in this comparison to avoid
false negatives if the web server uses a different
case in hex digits in escaped characters; TODO:
implement properly */
path = StringAfterPrefixIgnoreCase(path, base_path.c_str());
if (path == nullptr || *path == 0)
return nullptr;
@@ -529,10 +529,7 @@ protected:
if (escaped_name.IsNull())
return;
// TODO: unescape
const auto name = escaped_name;
entries.emplace_front(std::string(name.data, name.size));
entries.emplace_front(CurlUnescape(GetEasy(), escaped_name));
auto &info = entries.front().info;
info = StorageFileInfo(r.collection
@@ -546,10 +543,7 @@ protected:
std::unique_ptr<StorageDirectoryReader>
CurlStorage::OpenDirectory(const char *uri_utf8)
{
// TODO: escape the given URI
std::string uri = base;
uri += uri_utf8;
std::string uri = MapUTF8(uri_utf8);
/* collection URIs must end with a slash */
if (uri.back() != '/')