release v0.21.16

-----BEGIN PGP SIGNATURE-----
 
 iQJEBAABCgAuFiEEA5IzWngIOJSkMBxDI26KWMbbRRIFAl2m6cwQHG1heEBtdXNp
 Y3BkLm9yZwAKCRAjbopYxttFEuMlD/9W6UTA9WfbeB2k8F2gFvy30w3jarsIdANG
 iRl2qW8a/MSp6zNj5t3rIH/JTOPEVXgB7c844gIC2VHkKAu4M3kV2sqa7cDEcq6o
 tFH5npemuCFbpkkAqXHpgFindWGMOqZy01PCN8m70y9IbfmI0Q25jngKeeuzgZ9O
 4DHw9IniWNamAi9H3MdGt6BpyuQ+EJ+FOfuJdsJkLgfK15qMn/3LwNoUyCxjyK/K
 brdQO0qpBg7dLZoGb6ER7qkyV1Cp+z/Mqeeocn4iQf3RuepIKzhZFMB1MY9FS7O1
 YAA89Lpk9mvLqx1/LkArrPEOv7k5Ia5KSmxZJ5dsrdXm/TKVM1k0MxZuE7LoXLXp
 wbdhXFoyhuL6lwLkw20wj1zqcTGMAYIp6t48YbDlVy59f/9OVROr++pCQsY+3L2t
 JPY90z6hf6yDF5yZCucSt7gin/WXRdeQLTgAxd8EqGqFgIRrW0GZhssg+7O1iGCq
 aSAVlxfzhVFxz7eyo4u3Dq/+d6gh3NRzV6exUYMxp3WHu7eweemlnKyxPxQ1lvSF
 5EkZXC56wQp0JIcIRYLEXkJN8lmIy/i0xHaOLDB7cJN23CC5Z68Up6peCzvVPp+E
 PIzOtT+4/FnQ6euu6KLHeiDyTWxdmGSrSP1W6cc/FpyLU86ZV/5tLg0bEaNxb9Sl
 lYfRth0D/A==
 =9Cv+
 -----END PGP SIGNATURE-----

Merge tag 'v0.21.16'

release v0.21.16
This commit is contained in:
Max Kellermann 2019-10-16 12:03:12 +02:00
commit e1867a99e9
12 changed files with 249 additions and 38 deletions

11
NEWS
View File

@ -32,6 +32,17 @@ ver 0.22 (not yet released)
* switch to C++17 * switch to C++17
- GCC 7 or clang 4 (or newer) recommended - GCC 7 or clang 4 (or newer) recommended
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) ver 0.21.15 (2019/09/25)
* decoder * decoder
- dsdiff, dsf: fix displayed bit rate - dsdiff, dsf: fix displayed bit rate

View File

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

View File

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

View File

@ -30,6 +30,7 @@
#ifndef CURL_EASY_HXX #ifndef CURL_EASY_HXX
#define CURL_EASY_HXX #define CURL_EASY_HXX
#include "String.hxx"
#include "util/Compiler.h" #include "util/Compiler.h"
#include <curl/curl.h> #include <curl/curl.h>
@ -208,8 +209,8 @@ public:
return ::curl_easy_pause(handle, CURLPAUSE_CONT) == CURLE_OK; return ::curl_easy_pause(handle, CURLPAUSE_CONT) == CURLE_OK;
} }
char *Escape(const char *string, int length=0) const noexcept { CurlString Escape(const char *string, int length=0) const noexcept {
return curl_easy_escape(handle, string, length); return CurlString(curl_easy_escape(handle, string, length));
} }
}; };

71
src/lib/curl/Escape.cxx Normal file
View 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
View 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

View File

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

77
src/lib/curl/String.hxx Normal file
View 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

View File

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

View File

@ -20,7 +20,7 @@ if icu_dep.found()
elif not get_option('iconv').disabled() elif not get_option('iconv').disabled()
have_iconv = compiler.has_function('iconv') have_iconv = compiler.has_function('iconv')
conf.set('HAVE_ICONV', have_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') error('iconv() not available')
endif endif
endif endif

View File

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

View File

@ -25,8 +25,10 @@
#include "lib/curl/Init.hxx" #include "lib/curl/Init.hxx"
#include "lib/curl/Global.hxx" #include "lib/curl/Global.hxx"
#include "lib/curl/Slist.hxx" #include "lib/curl/Slist.hxx"
#include "lib/curl/String.hxx"
#include "lib/curl/Request.hxx" #include "lib/curl/Request.hxx"
#include "lib/curl/Handler.hxx" #include "lib/curl/Handler.hxx"
#include "lib/curl/Escape.hxx"
#include "lib/expat/ExpatParser.hxx" #include "lib/expat/ExpatParser.hxx"
#include "fs/Traits.hxx" #include "fs/Traits.hxx"
#include "event/DeferEvent.hxx" #include "event/DeferEvent.hxx"
@ -34,7 +36,6 @@
#include "thread/Cond.hxx" #include "thread/Cond.hxx"
#include "time/Parser.hxx" #include "time/Parser.hxx"
#include "util/ASCII.hxx" #include "util/ASCII.hxx"
#include "util/IterableSplitString.hxx"
#include "util/RuntimeError.hxx" #include "util/RuntimeError.hxx"
#include "util/StringCompare.hxx" #include "util/StringCompare.hxx"
#include "util/StringFormat.hxx" #include "util/StringFormat.hxx"
@ -74,26 +75,15 @@ CurlStorage::MapUTF8(const char *uri_utf8) const noexcept
if (StringIsEmpty(uri_utf8)) if (StringIsEmpty(uri_utf8))
return base; return base;
CurlEasy easy; std::string path_esc = CurlEscapeUriPath(uri_utf8);
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);
}
return PathTraitsUTF8::Build(base.c_str(), path_esc.c_str()); return PathTraitsUTF8::Build(base.c_str(), path_esc.c_str());
} }
const char * const char *
CurlStorage::MapToRelativeUTF8(const char *uri_utf8) const noexcept CurlStorage::MapToRelativeUTF8(const char *uri_utf8) const noexcept
{ {
// TODO: escape/unescape? return PathTraitsUTF8::Relative(base.c_str(),
CurlUnescape(uri_utf8).c_str());
return PathTraitsUTF8::Relative(base.c_str(), uri_utf8);
} }
class BlockingHttpRequest : protected CurlResponseHandler { class BlockingHttpRequest : protected CurlResponseHandler {
@ -128,6 +118,10 @@ public:
std::rethrow_exception(postponed_error); std::rethrow_exception(postponed_error);
} }
CURL *GetEasy() noexcept {
return request.Get();
}
protected: protected:
void SetDone() { void SetDone() {
assert(!done); assert(!done);
@ -265,6 +259,8 @@ public:
CommonExpatParser(ExpatNamespaceSeparator{'|'}) CommonExpatParser(ExpatNamespaceSeparator{'|'})
{ {
request.SetOption(CURLOPT_CUSTOMREQUEST, "PROPFIND"); request.SetOption(CURLOPT_CUSTOMREQUEST, "PROPFIND");
request.SetOption(CURLOPT_FOLLOWLOCATION, 1l);
request.SetOption(CURLOPT_MAXREDIRS, 1l);
request_headers.Append(StringFormat<40>("depth: %u", depth)); request_headers.Append(StringFormat<40>("depth: %u", depth));
@ -273,6 +269,7 @@ public:
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:getcontenttype/></a:prop>" "<a:prop><a:getcontenttype/></a:prop>"
"<a:prop><a:getcontentlength/></a:prop>" "<a:prop><a:getcontentlength/></a:prop>"
"</a:propfind>"); "</a:propfind>");
@ -280,6 +277,7 @@ public:
// TODO: send request body // TODO: send request body
} }
using BlockingHttpRequest::GetEasy;
using BlockingHttpRequest::Wait; using BlockingHttpRequest::Wait;
protected: protected:
@ -450,9 +448,7 @@ CurlStorage::GetInfo(const char *uri_utf8, gcc_unused bool follow)
{ {
// TODO: escape the given URI // TODO: escape the given URI
std::string uri = base; const auto uri = MapUTF8(uri_utf8);
uri += uri_utf8;
return HttpGetInfoOperation(*curl, uri.c_str()).Perform(); return HttpGetInfoOperation(*curl, uri.c_str()).Perform();
} }
@ -499,7 +495,11 @@ private:
if (path == nullptr) if (path == nullptr)
return 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) if (path == nullptr || *path == 0)
return nullptr; return nullptr;
@ -525,10 +525,7 @@ protected:
if (escaped_name.IsNull()) if (escaped_name.IsNull())
return; return;
// TODO: unescape entries.emplace_front(CurlUnescape(GetEasy(), escaped_name));
const auto name = escaped_name;
entries.emplace_front(std::string(name.data, name.size));
auto &info = entries.front().info; auto &info = entries.front().info;
info = StorageFileInfo(r.collection info = StorageFileInfo(r.collection
@ -542,10 +539,7 @@ protected:
std::unique_ptr<StorageDirectoryReader> std::unique_ptr<StorageDirectoryReader>
CurlStorage::OpenDirectory(const char *uri_utf8) CurlStorage::OpenDirectory(const char *uri_utf8)
{ {
// TODO: escape the given URI std::string uri = MapUTF8(uri_utf8);
std::string uri = base;
uri += uri_utf8;
/* collection URIs must end with a slash */ /* collection URIs must end with a slash */
if (uri.back() != '/') if (uri.back() != '/')