diff options
author | Max Kellermann <max@musicpd.org> | 2018-11-02 19:15:08 +0100 |
---|---|---|
committer | Max Kellermann <max@musicpd.org> | 2018-11-02 19:15:08 +0100 |
commit | 528f5b9cb94821e9b5e575b8ce99440bbbfc38ad (patch) | |
tree | a42a21ecb05dcbfc12351bdb08635b0c8dcffdef | |
parent | 96ae0ec93aa3a008ec15dd595bfe1890f411d58c (diff) |
song/Filter: allow escaping quotes in filter expressions
Closes #397
-rw-r--r-- | NEWS | 2 | ||||
-rw-r--r-- | doc/protocol.rst | 30 | ||||
-rw-r--r-- | src/song/BaseSongFilter.cxx | 3 | ||||
-rw-r--r-- | src/song/Escape.cxx | 41 | ||||
-rw-r--r-- | src/song/Escape.hxx | 31 | ||||
-rw-r--r-- | src/song/Filter.cxx | 25 | ||||
-rw-r--r-- | src/song/TagSongFilter.cxx | 3 | ||||
-rw-r--r-- | src/song/UriSongFilter.cxx | 3 | ||||
-rw-r--r-- | src/song/meson.build | 1 |
9 files changed, 130 insertions, 9 deletions
@@ -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', |