song/Filter: allow escaping quotes in filter expressions

Closes #397
This commit is contained in:
Max Kellermann 2018-11-02 19:15:08 +01:00
parent 96ae0ec93a
commit 528f5b9cb9
9 changed files with 130 additions and 9 deletions

2
NEWS
View File

@ -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

View File

@ -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

View File

@ -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

41
src/song/Escape.cxx Normal file
View File

@ -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;
}

31
src/song/Escape.hxx Normal file
View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -1,6 +1,7 @@
song = static_library(
'song',
'DetachedSong.cxx',
'Escape.cxx',
'StringFilter.cxx',
'UriSongFilter.cxx',
'BaseSongFilter.cxx',