From 90ea3bf985b94e1e2bc3dc77d79fc2f948c303b3 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@musicpd.org>
Date: Mon, 29 Jul 2019 09:52:18 +0200
Subject: [PATCH] playlist/Song: support backslash in relative URIs

Closes https://github.com/MusicPlayerDaemon/MPD/issues/607
---
 NEWS                          |  2 ++
 src/playlist/PlaylistSong.cxx | 16 ++++++++++++++++
 test/test_translate_song.cxx  | 20 ++++++++++++++++++++
 3 files changed, 38 insertions(+)

diff --git a/NEWS b/NEWS
index 3e81a3389..cc13617b9 100644
--- a/NEWS
+++ b/NEWS
@@ -1,4 +1,6 @@
 ver 0.21.12 (not yet released)
+* Windows
+  - support backslash in relative URIs loaded from playlists
 
 ver 0.21.11 (2019/07/03)
 * input
diff --git a/src/playlist/PlaylistSong.cxx b/src/playlist/PlaylistSong.cxx
index 7d63fe76c..543d8d96f 100644
--- a/src/playlist/PlaylistSong.cxx
+++ b/src/playlist/PlaylistSong.cxx
@@ -66,6 +66,22 @@ playlist_check_translate_song(DetachedSong &song, const char *base_uri,
 		base_uri = nullptr;
 
 	const char *uri = song.GetURI();
+
+#ifdef _WIN32
+	if (!PathTraitsUTF8::IsAbsolute(uri) && strchr(uri, '\\') != nullptr) {
+		/* Windows uses the backslash as path separator, but
+		   the MPD protocol uses the (forward) slash by
+		   definition; to allow backslashes in relative URIs
+		   loaded from playlist files, this step converts all
+		   backslashes to (forward) slashes */
+
+		std::string new_uri(uri);
+		std::replace(new_uri.begin(), new_uri.end(), '\\', '/');
+		song.SetURI(std::move(new_uri));
+		uri = song.GetURI();
+	}
+#endif
+
 	if (base_uri != nullptr && !uri_has_scheme(uri) &&
 	    !PathTraitsUTF8::IsAbsolute(uri))
 		song.SetURI(PathTraitsUTF8::Build(base_uri, uri));
diff --git a/test/test_translate_song.cxx b/test/test_translate_song.cxx
index 604e0c59b..cac712785 100644
--- a/test/test_translate_song.cxx
+++ b/test/test_translate_song.cxx
@@ -270,3 +270,23 @@ TEST_F(TranslateSongTest, Relative)
 						  insecure_loader));
 	EXPECT_EQ(se, ToString(song4));
 }
+
+TEST_F(TranslateSongTest, Backslash)
+{
+	const SongLoader loader(reinterpret_cast<const Database *>(1),
+				storage);
+
+	DetachedSong song1("foo\\bar.ogg", MakeTag2b());
+#ifdef _WIN32
+	/* on Windows, all backslashes are converted to slashes in
+	   relative paths from playlists */
+	auto se = ToString(DetachedSong(uri2, MakeTag2c()));
+	EXPECT_TRUE(playlist_check_translate_song(song1, nullptr,
+						  loader));
+	EXPECT_EQ(se, ToString(song1));
+#else
+	/* backslash only supported on Windows */
+	EXPECT_FALSE(playlist_check_translate_song(song1, nullptr,
+						   loader));
+#endif
+}