summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMax Kellermann <max@musicpd.org>2018-11-02 19:15:08 +0100
committerMax Kellermann <max@musicpd.org>2018-11-02 19:15:08 +0100
commit528f5b9cb94821e9b5e575b8ce99440bbbfc38ad (patch)
treea42a21ecb05dcbfc12351bdb08635b0c8dcffdef
parent96ae0ec93aa3a008ec15dd595bfe1890f411d58c (diff)
song/Filter: allow escaping quotes in filter expressions
Closes #397
-rw-r--r--NEWS2
-rw-r--r--doc/protocol.rst30
-rw-r--r--src/song/BaseSongFilter.cxx3
-rw-r--r--src/song/Escape.cxx41
-rw-r--r--src/song/Escape.hxx31
-rw-r--r--src/song/Filter.cxx25
-rw-r--r--src/song/TagSongFilter.cxx3
-rw-r--r--src/song/UriSongFilter.cxx3
-rw-r--r--src/song/meson.build1
9 files changed, 130 insertions, 9 deletions
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;
+
+ 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);
- s = StripLeft(end + 1);
- return {begin, end};
+ 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',