From bc5a53574c64eb51d2cbf31b26f4607e566eeaab Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Sat, 25 Oct 2014 20:42:50 +0200
Subject: [PATCH 01/28] decoder/faad: remove workaround for ancient libfaad2
 ABI bug

Many years ago, FAAD had a serious ABI bug: the NeAACDecInit()
prototype in its header declared the "samplerate" parameter to be
"unsigned long *", but internally, the function assumed it was
"uint32_t *" instead.  On 32 bit machines, that was no difference, but
on 64 bit, this left one portion of the return value uninitialized;
and worse, on big-endian, the wrong word was filled.  This bug had to
be worked around in MPD (commit 9c4e97a6).

A few months later, the bug was fixed in the FAAD CVS in commit 1.117
on file libfaad/decoder.c; the commit message was:

 "Use public headers internally to prevent duplicate declarations"

The commit message was too brief at best; the problem was not
duplicate declarations, but a prototype mismatch.  No mention of the
bug fix in the ChangeLog.

The MPD project never learned about this bug fix, and so MPD would
always pass a "uin32_t *" dressed up as a "unsigned long *".  Nearly 6
years later, it's about time to fix this second ABI problem.  Let's
kill the workaround!
---
 NEWS                                       |  1 +
 m4/faad.m4                                 | 31 +---------------------
 src/decoder/plugins/FaadDecoderPlugin.cxx  | 12 ++-------
 src/decoder/plugins/Mp4v2DecoderPlugin.cxx | 12 ++-------
 4 files changed, 6 insertions(+), 50 deletions(-)

diff --git a/NEWS b/NEWS
index e1497a0ed..2a6c5c439 100644
--- a/NEWS
+++ b/NEWS
@@ -1,5 +1,6 @@
 ver 0.19.2 (not yet released)
 * decoder
+  - faad: remove workaround for ancient libfaad2 ABI bug
   - ffmpeg: recognize MIME type audio/aacp
 * output
   - fix memory leak after filter initialization error
diff --git a/m4/faad.m4 b/m4/faad.m4
index 5ca520e79..9dcb1ccab 100644
--- a/m4/faad.m4
+++ b/m4/faad.m4
@@ -62,36 +62,7 @@ int main() {
 	CPPFLAGS=$oldcppflags
 fi
 
-if test x$enable_aac = xyes; then
-	oldcflags=$CFLAGS
-	oldlibs=$LIBS
-	oldcppflags=$CPPFLAGS
-	CFLAGS="$CFLAGS $FAAD_CFLAGS -Werror"
-	LIBS="$LIBS $FAAD_LIBS"
-	CPPFLAGS=$CFLAGS
-
-	AC_MSG_CHECKING(for broken libfaad headers)
-	AC_COMPILE_IFELSE([AC_LANG_SOURCE([
-#include <faad.h>
-#include <stddef.h>
-#include <stdint.h>
-
-int main() {
-	unsigned char channels;
-	uint32_t sample_rate;
-
-	NeAACDecInit2(NULL, NULL, 0, &sample_rate, &channels);
-	return 0;
-}
-	])],
-		[AC_MSG_RESULT(correct)],
-		[AC_MSG_RESULT(broken);
-		AC_DEFINE(HAVE_FAAD_LONG, 1, [Define if faad.h uses the broken "unsigned long" pointers])])
-
-	CFLAGS=$oldcflags
-	LIBS=$oldlibs
-	CPPFLAGS=$oldcppflags
-else
+if test x$enable_aac = xno; then
 	FAAD_LIBS=""
 	FAAD_CFLAGS=""
 fi
diff --git a/src/decoder/plugins/FaadDecoderPlugin.cxx b/src/decoder/plugins/FaadDecoderPlugin.cxx
index 793ab1011..add23aaa4 100644
--- a/src/decoder/plugins/FaadDecoderPlugin.cxx
+++ b/src/decoder/plugins/FaadDecoderPlugin.cxx
@@ -255,20 +255,12 @@ faad_decoder_init(NeAACDecHandle decoder, DecoderBuffer &buffer,
 	}
 
 	uint8_t channels;
-	uint32_t sample_rate;
-#ifdef HAVE_FAAD_LONG
-	/* neaacdec.h declares all arguments as "unsigned long", but
-	   internally expects uint32_t pointers.  To avoid gcc
-	   warnings, use this workaround. */
-	unsigned long *sample_rate_p = (unsigned long *)(void *)&sample_rate;
-#else
-	uint32_t *sample_rate_p = &sample_rate;
-#endif
+	unsigned long sample_rate;
 	long nbytes = NeAACDecInit(decoder,
 				   /* deconst hack, libfaad requires this */
 				   const_cast<unsigned char *>(data.data),
 				   data.size,
-				   sample_rate_p, &channels);
+				   &sample_rate, &channels);
 	if (nbytes < 0) {
 		error.Set(faad_decoder_domain, "Not an AAC stream");
 		return false;
diff --git a/src/decoder/plugins/Mp4v2DecoderPlugin.cxx b/src/decoder/plugins/Mp4v2DecoderPlugin.cxx
index bf97763c5..34bccd243 100644
--- a/src/decoder/plugins/Mp4v2DecoderPlugin.cxx
+++ b/src/decoder/plugins/Mp4v2DecoderPlugin.cxx
@@ -39,15 +39,7 @@ static MP4TrackId
 mp4_get_aac_track(MP4FileHandle handle, NeAACDecHandle decoder,
 		  AudioFormat &audio_format, Error &error)
 {
-	uint32_t sample_rate;
-#ifdef HAVE_FAAD_LONG
-	/* neaacdec.h declares all arguments as "unsigned long", but
-	   internally expects uint32_t pointers.  To avoid gcc
-	   warnings, use this workaround. */
-	unsigned long *sample_rate_r = (unsigned long*)&sample_rate;
-#else
-	uint32_t *sample_rate_r = sample_rate;
-#endif
+	unsigned long sample_rate;
 
 	const MP4TrackId tracks = MP4GetNumberOfTracks(handle);
 
@@ -80,7 +72,7 @@ mp4_get_aac_track(MP4FileHandle handle, NeAACDecHandle decoder,
 
 		uint8_t channels;
 		int32_t nbytes = NeAACDecInit(decoder, buff, buff_size,
-				       sample_rate_r, &channels);
+					      &sample_rate, &channels);
 
 		free(buff);
 

From d7f024c51047222dda4224c56865d66c8f384c76 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Sat, 25 Oct 2014 21:25:49 +0200
Subject: [PATCH 02/28] OutputThread: fall back to PCM if given DSD sample rate
 is not supported

Works around the "PCM conversion from f to dsd is not implemented"
error message that prevents DSD playback.
---
 NEWS                        |  1 +
 src/output/OutputThread.cxx | 30 ++++++++++++++++++++++++++++++
 2 files changed, 31 insertions(+)

diff --git a/NEWS b/NEWS
index 2a6c5c439..658ca8c5f 100644
--- a/NEWS
+++ b/NEWS
@@ -4,6 +4,7 @@ ver 0.19.2 (not yet released)
   - ffmpeg: recognize MIME type audio/aacp
 * output
   - fix memory leak after filter initialization error
+  - fall back to PCM if given DSD sample rate is not supported
 * fix assertion failure on unsupported PCM conversion
 * auto-disable plugins that require GLib when --disable-glib is used
 
diff --git a/src/output/OutputThread.cxx b/src/output/OutputThread.cxx
index 54664bb65..2ec0670c1 100644
--- a/src/output/OutputThread.cxx
+++ b/src/output/OutputThread.cxx
@@ -22,6 +22,7 @@
 #include "OutputAPI.hxx"
 #include "Domain.hxx"
 #include "pcm/PcmMix.hxx"
+#include "pcm/Domain.hxx"
 #include "notify.hxx"
 #include "filter/FilterInternal.hxx"
 #include "filter/plugins/ConvertFilterPlugin.hxx"
@@ -165,6 +166,10 @@ AudioOutput::Open()
 	out_audio_format.ApplyMask(config_audio_format);
 
 	mutex.unlock();
+
+	const AudioFormat retry_audio_format = out_audio_format;
+
+ retry_without_dsd:
 	success = ao_plugin_open(this, out_audio_format, error);
 	mutex.lock();
 
@@ -189,6 +194,31 @@ AudioOutput::Open()
 
 		mutex.unlock();
 		ao_plugin_close(this);
+
+		if (error.IsDomain(pcm_domain) &&
+		    out_audio_format.format == SampleFormat::DSD) {
+			/* if the audio output supports DSD, but not
+			   the given sample rate, it asks MPD to
+			   resample; resampling DSD however is not
+			   implemented; our last resort is to give up
+			   DSD and fall back to PCM */
+
+			// TODO: clean up this workaround
+
+			FormatError(output_domain, "Retrying without DSD");
+
+			out_audio_format = retry_audio_format;
+			out_audio_format.format = SampleFormat::FLOAT;
+
+			/* clear the Error to allow reusing it */
+			error.Clear();
+
+			/* sorry for the "goto" - this is a workaround
+			   for the stable branch that should be as
+			   unintrusive as possible */
+			goto retry_without_dsd;
+		}
+
 		CloseFilter();
 		mutex.lock();
 

From 394e3be482ed492e5d90222dab828c2448fbccce Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Sun, 26 Oct 2014 08:14:16 +0100
Subject: [PATCH 03/28] playlist/m3u: recognize the file suffix ".m3u8"

---
 NEWS                                          | 2 ++
 src/playlist/plugins/ExtM3uPlaylistPlugin.cxx | 1 +
 src/playlist/plugins/M3uPlaylistPlugin.cxx    | 1 +
 3 files changed, 4 insertions(+)

diff --git a/NEWS b/NEWS
index 658ca8c5f..6b2282a20 100644
--- a/NEWS
+++ b/NEWS
@@ -1,4 +1,6 @@
 ver 0.19.2 (not yet released)
+* playlist
+  - m3u: recognize the file suffix ".m3u8"
 * decoder
   - faad: remove workaround for ancient libfaad2 ABI bug
   - ffmpeg: recognize MIME type audio/aacp
diff --git a/src/playlist/plugins/ExtM3uPlaylistPlugin.cxx b/src/playlist/plugins/ExtM3uPlaylistPlugin.cxx
index fdd4357ca..93316ca6c 100644
--- a/src/playlist/plugins/ExtM3uPlaylistPlugin.cxx
+++ b/src/playlist/plugins/ExtM3uPlaylistPlugin.cxx
@@ -130,6 +130,7 @@ ExtM3uPlaylist::NextSong()
 
 static const char *const extm3u_suffixes[] = {
 	"m3u",
+	"m3u8",
 	nullptr
 };
 
diff --git a/src/playlist/plugins/M3uPlaylistPlugin.cxx b/src/playlist/plugins/M3uPlaylistPlugin.cxx
index a4125bc70..0428d291a 100644
--- a/src/playlist/plugins/M3uPlaylistPlugin.cxx
+++ b/src/playlist/plugins/M3uPlaylistPlugin.cxx
@@ -60,6 +60,7 @@ M3uPlaylist::NextSong()
 
 static const char *const m3u_suffixes[] = {
 	"m3u",
+	"m3u8",
 	nullptr
 };
 

From 217d88f21f11236478dd41c37b67bef5b0a06497 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Tue, 28 Oct 2014 22:10:47 +0100
Subject: [PATCH 04/28] TextInputStream: don't ignore unterminated last line

---
 NEWS                          |  1 +
 src/input/TextInputStream.cxx | 20 ++++++++++++++++----
 2 files changed, 17 insertions(+), 4 deletions(-)

diff --git a/NEWS b/NEWS
index 6b2282a20..5541db7f2 100644
--- a/NEWS
+++ b/NEWS
@@ -1,5 +1,6 @@
 ver 0.19.2 (not yet released)
 * playlist
+  - m3u: don't ignore unterminated last line
   - m3u: recognize the file suffix ".m3u8"
 * decoder
   - faad: remove workaround for ancient libfaad2 ABI bug
diff --git a/src/input/TextInputStream.cxx b/src/input/TextInputStream.cxx
index b79f64bdc..5a8dcc065 100644
--- a/src/input/TextInputStream.cxx
+++ b/src/input/TextInputStream.cxx
@@ -38,8 +38,8 @@ TextInputStream::ReadLine()
 	while (true) {
 		auto dest = buffer.Write();
 		if (dest.size < 2) {
-			/* end of file (or line too long): terminate
-			   the current line */
+			/* line too long: terminate the current
+			   line */
 
 			assert(!dest.IsEmpty());
 			dest[0] = 0;
@@ -66,7 +66,19 @@ TextInputStream::ReadLine()
 		if (line != nullptr)
 			return line;
 
-		if (nbytes == 0)
-			return nullptr;
+		if (nbytes == 0) {
+			/* end of file: see if there's an unterminated
+			   line */
+
+			dest = buffer.Write();
+			assert(!dest.IsEmpty());
+			dest[0] = 0;
+
+			auto r = buffer.Read();
+			buffer.Clear();
+			return r.IsEmpty()
+				? nullptr
+				: r.data;
+		}
 	}
 }

From 54c591bd9d9657065fb1c338eefe416840c78d3e Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Tue, 28 Oct 2014 22:22:30 +0100
Subject: [PATCH 05/28] decoder/mad: fix negative replay gain values

Negating an unsigned integer does not work.
---
 NEWS                                     | 1 +
 src/decoder/plugins/MadDecoderPlugin.cxx | 2 +-
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/NEWS b/NEWS
index 5541db7f2..66862543b 100644
--- a/NEWS
+++ b/NEWS
@@ -5,6 +5,7 @@ ver 0.19.2 (not yet released)
 * decoder
   - faad: remove workaround for ancient libfaad2 ABI bug
   - ffmpeg: recognize MIME type audio/aacp
+  - mad: fix negative replay gain values
 * output
   - fix memory leak after filter initialization error
   - fall back to PCM if given DSD sample rate is not supported
diff --git a/src/decoder/plugins/MadDecoderPlugin.cxx b/src/decoder/plugins/MadDecoderPlugin.cxx
index 41efb593d..de6c9b127 100644
--- a/src/decoder/plugins/MadDecoderPlugin.cxx
+++ b/src/decoder/plugins/MadDecoderPlugin.cxx
@@ -657,7 +657,7 @@ parse_lame(struct lame *lame, struct mad_bitptr *ptr, int *bitlen)
 	unsigned name = mad_bit_read(ptr, 3); /* gain name */
 	unsigned orig = mad_bit_read(ptr, 3); /* gain originator */
 	unsigned sign = mad_bit_read(ptr, 1); /* sign bit */
-	unsigned gain = mad_bit_read(ptr, 9); /* gain*10 */
+	int gain = mad_bit_read(ptr, 9); /* gain*10 */
 	if (gain && name == 1 && orig != 0) {
 		lame->track_gain = ((sign ? -gain : gain) / 10.0) + adj;
 		FormatDebug(mad_domain, "LAME track gain found: %f",

From 7350144ab36879ec92fdad3f4d3919913e660799 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Fri, 31 Oct 2014 14:59:27 +0100
Subject: [PATCH 06/28] PlaylistFile: don't allow empty playlist name

---
 NEWS                 | 1 +
 src/PlaylistFile.cxx | 4 ++++
 2 files changed, 5 insertions(+)

diff --git a/NEWS b/NEWS
index 66862543b..64490ac36 100644
--- a/NEWS
+++ b/NEWS
@@ -1,5 +1,6 @@
 ver 0.19.2 (not yet released)
 * playlist
+  - don't allow empty playlist name
   - m3u: don't ignore unterminated last line
   - m3u: recognize the file suffix ".m3u8"
 * decoder
diff --git a/src/PlaylistFile.cxx b/src/PlaylistFile.cxx
index f0aa2d2d7..ab269378a 100644
--- a/src/PlaylistFile.cxx
+++ b/src/PlaylistFile.cxx
@@ -64,6 +64,10 @@ spl_global_init(void)
 bool
 spl_valid_name(const char *name_utf8)
 {
+	if (*name_utf8 == 0)
+		/* empty name not allowed */
+		return false;
+
 	/*
 	 * Not supporting '/' was done out of laziness, and we should
 	 * really strive to support it in the future.

From 6ad1e4d99aba346440e984a232a312ea49561e7d Mon Sep 17 00:00:00 2001
From: NanoTech <nanotech@nanotechcorp.net>
Date: Sun, 12 Oct 2014 16:23:33 -0600
Subject: [PATCH 07/28] Revert "Main: run the OS X native event loop"

This reverts commit f0be48ff90503d9ffa5b295fd4454eec753950ee
(except for the NEWS entry).

If libdispatch (GCD) is used before forking, it
can't safely be used again after forking.
---
 Makefile.am         |  1 -
 src/Main.cxx        |  2 --
 src/Main.hxx        | 11 -----------
 src/osx/OSXMain.cxx | 37 -------------------------------------
 4 files changed, 51 deletions(-)
 delete mode 100644 src/osx/OSXMain.cxx

diff --git a/Makefile.am b/Makefile.am
index c105b9945..dac3e1772 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -130,7 +130,6 @@ libmpd_a_SOURCES = \
 	src/IOThread.cxx src/IOThread.hxx \
 	src/Instance.cxx src/Instance.hxx \
 	src/win32/Win32Main.cxx \
-	src/osx/OSXMain.cxx \
 	src/GlobalEvents.cxx src/GlobalEvents.hxx \
 	src/MixRampInfo.hxx \
 	src/MusicBuffer.cxx src/MusicBuffer.hxx \
diff --git a/src/Main.cxx b/src/Main.cxx
index d17590e44..417e055d6 100644
--- a/src/Main.cxx
+++ b/src/Main.cxx
@@ -401,8 +401,6 @@ int main(int argc, char *argv[])
 {
 #ifdef WIN32
 	return win32_main(argc, argv);
-#elif __APPLE__
-	return osx_main(argc, argv);
 #else
 	return mpd_main(argc, argv);
 #endif
diff --git a/src/Main.hxx b/src/Main.hxx
index dae7a5043..7e3fecd0b 100644
--- a/src/Main.hxx
+++ b/src/Main.hxx
@@ -75,15 +75,4 @@ win32_app_stopping(void);
 
 #endif
 
-#ifdef __APPLE__
-
-/* Runs the OS X native event loop in the main thread, and runs
- * mpd_main on a new thread. This lets CoreAudio receive route
- * change notifications (e.g. plugging or unplugging headphones).
- * All hardware output on OS X ultimately uses CoreAudio internally.
- */
-int osx_main(int argc, char *argv[]);
-
-#endif
-
 #endif
diff --git a/src/osx/OSXMain.cxx b/src/osx/OSXMain.cxx
deleted file mode 100644
index 1ac9aec23..000000000
--- a/src/osx/OSXMain.cxx
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2003-2014 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "Main.hxx"
-
-#ifdef __APPLE__
-
-#include <stdlib.h>
-#include <dispatch/dispatch.h>
-
-int osx_main(int argc, char *argv[])
-{
-	dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
-		exit(mpd_main(argc, argv));
-	});
-	dispatch_main();
-	return EXIT_FAILURE; // unreachable, because dispatch_main never returns
-}
-
-#endif

From d42c0f1dc5063d50a62817b63a1c2a4507c46071 Mon Sep 17 00:00:00 2001
From: NanoTech <nanotech@nanotechcorp.net>
Date: Sun, 12 Oct 2014 16:29:43 -0600
Subject: [PATCH 08/28] Main: run the OS X native event loop after forking

---
 src/Main.cxx | 27 +++++++++++++++++++++++++++
 1 file changed, 27 insertions(+)

diff --git a/src/Main.cxx b/src/Main.cxx
index 417e055d6..2719c05e0 100644
--- a/src/Main.cxx
+++ b/src/Main.cxx
@@ -114,6 +114,10 @@
 #include <ws2tcpip.h>
 #endif
 
+#ifdef __APPLE__
+#include <dispatch/dispatch.h>
+#endif
+
 #include <limits.h>
 
 static constexpr unsigned DEFAULT_BUFFER_SIZE = 4096;
@@ -408,6 +412,8 @@ int main(int argc, char *argv[])
 
 #endif
 
+static int mpd_main_after_fork(struct options);
+
 #ifdef ANDROID
 static inline
 #endif
@@ -511,6 +517,27 @@ int mpd_main(int argc, char *argv[])
 	daemonize_begin(options.daemon);
 #endif
 
+#ifdef __APPLE__
+	/* Runs the OS X native event loop in the main thread, and runs
+	   the rest of mpd_main on a new thread. This lets CoreAudio receive
+	   route change notifications (e.g. plugging or unplugging headphones).
+	   All hardware output on OS X ultimately uses CoreAudio internally.
+	   This must be run after forking; if dispatch is called before forking,
+	   the child process will have a broken internal dispatch state. */
+	dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
+		exit(mpd_main_after_fork(options));
+	});
+	dispatch_main();
+	return EXIT_FAILURE; // unreachable, because dispatch_main never returns
+#else
+	return mpd_main_after_fork(options);
+#endif
+}
+
+static int mpd_main_after_fork(struct options options)
+{
+	Error error;
+
 	GlobalEvents::Initialize(*instance->event_loop);
 	GlobalEvents::Register(GlobalEvents::IDLE, idle_event_emitted);
 #ifdef WIN32

From eab32f2e5d8114d08e392001f0004284267bebd5 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Sat, 1 Nov 2014 12:45:47 +0100
Subject: [PATCH 09/28] util/UriUtil: add uri_get_suffix() overload that
 ignores query string

---
 src/util/UriUtil.cxx | 17 +++++++++++++++++
 src/util/UriUtil.hxx | 11 +++++++++++
 test/test_util.cxx   | 19 +++++++++++++++++++
 3 files changed, 47 insertions(+)

diff --git a/src/util/UriUtil.cxx b/src/util/UriUtil.cxx
index fdca47c00..62977e91b 100644
--- a/src/util/UriUtil.cxx
+++ b/src/util/UriUtil.cxx
@@ -54,6 +54,23 @@ uri_get_suffix(const char *uri)
 	return suffix;
 }
 
+const char *
+uri_get_suffix(const char *uri, UriSuffixBuffer &buffer)
+{
+	const char *suffix = uri_get_suffix(uri);
+	if (suffix == nullptr)
+		return nullptr;
+
+	const char *q = strchr(suffix, '?');
+	if (q != nullptr && size_t(q - suffix) < sizeof(buffer.data)) {
+		memcpy(buffer.data, suffix, q - suffix);
+		buffer.data[q - suffix] = 0;
+		suffix = buffer.data;
+	}
+
+	return suffix;
+}
+
 static const char *
 verify_uri_segment(const char *p)
 {
diff --git a/src/util/UriUtil.hxx b/src/util/UriUtil.hxx
index c2cc97a63..d478d5b92 100644
--- a/src/util/UriUtil.hxx
+++ b/src/util/UriUtil.hxx
@@ -42,6 +42,17 @@ gcc_pure
 const char *
 uri_get_suffix(const char *uri);
 
+struct UriSuffixBuffer {
+	char data[8];
+};
+
+/**
+ * Returns the file name suffix, ignoring the query string.
+ */
+gcc_pure
+const char *
+uri_get_suffix(const char *uri, UriSuffixBuffer &buffer);
+
 /**
  * Returns true if this is a safe "local" URI:
  *
diff --git a/test/test_util.cxx b/test/test_util.cxx
index aaadec68f..3e79aeca0 100644
--- a/test/test_util.cxx
+++ b/test/test_util.cxx
@@ -34,6 +34,25 @@ public:
 				     uri_get_suffix(".jpg"));
 		CPPUNIT_ASSERT_EQUAL((const char *)nullptr,
 				     uri_get_suffix("/foo/.jpg"));
+
+		/* the first overload does not eliminate the query
+		   string */
+		CPPUNIT_ASSERT_EQUAL(0, strcmp(uri_get_suffix("/foo/bar.jpg?query_string"),
+					       "jpg?query_string"));
+
+		/* ... but the second one does */
+		UriSuffixBuffer buffer;
+		CPPUNIT_ASSERT_EQUAL(0, strcmp(uri_get_suffix("/foo/bar.jpg?query_string",
+							      buffer),
+					       "jpg"));
+
+		/* repeat some of the above tests with the second overload */
+		CPPUNIT_ASSERT_EQUAL((const char *)nullptr,
+				     uri_get_suffix("/foo/bar", buffer));
+		CPPUNIT_ASSERT_EQUAL((const char *)nullptr,
+				     uri_get_suffix("/foo.jpg/bar", buffer));
+		CPPUNIT_ASSERT_EQUAL(0, strcmp(uri_get_suffix("/foo/bar.jpg", buffer),
+					       "jpg"));
 	}
 
 	void TestRemoveAuth() {

From 7d5442e1039869d659533eafcf15d8157bf4c465 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Sat, 1 Nov 2014 13:20:39 +0100
Subject: [PATCH 10/28] Decoder, Playlist: ignore URI query string for plugin
 detection

Use the new uri_get_suffix() overload that removes the query string.
---
 NEWS                              | 1 +
 src/TagStream.cxx                 | 3 ++-
 src/decoder/DecoderThread.cxx     | 3 ++-
 src/playlist/PlaylistRegistry.cxx | 9 ++++++---
 4 files changed, 11 insertions(+), 5 deletions(-)

diff --git a/NEWS b/NEWS
index 64490ac36..4982e98ad 100644
--- a/NEWS
+++ b/NEWS
@@ -4,6 +4,7 @@ ver 0.19.2 (not yet released)
   - m3u: don't ignore unterminated last line
   - m3u: recognize the file suffix ".m3u8"
 * decoder
+  - ignore URI query string for plugin detection
   - faad: remove workaround for ancient libfaad2 ABI bug
   - ffmpeg: recognize MIME type audio/aacp
   - mad: fix negative replay gain values
diff --git a/src/TagStream.cxx b/src/TagStream.cxx
index 639763373..6201028f6 100644
--- a/src/TagStream.cxx
+++ b/src/TagStream.cxx
@@ -46,7 +46,8 @@ tag_stream_scan(InputStream &is, const tag_handler &handler, void *ctx)
 {
 	assert(is.IsReady());
 
-	const char *const suffix = uri_get_suffix(is.GetURI());
+	UriSuffixBuffer suffix_buffer;
+	const char *const suffix = uri_get_suffix(is.GetURI(), suffix_buffer);
 	const char *const mime = is.GetMimeType();
 
 	if (suffix == nullptr && mime == nullptr)
diff --git a/src/decoder/DecoderThread.cxx b/src/decoder/DecoderThread.cxx
index a39cfa6e9..dd5518b98 100644
--- a/src/decoder/DecoderThread.cxx
+++ b/src/decoder/DecoderThread.cxx
@@ -237,7 +237,8 @@ static bool
 decoder_run_stream_locked(Decoder &decoder, InputStream &is,
 			  const char *uri, bool &tried_r)
 {
-	const char *const suffix = uri_get_suffix(uri);
+	UriSuffixBuffer suffix_buffer;
+	const char *const suffix = uri_get_suffix(uri, suffix_buffer);
 
 	using namespace std::placeholders;
 	const auto f = std::bind(decoder_run_stream_plugin,
diff --git a/src/playlist/PlaylistRegistry.cxx b/src/playlist/PlaylistRegistry.cxx
index bc5932de3..4e9ef890e 100644
--- a/src/playlist/PlaylistRegistry.cxx
+++ b/src/playlist/PlaylistRegistry.cxx
@@ -139,12 +139,12 @@ static SongEnumerator *
 playlist_list_open_uri_suffix(const char *uri, Mutex &mutex, Cond &cond,
 			      const bool *tried)
 {
-	const char *suffix;
 	SongEnumerator *playlist = nullptr;
 
 	assert(uri != nullptr);
 
-	suffix = uri_get_suffix(uri);
+	UriSuffixBuffer suffix_buffer;
+	const char *const suffix = uri_get_suffix(uri, suffix_buffer);
 	if (suffix == nullptr)
 		return nullptr;
 
@@ -257,7 +257,10 @@ playlist_list_open_stream(InputStream &is, const char *uri)
 			return playlist;
 	}
 
-	const char *suffix = uri != nullptr ? uri_get_suffix(uri) : nullptr;
+	UriSuffixBuffer suffix_buffer;
+	const char *suffix = uri != nullptr
+		? uri_get_suffix(uri, suffix_buffer)
+		: nullptr;
 	if (suffix != nullptr) {
 		auto playlist = playlist_list_open_stream_suffix(is, suffix);
 		if (playlist != nullptr)

From a8770aa606a1d83f252d150530db667bc7614da9 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Sat, 1 Nov 2014 14:09:30 +0100
Subject: [PATCH 11/28] input/curl: fix curl_easy_setopt() parameter types

---
 src/input/plugins/CurlInputPlugin.cxx | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/input/plugins/CurlInputPlugin.cxx b/src/input/plugins/CurlInputPlugin.cxx
index 617805e2c..42575a3fa 100644
--- a/src/input/plugins/CurlInputPlugin.cxx
+++ b/src/input/plugins/CurlInputPlugin.cxx
@@ -720,9 +720,9 @@ CurlInputStream::InitEasy(Error &error)
 			 input_curl_writefunction);
 	curl_easy_setopt(easy, CURLOPT_WRITEDATA, this);
 	curl_easy_setopt(easy, CURLOPT_HTTP200ALIASES, http_200_aliases);
-	curl_easy_setopt(easy, CURLOPT_FOLLOWLOCATION, 1);
-	curl_easy_setopt(easy, CURLOPT_NETRC, 1);
-	curl_easy_setopt(easy, CURLOPT_MAXREDIRS, 5);
+	curl_easy_setopt(easy, CURLOPT_FOLLOWLOCATION, 1l);
+	curl_easy_setopt(easy, CURLOPT_NETRC, 1l);
+	curl_easy_setopt(easy, CURLOPT_MAXREDIRS, 5l);
 	curl_easy_setopt(easy, CURLOPT_FAILONERROR, true);
 	curl_easy_setopt(easy, CURLOPT_ERRORBUFFER, error_buffer);
 	curl_easy_setopt(easy, CURLOPT_NOPROGRESS, 1l);

From 054323c2bc8ec5f1e417f85e3293f2a91d8425d8 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Sun, 2 Nov 2014 11:04:13 +0100
Subject: [PATCH 12/28] lib/upnp/Discovery: add missing stdlib.h include

---
 src/lib/upnp/Discovery.cxx | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/lib/upnp/Discovery.cxx b/src/lib/upnp/Discovery.cxx
index 9ea78c624..1539e1512 100644
--- a/src/lib/upnp/Discovery.cxx
+++ b/src/lib/upnp/Discovery.cxx
@@ -26,6 +26,7 @@
 
 #include <upnp/upnptools.h>
 
+#include <stdlib.h>
 #include <string.h>
 
 // The service type string we are looking for.

From 432ce9b1de0f89e0f714d182980d5a562024faa5 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Sun, 2 Nov 2014 11:41:40 +0100
Subject: [PATCH 13/28] configure.ac: prepare for 0.18.17

---
 NEWS         | 2 ++
 configure.ac | 4 ++--
 2 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/NEWS b/NEWS
index 2e8c2dcdb..bea9b2ebb 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,5 @@
+ver 0.18.17 (not yet released)
+
 ver 0.18.16 (2014/09/26)
 * fix DSD breakage due to typo in configure.ac
 
diff --git a/configure.ac b/configure.ac
index 3d6b8526c..d6526e883 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,10 +1,10 @@
 AC_PREREQ(2.60)
 
-AC_INIT(mpd, 0.18.16, mpd-devel@musicpd.org)
+AC_INIT(mpd, 0.18.17, mpd-devel@musicpd.org)
 
 VERSION_MAJOR=0
 VERSION_MINOR=18
-VERSION_REVISION=0
+VERSION_REVISION=17
 VERSION_EXTRA=0
 
 AC_CONFIG_SRCDIR([src/Main.cxx])

From c37f7abb79b6c9f30a77ea605b18674acc5ffff2 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Fri, 10 Oct 2014 22:06:48 +0200
Subject: [PATCH 14/28] TagString: use g_strndup() for unterminated string

Fixes buffer overflow bug.
---
 src/tag/TagString.cxx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/tag/TagString.cxx b/src/tag/TagString.cxx
index 3e8d8c1b0..9ab095249 100644
--- a/src/tag/TagString.cxx
+++ b/src/tag/TagString.cxx
@@ -33,7 +33,7 @@ patch_utf8(const char *src, size_t length, const gchar *end)
 {
 	/* duplicate the string, and replace invalid bytes in that
 	   buffer */
-	char *dest = g_strdup(src);
+	char *dest = g_strndup(src, length);
 
 	do {
 		dest[end - src] = '?';

From c50a0cf7bf96a3eb2d49e5416dfe88dc86a589ef Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Thu, 23 Oct 2014 23:29:56 +0200
Subject: [PATCH 15/28] output/roar: remove unnecessary "volatile" keyword

A mutex acts as a memory barrier, and thus "volatile" is not
necessary.
---
 src/output/RoarOutputPlugin.cxx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/output/RoarOutputPlugin.cxx b/src/output/RoarOutputPlugin.cxx
index 895a165d1..20d69f3f9 100644
--- a/src/output/RoarOutputPlugin.cxx
+++ b/src/output/RoarOutputPlugin.cxx
@@ -46,7 +46,7 @@ class RoarOutput {
 	struct roar_connection con;
 	struct roar_audio_info info;
 	mutable Mutex mutex;
-	volatile bool alive;
+	bool alive;
 
 public:
 	RoarOutput()

From 94c240a0264b7ce1693fc341778ac19ac3a535b9 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Sat, 25 Oct 2014 00:19:01 +0200
Subject: [PATCH 16/28] configure.ac: show DSD in result

---
 configure.ac | 1 +
 1 file changed, 1 insertion(+)

diff --git a/configure.ac b/configure.ac
index d6526e883..05a4f5050 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1523,6 +1523,7 @@ results(un,[UNIX Domain Sockets])
 printf '\nFile format support:\n\t'
 results(aac, [AAC])
 results(adplug, [AdPlug])
+results(dsd, [DSD])
 results(sidplay, [C64 SID])
 results(ffmpeg, [FFMPEG])
 results(flac, [FLAC])

From bccd4ef2f72f723b7abb1d7f6d004a70cad735aa Mon Sep 17 00:00:00 2001
From: Steven OBrien <steven_obrien1@yahoo.co.uk>
Date: Sun, 9 Feb 2014 15:47:45 +0000
Subject: [PATCH 17/28] decoder/ffmpeg: recognize MIME type audio/aacp

---
 NEWS                                | 2 ++
 src/decoder/FfmpegDecoderPlugin.cxx | 1 +
 2 files changed, 3 insertions(+)

diff --git a/NEWS b/NEWS
index bea9b2ebb..0706e447c 100644
--- a/NEWS
+++ b/NEWS
@@ -1,4 +1,6 @@
 ver 0.18.17 (not yet released)
+* decoder
+  - ffmpeg: recognize MIME type audio/aacp
 
 ver 0.18.16 (2014/09/26)
 * fix DSD breakage due to typo in configure.ac
diff --git a/src/decoder/FfmpegDecoderPlugin.cxx b/src/decoder/FfmpegDecoderPlugin.cxx
index 9cd26c4fa..104129ad9 100644
--- a/src/decoder/FfmpegDecoderPlugin.cxx
+++ b/src/decoder/FfmpegDecoderPlugin.cxx
@@ -643,6 +643,7 @@ static const char *const ffmpeg_mime_types[] = {
 	"audio/8svx",
 	"audio/16sv",
 	"audio/aac",
+	"audio/aacp",
 	"audio/ac3",
 	"audio/aiff"
 	"audio/amr",

From f6b2899dd2f2b7985da0cf3734a7276ea54e23a2 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Sat, 25 Oct 2014 20:42:50 +0200
Subject: [PATCH 18/28] decoder/faad: remove workaround for ancient libfaad2
 ABI bug

Many years ago, FAAD had a serious ABI bug: the NeAACDecInit()
prototype in its header declared the "samplerate" parameter to be
"unsigned long *", but internally, the function assumed it was
"uint32_t *" instead.  On 32 bit machines, that was no difference, but
on 64 bit, this left one portion of the return value uninitialized;
and worse, on big-endian, the wrong word was filled.  This bug had to
be worked around in MPD (commit 9c4e97a6).

A few months later, the bug was fixed in the FAAD CVS in commit 1.117
on file libfaad/decoder.c; the commit message was:

 "Use public headers internally to prevent duplicate declarations"

The commit message was too brief at best; the problem was not
duplicate declarations, but a prototype mismatch.  No mention of the
bug fix in the ChangeLog.

The MPD project never learned about this bug fix, and so MPD would
always pass a "uin32_t *" dressed up as a "unsigned long *".  Nearly 6
years later, it's about time to fix this second ABI problem.  Let's
kill the workaround!
---
 NEWS                              |  1 +
 m4/faad.m4                        | 31 +------------------------------
 src/decoder/FaadDecoderPlugin.cxx | 12 ++----------
 3 files changed, 4 insertions(+), 40 deletions(-)

diff --git a/NEWS b/NEWS
index 0706e447c..f27bd8c4f 100644
--- a/NEWS
+++ b/NEWS
@@ -1,5 +1,6 @@
 ver 0.18.17 (not yet released)
 * decoder
+  - faad: remove workaround for ancient libfaad2 ABI bug
   - ffmpeg: recognize MIME type audio/aacp
 
 ver 0.18.16 (2014/09/26)
diff --git a/m4/faad.m4 b/m4/faad.m4
index 5ca520e79..9dcb1ccab 100644
--- a/m4/faad.m4
+++ b/m4/faad.m4
@@ -62,36 +62,7 @@ int main() {
 	CPPFLAGS=$oldcppflags
 fi
 
-if test x$enable_aac = xyes; then
-	oldcflags=$CFLAGS
-	oldlibs=$LIBS
-	oldcppflags=$CPPFLAGS
-	CFLAGS="$CFLAGS $FAAD_CFLAGS -Werror"
-	LIBS="$LIBS $FAAD_LIBS"
-	CPPFLAGS=$CFLAGS
-
-	AC_MSG_CHECKING(for broken libfaad headers)
-	AC_COMPILE_IFELSE([AC_LANG_SOURCE([
-#include <faad.h>
-#include <stddef.h>
-#include <stdint.h>
-
-int main() {
-	unsigned char channels;
-	uint32_t sample_rate;
-
-	NeAACDecInit2(NULL, NULL, 0, &sample_rate, &channels);
-	return 0;
-}
-	])],
-		[AC_MSG_RESULT(correct)],
-		[AC_MSG_RESULT(broken);
-		AC_DEFINE(HAVE_FAAD_LONG, 1, [Define if faad.h uses the broken "unsigned long" pointers])])
-
-	CFLAGS=$oldcflags
-	LIBS=$oldlibs
-	CPPFLAGS=$oldcppflags
-else
+if test x$enable_aac = xno; then
 	FAAD_LIBS=""
 	FAAD_CFLAGS=""
 fi
diff --git a/src/decoder/FaadDecoderPlugin.cxx b/src/decoder/FaadDecoderPlugin.cxx
index b446ac5be..ae1181b4c 100644
--- a/src/decoder/FaadDecoderPlugin.cxx
+++ b/src/decoder/FaadDecoderPlugin.cxx
@@ -277,20 +277,12 @@ faad_decoder_init(NeAACDecHandle decoder, DecoderBuffer *buffer,
 	}
 
 	uint8_t channels;
-	uint32_t sample_rate;
-#ifdef HAVE_FAAD_LONG
-	/* neaacdec.h declares all arguments as "unsigned long", but
-	   internally expects uint32_t pointers.  To avoid gcc
-	   warnings, use this workaround. */
-	unsigned long *sample_rate_p = (unsigned long *)(void *)&sample_rate;
-#else
-	uint32_t *sample_rate_p = &sample_rate;
-#endif
+	unsigned long sample_rate;
 	long nbytes = NeAACDecInit(decoder,
 				   /* deconst hack, libfaad requires this */
 				   const_cast<unsigned char *>(data),
 				   length,
-				   sample_rate_p, &channels);
+				   &sample_rate, &channels);
 	if (nbytes < 0) {
 		error.Set(faad_decoder_domain, "Not an AAC stream");
 		return false;

From c882568ccd5271a3f2c9d97a9a718706f9e71a65 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Sun, 26 Oct 2014 08:14:16 +0100
Subject: [PATCH 19/28] playlist/m3u: recognize the file suffix ".m3u8"

---
 NEWS                                  | 2 ++
 src/playlist/ExtM3uPlaylistPlugin.cxx | 3 ++-
 src/playlist/M3uPlaylistPlugin.cxx    | 1 +
 3 files changed, 5 insertions(+), 1 deletion(-)

diff --git a/NEWS b/NEWS
index f27bd8c4f..439f39c4e 100644
--- a/NEWS
+++ b/NEWS
@@ -1,4 +1,6 @@
 ver 0.18.17 (not yet released)
+* playlist
+  - m3u: recognize the file suffix ".m3u8"
 * decoder
   - faad: remove workaround for ancient libfaad2 ABI bug
   - ffmpeg: recognize MIME type audio/aacp
diff --git a/src/playlist/ExtM3uPlaylistPlugin.cxx b/src/playlist/ExtM3uPlaylistPlugin.cxx
index 8d260fec7..5ef010bda 100644
--- a/src/playlist/ExtM3uPlaylistPlugin.cxx
+++ b/src/playlist/ExtM3uPlaylistPlugin.cxx
@@ -135,7 +135,8 @@ ExtM3uPlaylist::NextSong()
 
 static const char *const extm3u_suffixes[] = {
 	"m3u",
-	NULL
+	"m3u8",
+	nullptr
 };
 
 static const char *const extm3u_mime_types[] = {
diff --git a/src/playlist/M3uPlaylistPlugin.cxx b/src/playlist/M3uPlaylistPlugin.cxx
index 3f99bdfdf..8b6adc2b6 100644
--- a/src/playlist/M3uPlaylistPlugin.cxx
+++ b/src/playlist/M3uPlaylistPlugin.cxx
@@ -61,6 +61,7 @@ M3uPlaylist::NextSong()
 
 static const char *const m3u_suffixes[] = {
 	"m3u",
+	"m3u8",
 	nullptr
 };
 

From 6ad336743d861a03df3079058fdc18eee07a3014 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Fri, 31 Oct 2014 14:59:27 +0100
Subject: [PATCH 20/28] PlaylistFile: don't allow empty playlist name

---
 NEWS                 | 1 +
 src/PlaylistFile.cxx | 4 ++++
 2 files changed, 5 insertions(+)

diff --git a/NEWS b/NEWS
index 439f39c4e..509627858 100644
--- a/NEWS
+++ b/NEWS
@@ -1,5 +1,6 @@
 ver 0.18.17 (not yet released)
 * playlist
+  - don't allow empty playlist name
   - m3u: recognize the file suffix ".m3u8"
 * decoder
   - faad: remove workaround for ancient libfaad2 ABI bug
diff --git a/src/PlaylistFile.cxx b/src/PlaylistFile.cxx
index e7dae6258..e5285ad04 100644
--- a/src/PlaylistFile.cxx
+++ b/src/PlaylistFile.cxx
@@ -69,6 +69,10 @@ spl_global_init(void)
 bool
 spl_valid_name(const char *name_utf8)
 {
+	if (*name_utf8 == 0)
+		/* empty name not allowed */
+		return false;
+
 	/*
 	 * Not supporting '/' was done out of laziness, and we should
 	 * really strive to support it in the future.

From 674091424e715fddd8fbfe8146f351da5bf84974 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Sat, 1 Nov 2014 12:45:47 +0100
Subject: [PATCH 21/28] util/UriUtil: add uri_get_suffix() overload that
 ignores query string

---
 src/util/UriUtil.cxx | 17 +++++++++++++++++
 src/util/UriUtil.hxx | 11 +++++++++++
 test/test_util.cxx   | 19 +++++++++++++++++++
 3 files changed, 47 insertions(+)

diff --git a/src/util/UriUtil.cxx b/src/util/UriUtil.cxx
index 2609db2cf..1783fbca5 100644
--- a/src/util/UriUtil.cxx
+++ b/src/util/UriUtil.cxx
@@ -44,6 +44,23 @@ uri_get_suffix(const char *uri)
 	return suffix;
 }
 
+const char *
+uri_get_suffix(const char *uri, UriSuffixBuffer &buffer)
+{
+	const char *suffix = uri_get_suffix(uri);
+	if (suffix == nullptr)
+		return nullptr;
+
+	const char *q = strchr(suffix, '?');
+	if (q != nullptr && size_t(q - suffix) < sizeof(buffer.data)) {
+		memcpy(buffer.data, suffix, q - suffix);
+		buffer.data[q - suffix] = 0;
+		suffix = buffer.data;
+	}
+
+	return suffix;
+}
+
 static const char *
 verify_uri_segment(const char *p)
 {
diff --git a/src/util/UriUtil.hxx b/src/util/UriUtil.hxx
index 78d0a6bff..1c6bce3ff 100644
--- a/src/util/UriUtil.hxx
+++ b/src/util/UriUtil.hxx
@@ -35,6 +35,17 @@ gcc_pure
 const char *
 uri_get_suffix(const char *uri);
 
+struct UriSuffixBuffer {
+	char data[8];
+};
+
+/**
+ * Returns the file name suffix, ignoring the query string.
+ */
+gcc_pure
+const char *
+uri_get_suffix(const char *uri, UriSuffixBuffer &buffer);
+
 /**
  * Returns true if this is a safe "local" URI:
  *
diff --git a/test/test_util.cxx b/test/test_util.cxx
index a472391a3..91e87957f 100644
--- a/test/test_util.cxx
+++ b/test/test_util.cxx
@@ -33,6 +33,25 @@ public:
 				     uri_get_suffix(".jpg"));
 		CPPUNIT_ASSERT_EQUAL((const char *)nullptr,
 				     uri_get_suffix("/foo/.jpg"));
+
+		/* the first overload does not eliminate the query
+		   string */
+		CPPUNIT_ASSERT_EQUAL(0, strcmp(uri_get_suffix("/foo/bar.jpg?query_string"),
+					       "jpg?query_string"));
+
+		/* ... but the second one does */
+		UriSuffixBuffer buffer;
+		CPPUNIT_ASSERT_EQUAL(0, strcmp(uri_get_suffix("/foo/bar.jpg?query_string",
+							      buffer),
+					       "jpg"));
+
+		/* repeat some of the above tests with the second overload */
+		CPPUNIT_ASSERT_EQUAL((const char *)nullptr,
+				     uri_get_suffix("/foo/bar", buffer));
+		CPPUNIT_ASSERT_EQUAL((const char *)nullptr,
+				     uri_get_suffix("/foo.jpg/bar", buffer));
+		CPPUNIT_ASSERT_EQUAL(0, strcmp(uri_get_suffix("/foo/bar.jpg", buffer),
+					       "jpg"));
 	}
 
 	void TestRemoveAuth() {

From 32b5654a6e7738211e6aa18ab8089cc6328aa1fa Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Sat, 1 Nov 2014 13:20:39 +0100
Subject: [PATCH 22/28] Decoder, Playlist: ignore URI query string for plugin
 detection

Use the new uri_get_suffix() overload that removes the query string.
---
 NEWS                     |  1 +
 src/DecoderThread.cxx    |  3 ++-
 src/PlaylistRegistry.cxx | 11 ++++++-----
 3 files changed, 9 insertions(+), 6 deletions(-)

diff --git a/NEWS b/NEWS
index 509627858..1cebfd2db 100644
--- a/NEWS
+++ b/NEWS
@@ -3,6 +3,7 @@ ver 0.18.17 (not yet released)
   - don't allow empty playlist name
   - m3u: recognize the file suffix ".m3u8"
 * decoder
+  - ignore URI query string for plugin detection
   - faad: remove workaround for ancient libfaad2 ABI bug
   - ffmpeg: recognize MIME type audio/aacp
 
diff --git a/src/DecoderThread.cxx b/src/DecoderThread.cxx
index cf21534f0..7ee36faca 100644
--- a/src/DecoderThread.cxx
+++ b/src/DecoderThread.cxx
@@ -212,7 +212,8 @@ static bool
 decoder_run_stream_locked(Decoder &decoder, InputStream &is,
 			  const char *uri, bool &tried_r)
 {
-	const char *const suffix = uri_get_suffix(uri);
+	UriSuffixBuffer suffix_buffer;
+	const char *const suffix = uri_get_suffix(uri, suffix_buffer);
 
 	using namespace std::placeholders;
 	const auto f = std::bind(decoder_run_stream_plugin,
diff --git a/src/PlaylistRegistry.cxx b/src/PlaylistRegistry.cxx
index 9afbe349d..f81978322 100644
--- a/src/PlaylistRegistry.cxx
+++ b/src/PlaylistRegistry.cxx
@@ -164,12 +164,12 @@ static SongEnumerator *
 playlist_list_open_uri_suffix(const char *uri, Mutex &mutex, Cond &cond,
 			      const bool *tried)
 {
-	const char *suffix;
 	SongEnumerator *playlist = nullptr;
 
 	assert(uri != nullptr);
 
-	suffix = uri_get_suffix(uri);
+	UriSuffixBuffer suffix_buffer;
+	const char *const suffix = uri_get_suffix(uri, suffix_buffer);
 	if (suffix == nullptr)
 		return nullptr;
 
@@ -273,8 +273,6 @@ playlist_list_open_stream_suffix(InputStream &is, const char *suffix)
 SongEnumerator *
 playlist_list_open_stream(InputStream &is, const char *uri)
 {
-	const char *suffix;
-
 	is.LockWaitReady();
 
 	const char *const mime = is.GetMimeType();
@@ -284,7 +282,10 @@ playlist_list_open_stream(InputStream &is, const char *uri)
 			return playlist;
 	}
 
-	suffix = uri != nullptr ? uri_get_suffix(uri) : nullptr;
+	UriSuffixBuffer suffix_buffer;
+	const char *suffix = uri != nullptr
+		? uri_get_suffix(uri, suffix_buffer)
+		: nullptr;
 	if (suffix != nullptr) {
 		auto playlist = playlist_list_open_stream_suffix(is, suffix);
 		if (playlist != nullptr)

From ec3191f50279c432ffef7449133db1d4c433120c Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Sat, 1 Nov 2014 14:09:30 +0100
Subject: [PATCH 23/28] input/curl: fix curl_easy_setopt() parameter types

---
 src/input/CurlInputPlugin.cxx | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/input/CurlInputPlugin.cxx b/src/input/CurlInputPlugin.cxx
index b78545951..031ebfea6 100644
--- a/src/input/CurlInputPlugin.cxx
+++ b/src/input/CurlInputPlugin.cxx
@@ -983,10 +983,10 @@ input_curl_easy_init(struct input_curl *c, Error &error)
 			 input_curl_writefunction);
 	curl_easy_setopt(c->easy, CURLOPT_WRITEDATA, c);
 	curl_easy_setopt(c->easy, CURLOPT_HTTP200ALIASES, http_200_aliases);
-	curl_easy_setopt(c->easy, CURLOPT_FOLLOWLOCATION, 1);
-	curl_easy_setopt(c->easy, CURLOPT_NETRC, 1);
-	curl_easy_setopt(c->easy, CURLOPT_MAXREDIRS, 5);
-	curl_easy_setopt(c->easy, CURLOPT_FAILONERROR, true);
+	curl_easy_setopt(c->easy, CURLOPT_FOLLOWLOCATION, 1l);
+	curl_easy_setopt(c->easy, CURLOPT_NETRC, 1l);
+	curl_easy_setopt(c->easy, CURLOPT_MAXREDIRS, 5l);
+	curl_easy_setopt(c->easy, CURLOPT_FAILONERROR, 1l);
 	curl_easy_setopt(c->easy, CURLOPT_ERRORBUFFER, c->error);
 	curl_easy_setopt(c->easy, CURLOPT_NOPROGRESS, 1l);
 	curl_easy_setopt(c->easy, CURLOPT_NOSIGNAL, 1l);

From 05c63af7c473de374406d76e146d73245de10a2b Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Sun, 2 Nov 2014 12:59:16 +0100
Subject: [PATCH 24/28] InputStream: add method ClearMimeType()

---
 src/input/InputStream.hxx | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/src/input/InputStream.hxx b/src/input/InputStream.hxx
index 15c350103..81b903ba2 100644
--- a/src/input/InputStream.hxx
+++ b/src/input/InputStream.hxx
@@ -200,6 +200,10 @@ public:
 		return mime.empty() ? nullptr : mime.c_str();
 	}
 
+	void ClearMimeType() {
+		mime.clear();
+	}
+
 	gcc_nonnull_all
 	void SetMimeType(const char *_mime) {
 		assert(!ready);

From a2eb14f3b379c966b259825c91c154f475f13eb6 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Sun, 2 Nov 2014 13:00:25 +0100
Subject: [PATCH 25/28] AsyncInputStream: add method ClearTag()

---
 src/input/AsyncInputStream.hxx | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/src/input/AsyncInputStream.hxx b/src/input/AsyncInputStream.hxx
index 7935f1a17..d1f0c3b9d 100644
--- a/src/input/AsyncInputStream.hxx
+++ b/src/input/AsyncInputStream.hxx
@@ -83,6 +83,10 @@ protected:
 	 */
 	void SetTag(Tag *_tag);
 
+	void ClearTag() {
+		SetTag(nullptr);
+	}
+
 	void Pause();
 
 	bool IsPaused() const {

From 56f763a4a83f6410b1bd9e1f8b41240df535ebc9 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Sun, 2 Nov 2014 12:59:45 +0100
Subject: [PATCH 26/28] input/curl: forget Content-Length (and more) after
 redirect

Fixes playback of redirected streams.
---
 NEWS                                  |  2 ++
 src/input/plugins/CurlInputPlugin.cxx | 26 ++++++++++++++++++++++++++
 2 files changed, 28 insertions(+)

diff --git a/NEWS b/NEWS
index 2f04931e6..a9fabed98 100644
--- a/NEWS
+++ b/NEWS
@@ -1,4 +1,6 @@
 ver 0.19.2 (not yet released)
+* input
+  - curl: fix redirected streams
 * playlist
   - don't allow empty playlist name
   - m3u: don't ignore unterminated last line
diff --git a/src/input/plugins/CurlInputPlugin.cxx b/src/input/plugins/CurlInputPlugin.cxx
index 0028158a3..1e1a46108 100644
--- a/src/input/plugins/CurlInputPlugin.cxx
+++ b/src/input/plugins/CurlInputPlugin.cxx
@@ -109,6 +109,13 @@ struct CurlInputStream final : public AsyncInputStream {
 	 */
 	void FreeEasyIndirect();
 
+	/**
+	 * Called when a new response begins.  This is used to discard
+	 * headers from previous responses (for example authentication
+	 * and redirects).
+	 */
+	void ResponseBoundary();
+
 	void HeaderReceived(const char *name, std::string &&value);
 
 	size_t DataReceived(const void *ptr, size_t size);
@@ -597,6 +604,20 @@ CurlInputStream::~CurlInputStream()
 	FreeEasyIndirect();
 }
 
+inline void
+CurlInputStream::ResponseBoundary()
+{
+	/* undo all effects of HeaderReceived() because the previous
+	   response was not applicable for this stream */
+
+	seekable = false;
+	size = UNKNOWN_SIZE;
+	ClearMimeType();
+	ClearTag();
+
+	// TODO: reset the IcyInputStream?
+}
+
 inline void
 CurlInputStream::HeaderReceived(const char *name, std::string &&value)
 {
@@ -645,6 +666,11 @@ input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream)
 	size *= nmemb;
 
 	const char *header = (const char *)ptr;
+	if (size > 5 && memcmp(header, "HTTP/", 5) == 0) {
+		c.ResponseBoundary();
+		return size;
+	}
+
 	const char *end = header + size;
 
 	char name[64];

From 38a0d15190a8ff45749dd7f048812ace35b9be7d Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Sun, 2 Nov 2014 13:06:20 +0100
Subject: [PATCH 27/28] release v0.18.17

---
 NEWS | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/NEWS b/NEWS
index 1cebfd2db..9be75c42e 100644
--- a/NEWS
+++ b/NEWS
@@ -1,4 +1,4 @@
-ver 0.18.17 (not yet released)
+ver 0.18.17 (2014/11/02)
 * playlist
   - don't allow empty playlist name
   - m3u: recognize the file suffix ".m3u8"

From 6a7f6cdacd81877276563c42fdeacad3a8deface Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Sun, 2 Nov 2014 13:46:32 +0100
Subject: [PATCH 28/28] release v0.19.2

---
 NEWS | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/NEWS b/NEWS
index 823f3dd6d..e8e84ff7e 100644
--- a/NEWS
+++ b/NEWS
@@ -1,4 +1,4 @@
-ver 0.19.2 (not yet released)
+ver 0.19.2 (2014/11/02)
 * input
   - curl: fix redirected streams
 * playlist