Filter.cxx 9.87 KB
Newer Older
1
/*
2
 * Copyright 2003-2018 The Music Player Daemon Project
3
 * http://www.musicpd.org
4 5 6 7 8 9 10 11 12 13
 *
 * 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.
14 15 16 17
 *
 * 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.
18 19
 */

20
#include "config.h"
21
#include "Filter.hxx"
22
#include "NotSongFilter.hxx"
23 24 25 26
#include "UriSongFilter.hxx"
#include "BaseSongFilter.hxx"
#include "TagSongFilter.hxx"
#include "ModifiedSinceSongFilter.hxx"
27
#include "AudioFormatSongFilter.hxx"
28
#include "LightSong.hxx"
29
#include "AudioParser.hxx"
30
#include "tag/ParseName.hxx"
31
#include "tag/Tag.hxx"
32
#include "util/CharUtil.hxx"
33
#include "util/ChronoUtil.hxx"
34
#include "util/ConstBuffer.hxx"
35
#include "util/RuntimeError.hxx"
36
#include "util/StringAPI.hxx"
37
#include "util/StringCompare.hxx"
38
#include "util/StringStrip.hxx"
39
#include "util/StringView.hxx"
40
#include "util/ASCII.hxx"
41
#include "util/TimeISO8601.hxx"
42
#include "util/UriUtil.hxx"
43
#include "lib/icu/CaseFold.hxx"
44

45
#include <exception>
46

47
#include <assert.h>
48 49
#include <stdlib.h>

50 51
#define LOCATE_TAG_FILE_KEY     "file"
#define LOCATE_TAG_FILE_KEY_OLD "filename"
52 53
#define LOCATE_TAG_ANY_KEY      "any"

54 55 56 57 58 59 60
enum {
	/**
	 * Limit the search to files within the given directory.
	 */
	LOCATE_TAG_BASE_TYPE = TAG_NUM_OF_ITEM_TYPES + 1,

	LOCATE_TAG_MODIFIED_SINCE,
61
	LOCATE_TAG_AUDIO_FORMAT,
62 63 64
	LOCATE_TAG_FILE_TYPE,
	LOCATE_TAG_ANY_TYPE,
};
65

66 67 68 69 70
/**
 * @return #TAG_NUM_OF_ITEM_TYPES on error
 */
gcc_pure
static unsigned
71
locate_parse_type(const char *str) noexcept
72
{
73 74
	if (StringEqualsCaseASCII(str, LOCATE_TAG_FILE_KEY) ||
	    StringEqualsCaseASCII(str, LOCATE_TAG_FILE_KEY_OLD))
75 76
		return LOCATE_TAG_FILE_TYPE;

77
	if (StringEqualsCaseASCII(str, LOCATE_TAG_ANY_KEY))
78 79
		return LOCATE_TAG_ANY_TYPE;

80 81 82
	if (strcmp(str, "base") == 0)
		return LOCATE_TAG_BASE_TYPE;

83 84 85
	if (strcmp(str, "modified-since") == 0)
		return LOCATE_TAG_MODIFIED_SINCE;

86 87 88
	if (StringEqualsCaseASCII(str, "AudioFormat"))
		return LOCATE_TAG_AUDIO_FORMAT;

89
	return tag_name_parse_i(str);
90 91
}

92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
SongFilter::SongFilter(TagType tag, const char *value, bool fold_case)
{
	and_filter.AddItem(std::make_unique<TagSongFilter>(tag, value,
							   fold_case, false));
}

SongFilter::~SongFilter()
{
	/* this destructor exists here just so it won't get inlined */
}

std::string
SongFilter::ToExpression() const noexcept
{
	return and_filter.ToExpression();
}

109 110
static std::chrono::system_clock::time_point
ParseTimeStamp(const char *s)
111 112 113 114 115 116 117
{
	assert(s != nullptr);

	char *endptr;
	unsigned long long value = strtoull(s, &endptr, 10);
	if (*endptr == 0 && endptr > s)
		/* it's an integral UNIX time stamp */
118
		return std::chrono::system_clock::from_time_t((time_t)value);
119

120
	/* try ISO 8601 */
121
	return ParseISO8601(s);
122 123
}

124 125 126
static constexpr bool
IsTagNameChar(char ch) noexcept
{
127
	return IsAlphaASCII(ch) || ch == '_' || ch == '-';
128 129 130 131 132 133 134 135 136 137 138
}

static const char *
FirstNonTagNameChar(const char *s) noexcept
{
	while (IsTagNameChar(*s))
		++s;
	return s;
}

static auto
139
ExpectWord(const char *&s)
140
{
141
	const char *begin = s;
142 143
	const char *end = FirstNonTagNameChar(s);
	if (end == s)
144
		throw std::runtime_error("Word expected");
145 146

	s = StripLeft(end);
147 148 149 150 151 152 153
	return std::string(begin, end);
}

static auto
ExpectFilterType(const char *&s)
{
	const auto name = ExpectWord(s);
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184

	const auto type = locate_parse_type(name.c_str());
	if (type == TAG_NUM_OF_ITEM_TYPES)
		throw FormatRuntimeError("Unknown filter type: %s",
					 name.c_str());

	return type;
}

static constexpr bool
IsQuote(char ch) noexcept
{
	return ch == '"' || ch == '\'';
}

static std::string
ExpectQuoted(const char *&s)
{
	const char quote = *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");

	s = StripLeft(end + 1);
	return {begin, end};
}

185 186
ISongFilterPtr
SongFilter::ParseExpression(const char *&s, bool fold_case)
187 188 189 190 191
{
	assert(*s == '(');

	s = StripLeft(s + 1);

192 193 194 195 196 197 198
	if (*s == '(') {
		auto first = ParseExpression(s, fold_case);
		if (*s == ')') {
			++s;
			return first;
		}

199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
		if (ExpectWord(s) != "AND")
			throw std::runtime_error("'AND' expected");

		auto and_filter = std::make_unique<AndSongFilter>();
		and_filter->AddItem(std::move(first));

		while (true) {
			and_filter->AddItem(ParseExpression(s, fold_case));

			if (*s == ')') {
				++s;
				return and_filter;
			}

			if (ExpectWord(s) != "AND")
				throw std::runtime_error("'AND' expected");
		}
216
	}
217

218 219 220 221 222 223 224 225 226 227 228 229 230 231
	if (*s == '!') {
		s = StripLeft(s + 1);

		if (*s != '(')
			throw std::runtime_error("'(' expected");

		auto inner = ParseExpression(s, fold_case);
		if (*s != ')')
			throw std::runtime_error("')' expected");
		s = StripLeft(s + 1);

		return std::make_unique<NotSongFilter>(std::move(inner));
	}

232
	auto type = ExpectFilterType(s);
233 234 235 236 237

	if (type == LOCATE_TAG_MODIFIED_SINCE) {
		const auto value_s = ExpectQuoted(s);
		if (*s != ')')
			throw std::runtime_error("')' expected");
238 239
		s = StripLeft(s + 1);
		return std::make_unique<ModifiedSinceSongFilter>(ParseTimeStamp(value_s.c_str()));
240 241 242 243
	} else if (type == LOCATE_TAG_BASE_TYPE) {
		auto value = ExpectQuoted(s);
		if (*s != ')')
			throw std::runtime_error("')' expected");
244
		s = StripLeft(s + 1);
245

246
		return std::make_unique<BaseSongFilter>(std::move(value));
247
	} else if (type == LOCATE_TAG_AUDIO_FORMAT) {
248 249 250 251 252 253 254
		bool mask;
		if (s[0] == '=' && s[1] == '=')
			mask = false;
		else if (s[0] == '=' && s[1] == '~')
			mask = true;
		else
			throw std::runtime_error("'==' or '=~' expected");
255 256 257 258

		s = StripLeft(s + 2);

		const auto value = ParseAudioFormat(ExpectQuoted(s).c_str(),
259
						    mask);
260 261 262 263 264 265

		if (*s != ')')
			throw std::runtime_error("')' expected");
		s = StripLeft(s + 1);

		return std::make_unique<AudioFormatSongFilter>(value);
266
	} else {
267 268 269 270 271
		bool negated = false;
		if (s[0] == '!' && s[1] == '=')
			negated = true;
		else if (s[0] != '=' || s[1] != '=')
			throw std::runtime_error("'==' or '!=' expected");
272 273 274 275 276 277

		s = StripLeft(s + 2);
		auto value = ExpectQuoted(s);
		if (*s != ')')
			throw std::runtime_error("')' expected");

278 279 280 281 282 283 284 285 286 287 288 289 290
		s = StripLeft(s + 1);

		if (type == LOCATE_TAG_ANY_TYPE)
			type = TAG_NUM_OF_ITEM_TYPES;

		if (type == LOCATE_TAG_FILE_TYPE)
			return std::make_unique<UriSongFilter>(std::move(value),
							       fold_case,
							       negated);

		return std::make_unique<TagSongFilter>(TagType(type),
						       std::move(value),
						       fold_case, negated);
291 292 293
	}
}

294
void
295 296 297
SongFilter::Parse(const char *tag_string, const char *value, bool fold_case)
{
	unsigned tag = locate_parse_type(tag_string);
298 299 300

	switch (tag) {
	case TAG_NUM_OF_ITEM_TYPES:
301
		throw std::runtime_error("Unknown filter type");
302

303
	case LOCATE_TAG_BASE_TYPE:
304
		if (!uri_safe_local(value))
305
			throw std::runtime_error("Bad URI");
306

307
		and_filter.AddItem(std::make_unique<BaseSongFilter>(value));
308 309 310
		break;

	case LOCATE_TAG_MODIFIED_SINCE:
311
		and_filter.AddItem(std::make_unique<ModifiedSinceSongFilter>(ParseTimeStamp(value)));
312
		break;
313

314
	case LOCATE_TAG_FILE_TYPE:
315
		and_filter.AddItem(std::make_unique<UriSongFilter>(value,
316 317 318 319 320 321 322 323
								   fold_case,
								   false));
		break;

	default:
		if (tag == LOCATE_TAG_ANY_TYPE)
			tag = TAG_NUM_OF_ITEM_TYPES;

324
		and_filter.AddItem(std::make_unique<TagSongFilter>(TagType(tag),
325 326 327 328 329
								   value,
								   fold_case,
								   false));
		break;
	}
330 331
}

332
void
333
SongFilter::Parse(ConstBuffer<const char *> args, bool fold_case)
334
{
335
	if (args.empty())
336
		throw std::runtime_error("Incorrect number of filter arguments");
337

338
	do {
339 340
		if (*args.front() == '(') {
			const char *s = args.shift();
341 342
			const char *end = s;
			auto f = ParseExpression(end, fold_case);
343 344 345
			if (*end != 0)
				throw std::runtime_error("Unparsed garbage after expression");

346
			and_filter.AddItem(std::move(f));
347 348 349
			continue;
		}

350 351 352 353 354 355 356
		if (args.size < 2)
			throw std::runtime_error("Incorrect number of filter arguments");

		const char *tag = args.shift();
		const char *value = args.shift();
		Parse(tag, value, fold_case);
	} while (!args.empty());
357 358
}

359 360 361 362 363 364
void
SongFilter::Optimize() noexcept
{
	OptimizeSongFilter(and_filter);
}

365
bool
366
SongFilter::Match(const LightSong &song) const noexcept
367
{
368
	return and_filter.Match(song);
369 370
}

371 372 373
bool
SongFilter::HasFoldCase() const noexcept
{
374
	for (const auto &i : and_filter.GetItems()) {
375 376 377 378 379 380 381 382 383 384 385 386
		if (auto t = dynamic_cast<const TagSongFilter *>(i.get())) {
			if (t->GetFoldCase())
				return true;
		} else if (auto u = dynamic_cast<const UriSongFilter *>(i.get())) {
			if (u->GetFoldCase())
				return true;
		}
	}

	return false;
}

387
bool
388
SongFilter::HasOtherThanBase() const noexcept
389
{
390
	for (const auto &i : and_filter.GetItems()) {
391 392
		const auto *f = dynamic_cast<const BaseSongFilter *>(i.get());
		if (f == nullptr)
393
			return true;
394
	}
395 396 397 398

	return false;
}

399
const char *
400
SongFilter::GetBase() const noexcept
401
{
402
	for (const auto &i : and_filter.GetItems()) {
403 404 405 406
		const auto *f = dynamic_cast<const BaseSongFilter *>(i.get());
		if (f != nullptr)
			return f->GetValue();
	}
407

408
	return nullptr;
409
}
410 411 412 413 414 415 416

SongFilter
SongFilter::WithoutBasePrefix(const char *_prefix) const noexcept
{
	const StringView prefix(_prefix);
	SongFilter result;

417
	for (const auto &i : and_filter.GetItems()) {
418 419 420
		const auto *f = dynamic_cast<const BaseSongFilter *>(i.get());
		if (f != nullptr) {
			const char *s = StringAfterPrefix(f->GetValue(), prefix);
421 422 423 424 425 426 427 428
			if (s != nullptr) {
				if (*s == 0)
					continue;

				if (*s == '/') {
					++s;

					if (*s != 0)
429
						result.and_filter.AddItem(std::make_unique<BaseSongFilter>(s));
430 431 432 433 434 435

					continue;
				}
			}
		}

436
		result.and_filter.AddItem(i->Clone());
437 438 439 440
	}

	return result;
}