diff --git a/NEWS b/NEWS
index df4904e15..53bdd6763 100644
--- a/NEWS
+++ b/NEWS
@@ -1,4 +1,6 @@
 ver 0.21.1 (not yet released)
+* protocol
+  - allow escaping quotes in filter expressions
 * decoder
   - ffmpeg: fix build failure with non-standard FFmpeg installation path
 * fix build failure on Linux-PowerPC
diff --git a/doc/protocol.rst b/doc/protocol.rst
index 5ace50aff..91208c8d5 100644
--- a/doc/protocol.rst
+++ b/doc/protocol.rst
@@ -184,6 +184,36 @@ Prior to MPD 0.21, the syntax looked like this::
 
  find TYPE VALUE
 
+Escaping String Values
+----------------------
+
+String values are quoted with single or double quotes, and special
+characters within those values must be escaped with the backslash
+(``\``).  Keep in mind that the backslash is also the escape character
+on the protocol level, which means you may need to use double
+backslash.
+
+Example expression which matches an artist named ``foo'bar"``::
+
+ (artist "foo\'bar\"")
+
+At the protocol level, the command must look like this::
+
+ find "(artist \"foo\\'bar\\\"\")"
+
+The double quotes enclosing the artist name must be escaped because
+they are inside a double-quoted ``find`` parameter.  The single quote
+inside that artist name must be escaped with two backslashes; one to
+escape the single quote, and another one because the backslash inside
+the string inside the parameter needs to be escaped as well.  The
+double quote has three confusing backslashes: two to build one
+backslash, and another one to escape the double quote on the protocol
+level.  Phew!
+
+To reduce confusion, you should use a library such as `libmpdclient
+<https://www.musicpd.org/libs/libmpdclient/>`_ which escapes command
+arguments for you.
+
 .. _tags:
 
 Tags
diff --git a/src/song/BaseSongFilter.cxx b/src/song/BaseSongFilter.cxx
index b30527478..393a9f3be 100644
--- a/src/song/BaseSongFilter.cxx
+++ b/src/song/BaseSongFilter.cxx
@@ -19,13 +19,14 @@
 
 #include "config.h"
 #include "BaseSongFilter.hxx"
+#include "Escape.hxx"
 #include "LightSong.hxx"
 #include "util/UriUtil.hxx"
 
 std::string
 BaseSongFilter::ToExpression() const noexcept
 {
-	return "(base \"" + value + "\")";
+	return "(base \"" + EscapeFilterString(value) + "\")";
 }
 
 bool
diff --git a/src/song/Escape.cxx b/src/song/Escape.cxx
new file mode 100644
index 000000000..25c64789b
--- /dev/null
+++ b/src/song/Escape.cxx
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2003-2018 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 "Escape.hxx"
+
+static constexpr bool
+MustEscape(char ch) noexcept
+{
+	return ch == '"' || ch == '\'' || ch == '\\';
+}
+
+std::string
+EscapeFilterString(const std::string &src) noexcept
+{
+	std::string result;
+	result.reserve(src.length() + 16);
+
+	for (char ch : src) {
+		if (MustEscape(ch))
+			result.push_back('\\');
+		result.push_back(ch);
+	}
+
+	return result;
+}
diff --git a/src/song/Escape.hxx b/src/song/Escape.hxx
new file mode 100644
index 000000000..b63faf06e
--- /dev/null
+++ b/src/song/Escape.hxx
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2003-2018 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.
+ */
+
+#ifndef MPD_SONG_ESCAPE_HXX
+#define MPD_SONG_ESCAPE_HXX
+
+#include "util/Compiler.h"
+
+#include <string>
+
+gcc_pure
+std::string
+EscapeFilterString(const std::string &src) noexcept;
+
+#endif
diff --git a/src/song/Filter.cxx b/src/song/Filter.cxx
index ff1be66ee..d873ea3bd 100644
--- a/src/song/Filter.cxx
+++ b/src/song/Filter.cxx
@@ -173,13 +173,26 @@ ExpectQuoted(const char *&s)
 	if (!IsQuote(quote))
 		throw std::runtime_error("Quoted string expected");
 
-	const char *begin = s;
-	const char *end = strchr(s, quote);
-	if (end == nullptr)
-		throw std::runtime_error("Closing quote not found");
+	char buffer[4096];
+	size_t length = 0;
 
-	s = StripLeft(end + 1);
-	return {begin, end};
+	while (*s != quote) {
+		if (*s == '\\')
+			/* backslash escapes the following character */
+			++s;
+
+		if (*s == 0)
+			throw std::runtime_error("Closing quote not found");
+
+		buffer[length++] = *s++;
+
+		if (length >= sizeof(buffer))
+			throw std::runtime_error("Quoted value is too long");
+	}
+
+	s = StripLeft(s + 1);
+
+	return {buffer, length};
 }
 
 ISongFilterPtr
diff --git a/src/song/TagSongFilter.cxx b/src/song/TagSongFilter.cxx
index c05f1765b..a6578331b 100644
--- a/src/song/TagSongFilter.cxx
+++ b/src/song/TagSongFilter.cxx
@@ -19,6 +19,7 @@
 
 #include "config.h"
 #include "TagSongFilter.hxx"
+#include "Escape.hxx"
 #include "LightSong.hxx"
 #include "tag/Tag.hxx"
 #include "tag/Fallback.hxx"
@@ -30,7 +31,7 @@ TagSongFilter::ToExpression() const noexcept
 		? "any"
 		: tag_item_names[type];
 
-	return std::string("(") + name + " " + (negated ? "!=" : "==") + " \"" + filter.GetValue() + "\")";
+	return std::string("(") + name + " " + (negated ? "!=" : "==") + " \"" + EscapeFilterString(filter.GetValue()) + "\")";
 }
 
 bool
diff --git a/src/song/UriSongFilter.cxx b/src/song/UriSongFilter.cxx
index 4445d8c08..584595b65 100644
--- a/src/song/UriSongFilter.cxx
+++ b/src/song/UriSongFilter.cxx
@@ -19,12 +19,13 @@
 
 #include "config.h"
 #include "UriSongFilter.hxx"
+#include "Escape.hxx"
 #include "LightSong.hxx"
 
 std::string
 UriSongFilter::ToExpression() const noexcept
 {
-	return std::string("(file ") + (negated ? "!=" : "==") + " \"" + filter.GetValue() + "\")";
+	return std::string("(file ") + (negated ? "!=" : "==") + " \"" + EscapeFilterString(filter.GetValue()) + "\")";
 }
 
 bool
diff --git a/src/song/meson.build b/src/song/meson.build
index 5e4cd534d..71eda8d65 100644
--- a/src/song/meson.build
+++ b/src/song/meson.build
@@ -1,6 +1,7 @@
 song = static_library(
   'song',
   'DetachedSong.cxx',
+  'Escape.cxx',
   'StringFilter.cxx',
   'UriSongFilter.cxx',
   'BaseSongFilter.cxx',